@beyondwork/docx-react-component 1.0.27 → 1.0.29

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 (356) hide show
  1. package/dist/canonical-document-BLEbzL2J.d.cts +844 -0
  2. package/dist/canonical-document-BLEbzL2J.d.ts +844 -0
  3. package/dist/chunk-2FJS5GZM.js +763 -0
  4. package/dist/chunk-2FJS5GZM.js.map +1 -0
  5. package/{src/core/commands/section-layout-commands.ts → dist/chunk-2OQBZS3F.js} +106 -340
  6. package/dist/chunk-2OQBZS3F.js.map +1 -0
  7. package/dist/chunk-2S7W4KFO.js +127 -0
  8. package/dist/chunk-2S7W4KFO.js.map +1 -0
  9. package/dist/chunk-2TG72QSW.js +3874 -0
  10. package/dist/chunk-2TG72QSW.js.map +1 -0
  11. package/{src/core/commands/table-structure-commands.ts → dist/chunk-36QNIZBO.js} +126 -315
  12. package/dist/chunk-36QNIZBO.js.map +1 -0
  13. package/dist/chunk-4AQOYAW4.js +3069 -0
  14. package/dist/chunk-4AQOYAW4.js.map +1 -0
  15. package/dist/chunk-4D5EWJ3P.js +77 -0
  16. package/dist/chunk-4D5EWJ3P.js.map +1 -0
  17. package/dist/chunk-5FN54NDH.js +2257 -0
  18. package/dist/chunk-5FN54NDH.js.map +1 -0
  19. package/dist/chunk-BOYGQYRQ.js +7306 -0
  20. package/dist/chunk-BOYGQYRQ.js.map +1 -0
  21. package/dist/chunk-CN3XMECL.js +212 -0
  22. package/dist/chunk-CN3XMECL.js.map +1 -0
  23. package/dist/chunk-EBI3BX6U.js +164 -0
  24. package/dist/chunk-EBI3BX6U.js.map +1 -0
  25. package/dist/chunk-EILUG3VB.js +1275 -0
  26. package/dist/chunk-EILUG3VB.js.map +1 -0
  27. package/dist/chunk-FUDY333O.js +70 -0
  28. package/dist/chunk-FUDY333O.js.map +1 -0
  29. package/dist/chunk-GBVOWFIK.js +1237 -0
  30. package/dist/chunk-GBVOWFIK.js.map +1 -0
  31. package/dist/chunk-H4TQ3H3Y.js +262 -0
  32. package/dist/chunk-H4TQ3H3Y.js.map +1 -0
  33. package/{src/core/commands/style-commands.ts → dist/chunk-JGB3IXZO.js} +40 -113
  34. package/dist/chunk-JGB3IXZO.js.map +1 -0
  35. package/dist/chunk-KD2QRQPY.js +4342 -0
  36. package/dist/chunk-KD2QRQPY.js.map +1 -0
  37. package/dist/chunk-KLMXQVYK.js +369 -0
  38. package/dist/chunk-KLMXQVYK.js.map +1 -0
  39. package/dist/chunk-KZUG5KFQ.js +214 -0
  40. package/dist/chunk-KZUG5KFQ.js.map +1 -0
  41. package/{src/core/state/text-transaction.ts → dist/chunk-QDAQ4CJU.js} +79 -236
  42. package/dist/chunk-QDAQ4CJU.js.map +1 -0
  43. package/{src/legal/bookmarks.ts → dist/chunk-RMH72RZI.js} +44 -130
  44. package/dist/chunk-RMH72RZI.js.map +1 -0
  45. package/dist/chunk-SWKWQZXM.js +117 -0
  46. package/dist/chunk-SWKWQZXM.js.map +1 -0
  47. package/{src/core/commands/formatting-commands.ts → dist/chunk-TJBP2K4T.js} +196 -536
  48. package/dist/chunk-TJBP2K4T.js.map +1 -0
  49. package/dist/chunk-TLCEAQDQ.js +542 -0
  50. package/dist/chunk-TLCEAQDQ.js.map +1 -0
  51. package/{src/core/commands/text-commands.ts → dist/chunk-UZXBISGO.js} +86 -142
  52. package/dist/chunk-UZXBISGO.js.map +1 -0
  53. package/dist/chunk-WGBAKP3Q.js +3220 -0
  54. package/dist/chunk-WGBAKP3Q.js.map +1 -0
  55. package/dist/compare/index.cjs +5475 -0
  56. package/dist/compare/index.cjs.map +1 -0
  57. package/dist/compare/index.d.cts +114 -0
  58. package/dist/compare/index.d.ts +114 -0
  59. package/dist/compare/index.js +731 -0
  60. package/dist/compare/index.js.map +1 -0
  61. package/dist/core/commands/formatting-commands.cjs +828 -0
  62. package/dist/core/commands/formatting-commands.cjs.map +1 -0
  63. package/dist/core/commands/formatting-commands.d.cts +63 -0
  64. package/dist/core/commands/formatting-commands.d.ts +63 -0
  65. package/dist/core/commands/formatting-commands.js +37 -0
  66. package/dist/core/commands/formatting-commands.js.map +1 -0
  67. package/dist/core/commands/image-commands.cjs +2023 -0
  68. package/dist/core/commands/image-commands.cjs.map +1 -0
  69. package/dist/core/commands/image-commands.d.cts +58 -0
  70. package/dist/core/commands/image-commands.d.ts +58 -0
  71. package/dist/core/commands/image-commands.js +18 -0
  72. package/dist/core/commands/image-commands.js.map +1 -0
  73. package/dist/core/commands/section-layout-commands.cjs +477 -0
  74. package/dist/core/commands/section-layout-commands.cjs.map +1 -0
  75. package/dist/core/commands/section-layout-commands.d.cts +62 -0
  76. package/dist/core/commands/section-layout-commands.d.ts +62 -0
  77. package/dist/core/commands/section-layout-commands.js +21 -0
  78. package/dist/core/commands/section-layout-commands.js.map +1 -0
  79. package/dist/core/commands/style-commands.cjs +214 -0
  80. package/dist/core/commands/style-commands.cjs.map +1 -0
  81. package/dist/core/commands/style-commands.d.cts +13 -0
  82. package/dist/core/commands/style-commands.d.ts +13 -0
  83. package/dist/core/commands/style-commands.js +9 -0
  84. package/dist/core/commands/style-commands.js.map +1 -0
  85. package/dist/core/commands/table-structure-commands.cjs +1883 -0
  86. package/dist/core/commands/table-structure-commands.cjs.map +1 -0
  87. package/dist/core/commands/table-structure-commands.d.cts +59 -0
  88. package/dist/core/commands/table-structure-commands.d.ts +59 -0
  89. package/dist/core/commands/table-structure-commands.js +12 -0
  90. package/dist/core/commands/table-structure-commands.js.map +1 -0
  91. package/dist/core/commands/text-commands.cjs +2391 -0
  92. package/dist/core/commands/text-commands.cjs.map +1 -0
  93. package/dist/core/commands/text-commands.d.cts +24 -0
  94. package/dist/core/commands/text-commands.d.ts +24 -0
  95. package/dist/core/commands/text-commands.js +28 -0
  96. package/dist/core/commands/text-commands.js.map +1 -0
  97. package/dist/core/selection/mapping.cjs +200 -0
  98. package/dist/core/selection/mapping.cjs.map +1 -0
  99. package/dist/core/selection/mapping.d.cts +2 -0
  100. package/dist/core/selection/mapping.d.ts +2 -0
  101. package/dist/core/selection/mapping.js +31 -0
  102. package/dist/core/selection/mapping.js.map +1 -0
  103. package/dist/core/state/editor-state.cjs +2278 -0
  104. package/dist/core/state/editor-state.cjs.map +1 -0
  105. package/dist/core/state/editor-state.d.cts +2 -0
  106. package/dist/core/state/editor-state.d.ts +2 -0
  107. package/dist/core/state/editor-state.js +26 -0
  108. package/dist/core/state/editor-state.js.map +1 -0
  109. package/dist/index.cjs +38553 -0
  110. package/dist/index.cjs.map +1 -0
  111. package/dist/index.d.cts +15 -0
  112. package/dist/index.d.ts +15 -0
  113. package/dist/index.js +7856 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/io/docx-session.cjs +16236 -0
  116. package/dist/io/docx-session.cjs.map +1 -0
  117. package/dist/io/docx-session.d.cts +21 -0
  118. package/dist/io/docx-session.d.ts +21 -0
  119. package/dist/io/docx-session.js +18 -0
  120. package/dist/io/docx-session.js.map +1 -0
  121. package/dist/legal/index.cjs +3900 -0
  122. package/dist/legal/index.cjs.map +1 -0
  123. package/dist/legal/index.d.cts +86 -0
  124. package/dist/legal/index.d.ts +86 -0
  125. package/dist/legal/index.js +616 -0
  126. package/dist/legal/index.js.map +1 -0
  127. package/dist/public-types-7ZL_94cz.d.ts +1573 -0
  128. package/dist/public-types-CeMaDueh.d.cts +1573 -0
  129. package/dist/public-types.cjs +19 -0
  130. package/dist/public-types.cjs.map +1 -0
  131. package/dist/public-types.d.cts +2 -0
  132. package/dist/public-types.d.ts +2 -0
  133. package/dist/public-types.js +1 -0
  134. package/dist/public-types.js.map +1 -0
  135. package/dist/runtime/document-runtime.cjs +11140 -0
  136. package/dist/runtime/document-runtime.cjs.map +1 -0
  137. package/dist/runtime/document-runtime.d.cts +231 -0
  138. package/dist/runtime/document-runtime.d.ts +231 -0
  139. package/dist/runtime/document-runtime.js +21 -0
  140. package/dist/runtime/document-runtime.js.map +1 -0
  141. package/dist/structural-helpers-CilgOVhh.d.cts +10 -0
  142. package/dist/structural-helpers-q0Gd-eBN.d.ts +10 -0
  143. package/dist/ui-tailwind/editor-surface/search-plugin.cjs +313 -0
  144. package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +1 -0
  145. package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +67 -0
  146. package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +67 -0
  147. package/dist/ui-tailwind/editor-surface/search-plugin.js +23 -0
  148. package/dist/ui-tailwind/editor-surface/search-plugin.js.map +1 -0
  149. package/dist/ui-tailwind/index.cjs +4833 -0
  150. package/dist/ui-tailwind/index.cjs.map +1 -0
  151. package/dist/ui-tailwind/index.d.cts +617 -0
  152. package/dist/ui-tailwind/index.d.ts +617 -0
  153. package/dist/ui-tailwind/index.js +575 -0
  154. package/dist/ui-tailwind/index.js.map +1 -0
  155. package/package.json +64 -54
  156. package/src/README.md +0 -85
  157. package/src/api/README.md +0 -26
  158. package/src/api/public-types.ts +0 -1418
  159. package/src/api/session-state.ts +0 -60
  160. package/src/compare/diff-engine.ts +0 -623
  161. package/src/compare/export-redlines.ts +0 -280
  162. package/src/compare/index.ts +0 -25
  163. package/src/compare/snapshot.ts +0 -97
  164. package/src/component-inventory.md +0 -99
  165. package/src/core/README.md +0 -10
  166. package/src/core/commands/README.md +0 -3
  167. package/src/core/commands/image-commands.ts +0 -373
  168. package/src/core/commands/index.ts +0 -1757
  169. package/src/core/commands/list-commands.ts +0 -565
  170. package/src/core/commands/paragraph-layout-commands.ts +0 -339
  171. package/src/core/commands/review-commands.ts +0 -108
  172. package/src/core/commands/structural-helpers.ts +0 -309
  173. package/src/core/schema/README.md +0 -3
  174. package/src/core/schema/text-schema.ts +0 -516
  175. package/src/core/search/search-text.ts +0 -357
  176. package/src/core/selection/README.md +0 -3
  177. package/src/core/selection/mapping.ts +0 -289
  178. package/src/core/selection/review-anchors.ts +0 -183
  179. package/src/core/state/README.md +0 -3
  180. package/src/core/state/editor-state.ts +0 -892
  181. package/src/formats/xlsx/io/parse-shared-strings.ts +0 -41
  182. package/src/formats/xlsx/io/parse-sheet.ts +0 -459
  183. package/src/formats/xlsx/io/parse-styles.ts +0 -59
  184. package/src/formats/xlsx/io/parse-workbook.ts +0 -75
  185. package/src/formats/xlsx/io/serialize-shared-strings.ts +0 -72
  186. package/src/formats/xlsx/io/serialize-sheet.ts +0 -333
  187. package/src/formats/xlsx/io/serialize-styles.ts +0 -98
  188. package/src/formats/xlsx/io/serialize-workbook.ts +0 -429
  189. package/src/formats/xlsx/io/xlsx-session.ts +0 -314
  190. package/src/formats/xlsx/model/cell.ts +0 -189
  191. package/src/formats/xlsx/model/sheet.ts +0 -326
  192. package/src/formats/xlsx/model/styles.ts +0 -118
  193. package/src/formats/xlsx/model/workbook.ts +0 -453
  194. package/src/formats/xlsx/runtime/cell-commands.ts +0 -567
  195. package/src/formats/xlsx/runtime/sheet-commands.ts +0 -206
  196. package/src/formats/xlsx/runtime/workbook-runtime.ts +0 -177
  197. package/src/formats/xlsx/runtime/workbook-transaction.ts +0 -822
  198. package/src/index.ts +0 -101
  199. package/src/io/README.md +0 -10
  200. package/src/io/docx-session.ts +0 -2882
  201. package/src/io/export/README.md +0 -3
  202. package/src/io/export/export-session.ts +0 -220
  203. package/src/io/export/minimal-docx.ts +0 -115
  204. package/src/io/export/reattach-preserved-parts.ts +0 -54
  205. package/src/io/export/serialize-comments.ts +0 -947
  206. package/src/io/export/serialize-footnotes.ts +0 -399
  207. package/src/io/export/serialize-headers-footers.ts +0 -372
  208. package/src/io/export/serialize-main-document.ts +0 -1376
  209. package/src/io/export/serialize-numbering.ts +0 -118
  210. package/src/io/export/serialize-revisions.ts +0 -389
  211. package/src/io/export/serialize-runtime-revisions.ts +0 -269
  212. package/src/io/export/serialize-tables.ts +0 -174
  213. package/src/io/export/split-review-boundaries.ts +0 -356
  214. package/src/io/normalize/README.md +0 -3
  215. package/src/io/normalize/normalize-text.ts +0 -639
  216. package/src/io/ooxml/README.md +0 -3
  217. package/src/io/ooxml/highlight-colors.ts +0 -39
  218. package/src/io/ooxml/numbering-sentinels.ts +0 -44
  219. package/src/io/ooxml/parse-comments.ts +0 -846
  220. package/src/io/ooxml/parse-complex-content.ts +0 -287
  221. package/src/io/ooxml/parse-fields.ts +0 -834
  222. package/src/io/ooxml/parse-footnotes.ts +0 -896
  223. package/src/io/ooxml/parse-headers-footers.ts +0 -1169
  224. package/src/io/ooxml/parse-inline-media.ts +0 -461
  225. package/src/io/ooxml/parse-main-document.ts +0 -2877
  226. package/src/io/ooxml/parse-numbering.ts +0 -432
  227. package/src/io/ooxml/parse-revisions.ts +0 -931
  228. package/src/io/ooxml/parse-settings.ts +0 -184
  229. package/src/io/ooxml/parse-shapes.ts +0 -296
  230. package/src/io/ooxml/parse-styles.ts +0 -463
  231. package/src/io/ooxml/parse-tables.ts +0 -618
  232. package/src/io/ooxml/parse-theme.ts +0 -346
  233. package/src/io/ooxml/part-manifest.ts +0 -136
  234. package/src/io/ooxml/revision-boundaries.ts +0 -351
  235. package/src/io/opc/README.md +0 -3
  236. package/src/io/opc/corrupt-package.ts +0 -166
  237. package/src/io/opc/docx-package.ts +0 -74
  238. package/src/io/opc/package-reader.ts +0 -325
  239. package/src/io/opc/package-writer.ts +0 -273
  240. package/src/io/source-package-provenance.ts +0 -241
  241. package/src/legal/cross-references.ts +0 -414
  242. package/src/legal/defined-terms.ts +0 -203
  243. package/src/legal/index.ts +0 -32
  244. package/src/legal/signature-blocks.ts +0 -259
  245. package/src/model/README.md +0 -3
  246. package/src/model/canonical-document.ts +0 -2632
  247. package/src/model/cds-1.0.0.ts +0 -212
  248. package/src/model/snapshot.ts +0 -649
  249. package/src/preservation/README.md +0 -3
  250. package/src/preservation/markup-compatibility.ts +0 -48
  251. package/src/preservation/opaque-fragment-store.ts +0 -89
  252. package/src/preservation/opaque-region.ts +0 -233
  253. package/src/preservation/package-preservation.ts +0 -113
  254. package/src/preservation/preserved-part-manifest.ts +0 -56
  255. package/src/preservation/relationship-retention.ts +0 -57
  256. package/src/preservation/store.ts +0 -185
  257. package/src/review/README.md +0 -16
  258. package/src/review/store/README.md +0 -3
  259. package/src/review/store/comment-anchors.ts +0 -70
  260. package/src/review/store/comment-remapping.ts +0 -154
  261. package/src/review/store/comment-store.ts +0 -331
  262. package/src/review/store/comment-thread.ts +0 -109
  263. package/src/review/store/revision-actions.ts +0 -394
  264. package/src/review/store/revision-store.ts +0 -312
  265. package/src/review/store/revision-types.ts +0 -171
  266. package/src/review/store/runtime-comment-store.ts +0 -43
  267. package/src/runtime/README.md +0 -3
  268. package/src/runtime/ai-action-policy.ts +0 -764
  269. package/src/runtime/collab-review-sync.ts +0 -254
  270. package/src/runtime/document-layout.ts +0 -332
  271. package/src/runtime/document-navigation.ts +0 -603
  272. package/src/runtime/document-runtime.ts +0 -3159
  273. package/src/runtime/document-search.ts +0 -145
  274. package/src/runtime/numbering-prefix.ts +0 -216
  275. package/src/runtime/page-layout-estimation.ts +0 -212
  276. package/src/runtime/read-only-diagnostics-runtime.ts +0 -241
  277. package/src/runtime/review-runtime.ts +0 -44
  278. package/src/runtime/revision-runtime.ts +0 -107
  279. package/src/runtime/session-capabilities.ts +0 -192
  280. package/src/runtime/story-context.ts +0 -164
  281. package/src/runtime/story-targeting.ts +0 -162
  282. package/src/runtime/surface-projection.ts +0 -1357
  283. package/src/runtime/table-commands.ts +0 -173
  284. package/src/runtime/table-schema.ts +0 -309
  285. package/src/runtime/view-state.ts +0 -477
  286. package/src/runtime/virtualized-rendering.ts +0 -258
  287. package/src/runtime/workflow-markup.ts +0 -353
  288. package/src/ui/README.md +0 -30
  289. package/src/ui/WordReviewEditor.tsx +0 -4097
  290. package/src/ui/browser-export.ts +0 -52
  291. package/src/ui/comments/README.md +0 -3
  292. package/src/ui/compatibility/README.md +0 -3
  293. package/src/ui/editor-command-bag.ts +0 -120
  294. package/src/ui/editor-runtime-boundary.ts +0 -1457
  295. package/src/ui/editor-shell-view.tsx +0 -142
  296. package/src/ui/editor-surface/README.md +0 -3
  297. package/src/ui/editor-surface-controller.tsx +0 -63
  298. package/src/ui/headless/comment-decoration-model.ts +0 -124
  299. package/src/ui/headless/preserve-editor-selection.ts +0 -5
  300. package/src/ui/headless/revision-decoration-model.ts +0 -128
  301. package/src/ui/headless/selection-helpers.ts +0 -54
  302. package/src/ui/headless/selection-toolbar-model.ts +0 -34
  303. package/src/ui/headless/use-editor-keyboard.ts +0 -103
  304. package/src/ui/review/README.md +0 -3
  305. package/src/ui/runtime-snapshot-selectors.ts +0 -197
  306. package/src/ui/shared/revision-filters.ts +0 -31
  307. package/src/ui/status/README.md +0 -3
  308. package/src/ui/theme/README.md +0 -3
  309. package/src/ui/toolbar/README.md +0 -3
  310. package/src/ui/workflow-surface-blocked-rails.ts +0 -94
  311. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +0 -64
  312. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +0 -129
  313. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +0 -114
  314. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +0 -34
  315. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +0 -386
  316. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +0 -186
  317. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +0 -139
  318. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +0 -128
  319. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +0 -58
  320. package/src/ui-tailwind/chrome/use-before-unload.ts +0 -20
  321. package/src/ui-tailwind/editor-surface/perf-probe.ts +0 -179
  322. package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
  323. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +0 -178
  324. package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +0 -31
  325. package/src/ui-tailwind/editor-surface/pm-decorations.ts +0 -427
  326. package/src/ui-tailwind/editor-surface/pm-position-map.ts +0 -123
  327. package/src/ui-tailwind/editor-surface/pm-schema.ts +0 -876
  328. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +0 -504
  329. package/src/ui-tailwind/editor-surface/search-plugin.ts +0 -168
  330. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +0 -61
  331. package/src/ui-tailwind/editor-surface/tw-caret.tsx +0 -12
  332. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +0 -150
  333. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +0 -129
  334. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +0 -58
  335. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +0 -151
  336. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +0 -973
  337. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +0 -111
  338. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -436
  339. package/src/ui-tailwind/index.ts +0 -62
  340. package/src/ui-tailwind/page-chrome-model.ts +0 -27
  341. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +0 -406
  342. package/src/ui-tailwind/review/tw-health-panel.tsx +0 -149
  343. package/src/ui-tailwind/review/tw-review-rail.tsx +0 -120
  344. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +0 -164
  345. package/src/ui-tailwind/status/tw-status-bar.tsx +0 -61
  346. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +0 -52
  347. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +0 -1064
  348. package/src/ui-tailwind/tw-review-workspace.tsx +0 -1417
  349. package/src/validation/README.md +0 -3
  350. package/src/validation/compatibility-engine.ts +0 -634
  351. package/src/validation/compatibility-report.ts +0 -161
  352. package/src/validation/diagnostics.ts +0 -204
  353. package/src/validation/docx-comment-proof.ts +0 -707
  354. package/src/validation/import-diagnostics.ts +0 -128
  355. package/src/validation/low-priority-word-surfaces.ts +0 -373
  356. /package/{src → dist}/ui-tailwind/theme/editor-theme.css +0 -0
@@ -0,0 +1,4342 @@
1
+ import {
2
+ deleteSelectionOrBackward,
3
+ deleteSelectionOrForward,
4
+ insertHardBreak,
5
+ insertTab,
6
+ insertText,
7
+ splitParagraph
8
+ } from "./chunk-UZXBISGO.js";
9
+ import {
10
+ applyTextTransaction
11
+ } from "./chunk-QDAQ4CJU.js";
12
+ import {
13
+ searchSurfaceBlocks
14
+ } from "./chunk-CN3XMECL.js";
15
+ import {
16
+ buildPageLayoutSnapshot,
17
+ buildResolvedSections,
18
+ canCreateDocxCommentAnchor,
19
+ createDocumentNavigationSnapshot,
20
+ createEditorViewStateSnapshot,
21
+ createViewState,
22
+ detachReviewAnchor,
23
+ findPageForOffset,
24
+ getAnchorRange,
25
+ incrementInvalidationCounter,
26
+ mapReviewAnchor,
27
+ mappingTouchesAnchorContent,
28
+ rangeStaysWithinSingleParagraph,
29
+ recordPerfSample,
30
+ resolveActiveSection,
31
+ setDocumentMode,
32
+ setFocused,
33
+ setViewMode,
34
+ setWorkspaceMode,
35
+ setZoomLevel
36
+ } from "./chunk-GBVOWFIK.js";
37
+ import {
38
+ parseTextStory
39
+ } from "./chunk-KLMXQVYK.js";
40
+ import {
41
+ createEditorSurfaceSnapshot,
42
+ getStoryBlocks,
43
+ normalizeHeaderFooterTarget,
44
+ replaceStoryBlocks,
45
+ storyTargetKey
46
+ } from "./chunk-EILUG3VB.js";
47
+ import {
48
+ buildCompatibilityReport,
49
+ createDiagnostics,
50
+ createRevisionSidebarProjection,
51
+ diagnosticsBlockExport,
52
+ editorSessionStateFromPersistedSnapshot,
53
+ getRevisionActionability,
54
+ persistedSnapshotFromEditorSessionState,
55
+ remapRevisionStore,
56
+ setRevisionStatus
57
+ } from "./chunk-2FJS5GZM.js";
58
+ import {
59
+ createCommentSidebarProjection,
60
+ createCommentStoreFromRuntimeComments
61
+ } from "./chunk-2S7W4KFO.js";
62
+ import {
63
+ describeOpaqueFragment,
64
+ findOpaqueFragmentsIntersectingRange
65
+ } from "./chunk-SWKWQZXM.js";
66
+ import {
67
+ createEditorState,
68
+ createPersistedEditorSnapshot,
69
+ createSelectionSnapshot,
70
+ deriveDocumentStats,
71
+ normalizeCommentThreadRecord
72
+ } from "./chunk-5FN54NDH.js";
73
+ import {
74
+ DEFAULT_BOUNDARY_ASSOC,
75
+ MAIN_STORY_TARGET,
76
+ areAnchorsEqual,
77
+ createDetachedAnchor,
78
+ createEmptyMapping,
79
+ createNodeAnchor,
80
+ createRangeAnchor,
81
+ mapAnchor,
82
+ mapRange,
83
+ storyTargetsEqual
84
+ } from "./chunk-EBI3BX6U.js";
85
+ import {
86
+ buildBookmarkNameMap
87
+ } from "./chunk-RMH72RZI.js";
88
+ import {
89
+ buildFieldRegistry,
90
+ isSupportedFieldFamily,
91
+ parseTocLevelRange,
92
+ resolveRefFieldText
93
+ } from "./chunk-TLCEAQDQ.js";
94
+
95
+ // src/core/commands/review-commands.ts
96
+ function isSingleRevisionReviewCommand(command) {
97
+ return "revisionId" in command;
98
+ }
99
+ function getReviewCommandIntent(command) {
100
+ switch (command.type) {
101
+ case "review.accept-revision":
102
+ case "review.accept-all-revisions":
103
+ return "accept";
104
+ case "review.reject-revision":
105
+ case "review.reject-all-revisions":
106
+ return "reject";
107
+ }
108
+ }
109
+ function createAcceptRevisionCommand(revisionId, origin) {
110
+ return {
111
+ type: "review.accept-revision",
112
+ revisionId,
113
+ ...origin ? { origin } : {}
114
+ };
115
+ }
116
+ function createRejectRevisionCommand(revisionId, origin) {
117
+ return {
118
+ type: "review.reject-revision",
119
+ revisionId,
120
+ ...origin ? { origin } : {}
121
+ };
122
+ }
123
+
124
+ // src/review/store/comment-remapping.ts
125
+ function remapCommentThreads(options) {
126
+ const comments = Object.fromEntries(
127
+ Object.entries(options.comments).map(([commentId, comment]) => [
128
+ commentId,
129
+ remapCommentThread(comment, options.mapping, options.nextContent)
130
+ ])
131
+ );
132
+ const detachedCommentIds = Object.values(comments).filter((comment) => comment.anchor.kind === "detached").map((comment) => comment.commentId);
133
+ return {
134
+ comments,
135
+ warnings: mergeDetachedAnchorWarnings(comments, options.existingWarnings ?? []),
136
+ detachedCommentIds
137
+ };
138
+ }
139
+ function remapCommentThread(comment, mapping, nextContent) {
140
+ if (comment.anchor.kind === "detached") {
141
+ return comment;
142
+ }
143
+ const mappedAnchor = mapReviewAnchor(comment.anchor, mapping);
144
+ const anchor = normalizeCommentAnchor(comment.anchor, mappedAnchor, mapping, nextContent);
145
+ return {
146
+ ...comment,
147
+ anchor
148
+ };
149
+ }
150
+ function normalizeCommentAnchor(previousAnchor, mappedAnchor, mapping, nextContent) {
151
+ if (mappedAnchor.kind === "detached") {
152
+ return mappedAnchor;
153
+ }
154
+ const previousRange = getAnchorRange(previousAnchor);
155
+ const mappedRange = getAnchorRange(mappedAnchor);
156
+ if (previousAnchor.kind === "range" && previousRange.from < previousRange.to && mappedAnchor.kind === "range" && mappedRange.from === mappedRange.to && mappingTouchesAnchorContent(previousAnchor, mapping)) {
157
+ return detachReviewAnchor(previousRange, detachReason(mapping));
158
+ }
159
+ if (mappedAnchor.kind === "range" && !rangeStaysWithinSingleParagraph(nextContent, mappedAnchor.range)) {
160
+ return detachReviewAnchor(previousRange, "invalidatedByStructureChange");
161
+ }
162
+ return mappedAnchor;
163
+ }
164
+ function detachReason(mapping) {
165
+ return mapping.metadata?.invalidatesStructures ? "invalidatedByStructureChange" : "deleted";
166
+ }
167
+ function mergeDetachedAnchorWarnings(comments, existingWarnings) {
168
+ const retainedWarnings = existingWarnings.filter(
169
+ (warning) => warning.code !== "comment_anchor_detached" || !warning.details || typeof warning.details.commentId !== "string" || comments[warning.details.commentId]?.anchor.kind === "detached"
170
+ );
171
+ const knownDetachedIds = new Set(
172
+ retainedWarnings.filter((warning) => warning.code === "comment_anchor_detached").map(
173
+ (warning) => typeof warning.details?.commentId === "string" ? warning.details.commentId : void 0
174
+ ).filter((value) => Boolean(value))
175
+ );
176
+ const detachedWarnings = Object.values(comments).filter(
177
+ (comment) => comment.anchor.kind === "detached" && !knownDetachedIds.has(comment.commentId)
178
+ ).map((comment) => createDetachedCommentWarning(comment));
179
+ return [...retainedWarnings, ...detachedWarnings];
180
+ }
181
+ function createDetachedCommentWarning(comment) {
182
+ const anchor = comment.anchor.kind === "detached" ? comment.anchor : void 0;
183
+ return {
184
+ warningId: `warning:comment-anchor-detached:${comment.commentId}`,
185
+ code: "comment_anchor_detached",
186
+ severity: "warning",
187
+ message: `Comment ${comment.commentId} detached after edit remapping.`,
188
+ source: "review",
189
+ affectedAnchor: anchor,
190
+ details: {
191
+ commentId: comment.commentId,
192
+ reason: anchor?.reason
193
+ }
194
+ };
195
+ }
196
+
197
+ // src/review/store/revision-actions.ts
198
+ function applyRevisionAction(options) {
199
+ const revision = options.store.revisions[options.revisionId];
200
+ if (!revision) {
201
+ return skippedResult(options, "missing", "Revision record was not found.");
202
+ }
203
+ if (revision.status !== "active") {
204
+ return skippedResult(
205
+ options,
206
+ "already-resolved",
207
+ `Revision is already ${revision.status}.`
208
+ );
209
+ }
210
+ if (revision.anchor.kind === "detached") {
211
+ return skippedResult(
212
+ options,
213
+ "detached-anchor",
214
+ "Detached revisions remain visible but cannot be accepted or rejected."
215
+ );
216
+ }
217
+ if (getRevisionActionability(revision) !== "actionable") {
218
+ return skippedResult(
219
+ options,
220
+ "preserve-only",
221
+ revision.metadata.preserveOnlyReason ?? "This revision kind remains preserve-only in the current runtime."
222
+ );
223
+ }
224
+ if (revision.anchor.kind !== "range") {
225
+ return skippedResult(
226
+ options,
227
+ "structural-range",
228
+ "Non-range revisions remain preserve-only in the current runtime."
229
+ );
230
+ }
231
+ const story = parseTextStory(options.document.content);
232
+ const range = normalizeRange(
233
+ revision.anchor.range.from,
234
+ revision.anchor.range.to
235
+ );
236
+ if (range.to > story.size) {
237
+ return skippedResult(
238
+ options,
239
+ "invalid-range",
240
+ `Revision range ${range.from}-${range.to} exceeds story size ${story.size}.`
241
+ );
242
+ }
243
+ const paragraphMarkRange = resolveParagraphMarkDeletionRange(story, revision, options.intent);
244
+ if (paragraphMarkRange) {
245
+ const resultingStatus2 = toResultingStatus(options.intent);
246
+ const textResult2 = applyTextTransaction(
247
+ options.document,
248
+ createSelectionSnapshot(paragraphMarkRange.from, paragraphMarkRange.to),
249
+ {
250
+ type: "replace",
251
+ range: paragraphMarkRange,
252
+ insertion: []
253
+ },
254
+ {
255
+ timestamp: options.timestamp
256
+ }
257
+ );
258
+ const nextStore2 = remapRevisionStore(
259
+ setRevisionStatus(options.store, revision.revisionId, resultingStatus2),
260
+ textResult2.mapping
261
+ );
262
+ return {
263
+ document: textResult2.document,
264
+ store: nextStore2,
265
+ mapping: textResult2.mapping,
266
+ outcome: {
267
+ kind: "applied",
268
+ revisionId: revision.revisionId,
269
+ intent: options.intent,
270
+ resultingStatus: resultingStatus2,
271
+ contentChanged: true
272
+ },
273
+ detachedRevisionIds: findNewDetachedRevisionIds(options.store, nextStore2)
274
+ };
275
+ }
276
+ if (requiresParagraphBoundaryDeletion(revision, options.intent)) {
277
+ return skippedResult(
278
+ options,
279
+ "structural-range",
280
+ "Paragraph-boundary revisions need a stable paragraph break and remain blocked for this shape."
281
+ );
282
+ }
283
+ const slice = story.units.slice(range.from, range.to);
284
+ if (slice.some(
285
+ (unit) => unit.kind === "paragraph_break" || unit.kind === "opaque_block"
286
+ )) {
287
+ return skippedResult(
288
+ options,
289
+ "structural-range",
290
+ "Paragraph-boundary and structural revisions remain preserve-only."
291
+ );
292
+ }
293
+ if (slice.some(
294
+ (unit) => unit.kind === "image" || unit.kind === "opaque_inline"
295
+ )) {
296
+ return skippedResult(
297
+ options,
298
+ "protected-range",
299
+ "Revisions touching protected inline content remain preserve-only."
300
+ );
301
+ }
302
+ const resultingStatus = toResultingStatus(options.intent);
303
+ const contentChanged = requiresContentDeletion(revision, options.intent);
304
+ if (!contentChanged) {
305
+ return {
306
+ document: options.document,
307
+ store: setRevisionStatus(options.store, revision.revisionId, resultingStatus),
308
+ mapping: createEmptyMapping(),
309
+ outcome: {
310
+ kind: "applied",
311
+ revisionId: revision.revisionId,
312
+ intent: options.intent,
313
+ resultingStatus,
314
+ contentChanged: false
315
+ },
316
+ detachedRevisionIds: []
317
+ };
318
+ }
319
+ const textResult = applyTextTransaction(
320
+ options.document,
321
+ createSelectionSnapshot(range.from, range.to),
322
+ {
323
+ type: "replace",
324
+ range,
325
+ insertion: []
326
+ },
327
+ {
328
+ timestamp: options.timestamp
329
+ }
330
+ );
331
+ const nextStore = remapRevisionStore(
332
+ setRevisionStatus(options.store, revision.revisionId, resultingStatus),
333
+ textResult.mapping
334
+ );
335
+ return {
336
+ document: textResult.document,
337
+ store: nextStore,
338
+ mapping: textResult.mapping,
339
+ outcome: {
340
+ kind: "applied",
341
+ revisionId: revision.revisionId,
342
+ intent: options.intent,
343
+ resultingStatus,
344
+ contentChanged: true
345
+ },
346
+ detachedRevisionIds: findNewDetachedRevisionIds(options.store, nextStore)
347
+ };
348
+ }
349
+ function skippedResult(options, reason, detail) {
350
+ return {
351
+ document: options.document,
352
+ store: options.store,
353
+ mapping: createEmptyMapping(),
354
+ outcome: {
355
+ kind: "skipped",
356
+ revisionId: options.revisionId,
357
+ intent: options.intent,
358
+ reason,
359
+ detail
360
+ },
361
+ detachedRevisionIds: []
362
+ };
363
+ }
364
+ function toResultingStatus(intent) {
365
+ return intent === "accept" ? "accepted" : "rejected";
366
+ }
367
+ function requiresContentDeletion(revision, intent) {
368
+ return revision.kind === "insertion" && intent === "reject" || revision.kind === "deletion" && intent === "accept";
369
+ }
370
+ function requiresParagraphBoundaryDeletion(revision, intent) {
371
+ return revision.metadata.originalRevisionType === "paragraph-del" && intent === "accept" || revision.metadata.originalRevisionType === "paragraph-ins" && intent === "reject";
372
+ }
373
+ function resolveParagraphMarkDeletionRange(story, revision, intent) {
374
+ const originalRevisionType = revision.metadata.originalRevisionType;
375
+ if (revision.anchor.kind !== "range" || originalRevisionType !== "paragraph-del" && originalRevisionType !== "paragraph-ins") {
376
+ return void 0;
377
+ }
378
+ const shouldDeleteBoundary = originalRevisionType === "paragraph-del" && intent === "accept" || originalRevisionType === "paragraph-ins" && intent === "reject";
379
+ if (!shouldDeleteBoundary) {
380
+ return void 0;
381
+ }
382
+ const paragraphs = mapParagraphRanges(story);
383
+ const anchorPosition = normalizeRange(
384
+ revision.anchor.range.from,
385
+ revision.anchor.range.to
386
+ ).from;
387
+ const paragraph = paragraphs.find(
388
+ (candidate) => candidate.end === anchorPosition || anchorPosition >= candidate.start && anchorPosition <= candidate.end
389
+ );
390
+ if (!paragraph) {
391
+ return void 0;
392
+ }
393
+ if (originalRevisionType === "paragraph-del") {
394
+ const boundaryIndex2 = paragraph.end;
395
+ if (story.units[boundaryIndex2]?.kind !== "paragraph_break") {
396
+ return void 0;
397
+ }
398
+ return {
399
+ from: boundaryIndex2,
400
+ to: boundaryIndex2 + 1
401
+ };
402
+ }
403
+ const boundaryIndex = paragraph.start - 1;
404
+ if (boundaryIndex < 0 || story.units[boundaryIndex]?.kind !== "paragraph_break") {
405
+ return void 0;
406
+ }
407
+ return {
408
+ from: boundaryIndex,
409
+ to: boundaryIndex + 1
410
+ };
411
+ }
412
+ function mapParagraphRanges(story) {
413
+ const paragraphs = [];
414
+ let start = 0;
415
+ let hasPendingParagraph = true;
416
+ for (let index = 0; index < story.units.length; index += 1) {
417
+ const unit = story.units[index];
418
+ if (unit.kind === "paragraph_break") {
419
+ paragraphs.push({ start, end: index });
420
+ start = index + 1;
421
+ hasPendingParagraph = true;
422
+ continue;
423
+ }
424
+ if (unit.kind === "opaque_block") {
425
+ if (hasPendingParagraph) {
426
+ paragraphs.push({ start, end: index });
427
+ }
428
+ start = index + 1;
429
+ hasPendingParagraph = Boolean(unit.nextParagraph);
430
+ }
431
+ }
432
+ if (hasPendingParagraph) {
433
+ paragraphs.push({ start, end: story.units.length });
434
+ }
435
+ return paragraphs;
436
+ }
437
+ function normalizeRange(from, to) {
438
+ return {
439
+ from: Math.min(from, to),
440
+ to: Math.max(from, to)
441
+ };
442
+ }
443
+ function findNewDetachedRevisionIds(previousStore, nextStore) {
444
+ const detachedRevisionIds = [];
445
+ for (const [revisionId, revision] of Object.entries(nextStore.revisions)) {
446
+ if (revision.status === "detached" && previousStore.revisions[revisionId]?.status !== "detached") {
447
+ detachedRevisionIds.push(revisionId);
448
+ }
449
+ }
450
+ return detachedRevisionIds;
451
+ }
452
+
453
+ // src/runtime/revision-runtime.ts
454
+ function applyRevisionRuntimeCommand(options) {
455
+ const revisionIds = isSingleRevisionReviewCommand(options.command) ? [options.command.revisionId] : listBatchRevisionIds(options.state.store);
456
+ const outcomes = [];
457
+ const mappings = [];
458
+ const appliedRevisionIds = [];
459
+ const detachedRevisionIds = /* @__PURE__ */ new Set();
460
+ let state = options.state;
461
+ for (const revisionId of revisionIds) {
462
+ const result = applyRevisionAction({
463
+ document: state.document,
464
+ store: state.store,
465
+ revisionId,
466
+ intent: getReviewCommandIntent(options.command),
467
+ timestamp: options.timestamp
468
+ });
469
+ state = {
470
+ document: result.document,
471
+ store: result.store
472
+ };
473
+ outcomes.push(result.outcome);
474
+ mappings.push({
475
+ revisionId,
476
+ mapping: result.mapping,
477
+ steps: result.mapping.steps.length
478
+ });
479
+ if (result.outcome.kind === "applied") {
480
+ appliedRevisionIds.push(revisionId);
481
+ }
482
+ for (const detachedRevisionId of result.detachedRevisionIds) {
483
+ detachedRevisionIds.add(detachedRevisionId);
484
+ }
485
+ }
486
+ return {
487
+ ...state,
488
+ outcomes,
489
+ mappings,
490
+ effects: {
491
+ appliedRevisionIds,
492
+ skippedRevisions: outcomes.filter(
493
+ (outcome) => outcome.kind === "skipped"
494
+ ),
495
+ detachedRevisionIds: [...detachedRevisionIds]
496
+ }
497
+ };
498
+ }
499
+ function listBatchRevisionIds(store) {
500
+ return Object.values(store.revisions).filter((revision) => revision.status === "active").sort((left, right) => {
501
+ const createdAtDelta = left.createdAt.localeCompare(right.createdAt);
502
+ if (createdAtDelta !== 0) {
503
+ return createdAtDelta;
504
+ }
505
+ return left.revisionId.localeCompare(right.revisionId);
506
+ }).map((revision) => revision.revisionId);
507
+ }
508
+
509
+ // src/core/commands/index.ts
510
+ function executeEditorCommand(state, command, context) {
511
+ switch (command.type) {
512
+ case "selection.set":
513
+ return createTransaction(
514
+ {
515
+ ...state,
516
+ selection: normalizeSelection(command.selection)
517
+ },
518
+ {
519
+ historyBoundary: "skip",
520
+ markDirty: false
521
+ }
522
+ );
523
+ case "document.replace": {
524
+ const mapping = command.mapping ?? createEmptyMapping();
525
+ const selection = command.selection ?? remapSelection(state.selection, mapping);
526
+ const reviewState = remapReviewStateAfterContentChange(
527
+ state,
528
+ command.document,
529
+ mapping
530
+ );
531
+ return createTransaction(
532
+ {
533
+ ...state,
534
+ document: reviewState.document,
535
+ selection,
536
+ warnings: reviewState.warnings,
537
+ runtime: {
538
+ ...state.runtime,
539
+ activeCommentId: reviewState.activeCommentId
540
+ },
541
+ compatibility: {
542
+ ...state.compatibility,
543
+ generatedAt: context.timestamp,
544
+ warnings: reviewState.warnings,
545
+ featureEntries: state.compatibility.featureEntries.map(
546
+ (entry) => entry.affectedAnchor ? {
547
+ ...entry,
548
+ affectedAnchor: mapAnchor(entry.affectedAnchor, mapping)
549
+ } : entry
550
+ )
551
+ }
552
+ },
553
+ {
554
+ historyBoundary: "push",
555
+ markDirty: true,
556
+ mapping,
557
+ effects: reviewState.effects
558
+ }
559
+ );
560
+ }
561
+ case "text.insert": {
562
+ const suggestingResult = context.documentMode === "suggesting" ? applySuggestingInsert(state, command.text, context) : void 0;
563
+ if (suggestingResult) return suggestingResult;
564
+ return applyTextCommand(
565
+ state,
566
+ context.timestamp,
567
+ (document, selection) => insertText(document, selection, command.text, context)
568
+ );
569
+ }
570
+ case "text.delete-backward": {
571
+ const suggestingResult = context.documentMode === "suggesting" ? applySuggestingDelete(state, "backward", context) : void 0;
572
+ if (suggestingResult) return suggestingResult;
573
+ return applyTextCommand(
574
+ state,
575
+ context.timestamp,
576
+ (document, selection) => deleteSelectionOrBackward(document, selection, context)
577
+ );
578
+ }
579
+ case "text.delete-forward": {
580
+ const suggestingResult = context.documentMode === "suggesting" ? applySuggestingDelete(state, "forward", context) : void 0;
581
+ if (suggestingResult) return suggestingResult;
582
+ return applyTextCommand(
583
+ state,
584
+ context.timestamp,
585
+ (document, selection) => deleteSelectionOrForward(document, selection, context)
586
+ );
587
+ }
588
+ case "text.insert-tab": {
589
+ const suggestingResult = context.documentMode === "suggesting" ? applySuggestingInsertUnit(state, "tab", context) : void 0;
590
+ if (suggestingResult) return suggestingResult;
591
+ return applyTextCommand(
592
+ state,
593
+ context.timestamp,
594
+ (document, selection) => insertTab(document, selection, context)
595
+ );
596
+ }
597
+ case "text.insert-hard-break": {
598
+ const suggestingResult = context.documentMode === "suggesting" ? applySuggestingInsertUnit(state, "hard_break", context) : void 0;
599
+ if (suggestingResult) return suggestingResult;
600
+ return applyTextCommand(
601
+ state,
602
+ context.timestamp,
603
+ (document, selection) => insertHardBreak(document, selection, context)
604
+ );
605
+ }
606
+ case "paragraph.split":
607
+ if (context.documentMode === "suggesting") {
608
+ return createTransaction(state, {
609
+ historyBoundary: "skip",
610
+ markDirty: false,
611
+ effects: {
612
+ commandBlocked: {
613
+ code: "suggesting_unsupported",
614
+ message: "Paragraph splits are not supported in suggesting mode."
615
+ }
616
+ }
617
+ });
618
+ }
619
+ return applyTextCommand(
620
+ state,
621
+ context.timestamp,
622
+ (document, selection) => splitParagraph(document, selection, context)
623
+ );
624
+ case "runtime.set-read-only":
625
+ return createTransaction(
626
+ {
627
+ ...state,
628
+ readOnly: command.readOnly
629
+ },
630
+ {
631
+ historyBoundary: "skip",
632
+ markDirty: false
633
+ }
634
+ );
635
+ case "runtime.focus":
636
+ return createTransaction(
637
+ {
638
+ ...state,
639
+ runtime: {
640
+ ...state.runtime,
641
+ hasFocus: command.focused
642
+ }
643
+ },
644
+ {
645
+ historyBoundary: "skip",
646
+ markDirty: false
647
+ }
648
+ );
649
+ case "warning.add": {
650
+ if (state.warnings.some((warning) => warning.warningId === command.warning.warningId)) {
651
+ return createTransaction(state, {
652
+ historyBoundary: "skip",
653
+ markDirty: false
654
+ });
655
+ }
656
+ const warnings = [...state.warnings, command.warning];
657
+ return createTransaction(
658
+ {
659
+ ...state,
660
+ warnings,
661
+ compatibility: {
662
+ ...state.compatibility,
663
+ generatedAt: context.timestamp,
664
+ warnings
665
+ }
666
+ },
667
+ {
668
+ historyBoundary: "skip",
669
+ markDirty: false,
670
+ effects: {
671
+ warningsAdded: [command.warning]
672
+ }
673
+ }
674
+ );
675
+ }
676
+ case "warning.clear": {
677
+ const existing = state.warnings.find((warning) => warning.warningId === command.warningId);
678
+ if (!existing) {
679
+ return createTransaction(state, {
680
+ historyBoundary: "skip",
681
+ markDirty: false
682
+ });
683
+ }
684
+ const warnings = state.warnings.filter((warning) => warning.warningId !== command.warningId);
685
+ return createTransaction(
686
+ {
687
+ ...state,
688
+ warnings,
689
+ compatibility: {
690
+ ...state.compatibility,
691
+ generatedAt: context.timestamp,
692
+ warnings
693
+ }
694
+ },
695
+ {
696
+ historyBoundary: "skip",
697
+ markDirty: false,
698
+ effects: {
699
+ warningsCleared: [
700
+ {
701
+ warningId: existing.warningId,
702
+ code: existing.code
703
+ }
704
+ ]
705
+ }
706
+ }
707
+ );
708
+ }
709
+ case "comment.add": {
710
+ const comments = {
711
+ ...state.document.review.comments,
712
+ [command.comment.commentId]: normalizeCommentThreadRecord(command.comment)
713
+ };
714
+ return createTransaction(
715
+ {
716
+ ...state,
717
+ document: {
718
+ ...state.document,
719
+ updatedAt: context.timestamp,
720
+ review: {
721
+ ...state.document.review,
722
+ comments
723
+ }
724
+ },
725
+ runtime: {
726
+ ...state.runtime,
727
+ activeCommentId: command.comment.commentId
728
+ }
729
+ },
730
+ {
731
+ historyBoundary: "push",
732
+ markDirty: true,
733
+ effects: {
734
+ commentAdded: {
735
+ commentId: command.comment.commentId,
736
+ anchor: command.comment.anchor
737
+ }
738
+ }
739
+ }
740
+ );
741
+ }
742
+ case "comment.open":
743
+ return createTransaction(
744
+ {
745
+ ...state,
746
+ runtime: {
747
+ ...state.runtime,
748
+ activeCommentId: command.commentId
749
+ }
750
+ },
751
+ {
752
+ historyBoundary: "skip",
753
+ markDirty: false
754
+ }
755
+ );
756
+ case "comment.resolve": {
757
+ const existing = state.document.review.comments[command.commentId];
758
+ if (!existing) {
759
+ return createTransaction(state, {
760
+ historyBoundary: "skip",
761
+ markDirty: false
762
+ });
763
+ }
764
+ const comments = {
765
+ ...state.document.review.comments,
766
+ [command.commentId]: {
767
+ ...existing,
768
+ status: existing.anchor.kind === "detached" ? "detached" : "resolved",
769
+ resolution: {
770
+ resolvedAt: context.timestamp,
771
+ resolvedBy: command.resolvedBy ?? existing.createdBy ?? existing.authorId ?? existing.entries?.[0]?.authorId ?? "unknown"
772
+ },
773
+ isResolved: true,
774
+ resolvedAt: context.timestamp
775
+ }
776
+ };
777
+ return createTransaction(
778
+ {
779
+ ...state,
780
+ document: {
781
+ ...state.document,
782
+ updatedAt: context.timestamp,
783
+ review: {
784
+ ...state.document.review,
785
+ comments
786
+ }
787
+ }
788
+ },
789
+ {
790
+ historyBoundary: "push",
791
+ markDirty: true,
792
+ effects: {
793
+ commentResolved: {
794
+ commentId: command.commentId
795
+ }
796
+ }
797
+ }
798
+ );
799
+ }
800
+ case "comment.reopen": {
801
+ const existingThread = state.document.review.comments[command.commentId];
802
+ if (!existingThread || existingThread.status === "detached") {
803
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
804
+ }
805
+ const reopenedComments = {
806
+ ...state.document.review.comments,
807
+ [command.commentId]: {
808
+ ...existingThread,
809
+ status: "open",
810
+ isResolved: false,
811
+ resolvedAt: void 0,
812
+ resolution: void 0
813
+ }
814
+ };
815
+ return createTransaction(
816
+ {
817
+ ...state,
818
+ document: {
819
+ ...state.document,
820
+ updatedAt: context.timestamp,
821
+ review: { ...state.document.review, comments: reopenedComments }
822
+ },
823
+ runtime: { ...state.runtime, activeCommentId: command.commentId }
824
+ },
825
+ {
826
+ historyBoundary: "push",
827
+ markDirty: true,
828
+ effects: { commentReopened: { commentId: command.commentId } }
829
+ }
830
+ );
831
+ }
832
+ case "comment.add-reply": {
833
+ const threadForReply = state.document.review.comments[command.commentId];
834
+ if (!threadForReply) {
835
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
836
+ }
837
+ if (threadForReply.status === "resolved" || threadForReply.status === "detached") {
838
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
839
+ }
840
+ const entryId = `${command.commentId}-entry-${(threadForReply.entries?.length ?? 0) + 1}`;
841
+ const newEntry = {
842
+ entryId,
843
+ authorId: command.authorId ?? threadForReply.createdBy ?? "unknown",
844
+ body: command.body,
845
+ createdAt: context.timestamp
846
+ };
847
+ const updatedThread = {
848
+ ...threadForReply,
849
+ entries: [...threadForReply.entries ?? [], newEntry]
850
+ };
851
+ const replyComments = {
852
+ ...state.document.review.comments,
853
+ [command.commentId]: updatedThread
854
+ };
855
+ return createTransaction(
856
+ {
857
+ ...state,
858
+ document: {
859
+ ...state.document,
860
+ updatedAt: context.timestamp,
861
+ review: { ...state.document.review, comments: replyComments }
862
+ }
863
+ },
864
+ {
865
+ historyBoundary: "push",
866
+ markDirty: true,
867
+ effects: { commentReplyAdded: { commentId: command.commentId } }
868
+ }
869
+ );
870
+ }
871
+ case "comment.edit-body": {
872
+ const threadToEdit = state.document.review.comments[command.commentId];
873
+ if (!threadToEdit || !threadToEdit.entries?.length) {
874
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
875
+ }
876
+ const editedEntries = [...threadToEdit.entries];
877
+ editedEntries[0] = { ...editedEntries[0], body: command.body };
878
+ const editedThread = {
879
+ ...threadToEdit,
880
+ body: command.body,
881
+ entries: editedEntries
882
+ };
883
+ const editedComments = {
884
+ ...state.document.review.comments,
885
+ [command.commentId]: editedThread
886
+ };
887
+ return createTransaction(
888
+ {
889
+ ...state,
890
+ document: {
891
+ ...state.document,
892
+ updatedAt: context.timestamp,
893
+ review: { ...state.document.review, comments: editedComments }
894
+ }
895
+ },
896
+ {
897
+ historyBoundary: "push",
898
+ markDirty: true,
899
+ effects: { commentBodyEdited: { commentId: command.commentId } }
900
+ }
901
+ );
902
+ }
903
+ case "change.accept":
904
+ return applyReviewCommand(
905
+ state,
906
+ createAcceptRevisionCommand(command.changeId, command.origin),
907
+ context.timestamp
908
+ );
909
+ case "change.reject":
910
+ return applyReviewCommand(
911
+ state,
912
+ createRejectRevisionCommand(command.changeId, command.origin),
913
+ context.timestamp
914
+ );
915
+ case "change.accept-all":
916
+ return applyReviewCommand(
917
+ state,
918
+ {
919
+ type: "review.accept-all-revisions",
920
+ ...command.origin ? { origin: command.origin } : {}
921
+ },
922
+ context.timestamp
923
+ );
924
+ case "change.reject-all":
925
+ return applyReviewCommand(
926
+ state,
927
+ {
928
+ type: "review.reject-all-revisions",
929
+ ...command.origin ? { origin: command.origin } : {}
930
+ },
931
+ context.timestamp
932
+ );
933
+ }
934
+ }
935
+ function remapSelection(selection, mapping) {
936
+ const activeRange = mapAnchor(selection.activeRange, mapping);
937
+ if (activeRange.kind === "range") {
938
+ return {
939
+ anchor: activeRange.range.from,
940
+ head: activeRange.range.to,
941
+ isCollapsed: activeRange.range.from === activeRange.range.to,
942
+ activeRange
943
+ };
944
+ }
945
+ if (activeRange.kind === "node") {
946
+ return {
947
+ anchor: activeRange.at,
948
+ head: activeRange.at,
949
+ isCollapsed: true,
950
+ activeRange
951
+ };
952
+ }
953
+ return {
954
+ anchor: selection.anchor,
955
+ head: selection.head,
956
+ isCollapsed: selection.anchor === selection.head,
957
+ activeRange
958
+ };
959
+ }
960
+ function selectionChanged(left, right) {
961
+ return left.anchor !== right.anchor || left.head !== right.head || left.isCollapsed !== right.isCollapsed || !areAnchorsEqual(left.activeRange, right.activeRange);
962
+ }
963
+ function createTransaction(nextState, options) {
964
+ return {
965
+ nextState,
966
+ mapping: options.mapping ?? createEmptyMapping(),
967
+ historyBoundary: options.historyBoundary,
968
+ markDirty: options.markDirty,
969
+ effects: {
970
+ warningsAdded: options.effects?.warningsAdded ?? [],
971
+ warningsCleared: options.effects?.warningsCleared ?? [],
972
+ commentAdded: options.effects?.commentAdded,
973
+ commentResolved: options.effects?.commentResolved,
974
+ changeAccepted: options.effects?.changeAccepted,
975
+ changeRejected: options.effects?.changeRejected,
976
+ revisionAuthored: options.effects?.revisionAuthored,
977
+ commandBlocked: options.effects?.commandBlocked
978
+ }
979
+ };
980
+ }
981
+ function normalizeSelection(selection) {
982
+ const activeRange = normalizeSelectionAnchor(
983
+ selection.activeRange,
984
+ selection.anchor,
985
+ selection.head
986
+ );
987
+ if (activeRange.kind === "range") {
988
+ return {
989
+ anchor: activeRange.range.from,
990
+ head: activeRange.range.to,
991
+ isCollapsed: activeRange.range.from === activeRange.range.to,
992
+ activeRange
993
+ };
994
+ }
995
+ if (activeRange.kind === "node") {
996
+ return {
997
+ anchor: activeRange.at,
998
+ head: activeRange.at,
999
+ isCollapsed: true,
1000
+ activeRange
1001
+ };
1002
+ }
1003
+ return {
1004
+ anchor: activeRange.lastKnownRange.from,
1005
+ head: activeRange.lastKnownRange.to,
1006
+ isCollapsed: activeRange.lastKnownRange.from === activeRange.lastKnownRange.to,
1007
+ activeRange
1008
+ };
1009
+ }
1010
+ function normalizeSelectionAnchor(value, anchor, head) {
1011
+ if (!value || typeof value !== "object") {
1012
+ return createRangeAnchor(anchor, head, DEFAULT_BOUNDARY_ASSOC);
1013
+ }
1014
+ const record = value;
1015
+ switch (record.kind) {
1016
+ case "range": {
1017
+ const assoc = normalizeBoundaryAssoc(record.assoc);
1018
+ const internalRange = record.range;
1019
+ if (internalRange && typeof internalRange === "object" && typeof internalRange.from === "number" && typeof internalRange.to === "number") {
1020
+ return createRangeAnchor(
1021
+ internalRange.from,
1022
+ internalRange.to,
1023
+ assoc
1024
+ );
1025
+ }
1026
+ if (typeof record.from === "number" && typeof record.to === "number") {
1027
+ return createRangeAnchor(record.from, record.to, assoc);
1028
+ }
1029
+ return createRangeAnchor(anchor, head, assoc);
1030
+ }
1031
+ case "node":
1032
+ return createNodeAnchor(
1033
+ typeof record.at === "number" ? record.at : anchor,
1034
+ normalizeAssoc(record.assoc)
1035
+ );
1036
+ case "detached": {
1037
+ const lastKnownRange = record.lastKnownRange;
1038
+ if (lastKnownRange && typeof lastKnownRange === "object" && typeof lastKnownRange.from === "number" && typeof lastKnownRange.to === "number") {
1039
+ return createDetachedAnchor(
1040
+ {
1041
+ from: lastKnownRange.from,
1042
+ to: lastKnownRange.to
1043
+ },
1044
+ normalizeDetachedReason(record.reason)
1045
+ );
1046
+ }
1047
+ return createDetachedAnchor({ from: anchor, to: head }, normalizeDetachedReason(record.reason));
1048
+ }
1049
+ default:
1050
+ return createRangeAnchor(anchor, head, DEFAULT_BOUNDARY_ASSOC);
1051
+ }
1052
+ }
1053
+ function normalizeBoundaryAssoc(value) {
1054
+ if (value && typeof value === "object") {
1055
+ const record = value;
1056
+ return {
1057
+ start: normalizeAssoc(record.start),
1058
+ end: normalizeAssoc(record.end)
1059
+ };
1060
+ }
1061
+ return DEFAULT_BOUNDARY_ASSOC;
1062
+ }
1063
+ function normalizeAssoc(value) {
1064
+ return value === -1 ? -1 : 1;
1065
+ }
1066
+ function normalizeDetachedReason(value) {
1067
+ if (value === "deleted" || value === "invalidatedByStructureChange" || value === "importAmbiguity") {
1068
+ return value;
1069
+ }
1070
+ return "importAmbiguity";
1071
+ }
1072
+ function applyTextCommand(state, timestamp, apply) {
1073
+ if (state.readOnly) {
1074
+ return createTransaction(state, {
1075
+ historyBoundary: "skip",
1076
+ markDirty: false
1077
+ });
1078
+ }
1079
+ const result = apply(state.document, state.selection);
1080
+ const reviewState = remapReviewStateAfterContentChange(
1081
+ state,
1082
+ result.document,
1083
+ result.mapping
1084
+ );
1085
+ return createTransaction(
1086
+ {
1087
+ ...state,
1088
+ document: reviewState.document,
1089
+ selection: result.selection,
1090
+ warnings: reviewState.warnings,
1091
+ runtime: {
1092
+ ...state.runtime,
1093
+ activeCommentId: reviewState.activeCommentId
1094
+ }
1095
+ },
1096
+ {
1097
+ historyBoundary: "push",
1098
+ markDirty: true,
1099
+ mapping: result.mapping,
1100
+ effects: reviewState.effects
1101
+ }
1102
+ );
1103
+ }
1104
+ function applyReviewCommand(state, command, timestamp) {
1105
+ if (state.readOnly) {
1106
+ return createTransaction(state, {
1107
+ historyBoundary: "skip",
1108
+ markDirty: false
1109
+ });
1110
+ }
1111
+ const result = applyRevisionRuntimeCommand({
1112
+ state: {
1113
+ document: state.document,
1114
+ store: createRevisionStoreFromState(state)
1115
+ },
1116
+ command,
1117
+ timestamp
1118
+ });
1119
+ const hasAppliedOutcome = result.outcomes.some((outcome) => outcome.kind === "applied");
1120
+ if (!hasAppliedOutcome) {
1121
+ return createTransaction(state, {
1122
+ historyBoundary: "skip",
1123
+ markDirty: false
1124
+ });
1125
+ }
1126
+ let selection = state.selection;
1127
+ let comments = result.document.review.comments;
1128
+ let warnings = state.warnings;
1129
+ let activeCommentId = state.runtime.activeCommentId;
1130
+ const mappingSteps = [];
1131
+ for (const entry of result.mappings) {
1132
+ if (entry.steps === 0) {
1133
+ continue;
1134
+ }
1135
+ selection = remapSelection(selection, entry.mapping);
1136
+ mappingSteps.push(...entry.mapping.steps);
1137
+ const remappedComments = remapCommentThreads({
1138
+ comments,
1139
+ mapping: entry.mapping,
1140
+ nextContent: result.document.content,
1141
+ existingWarnings: mapWarningsThroughMapping(warnings, entry.mapping)
1142
+ });
1143
+ comments = remappedComments.comments;
1144
+ warnings = remappedComments.warnings;
1145
+ activeCommentId = activeCommentId && comments[activeCommentId] ? activeCommentId : void 0;
1146
+ }
1147
+ warnings = mergeDetachedRevisionWarnings(result.store, warnings);
1148
+ const nextDocument = {
1149
+ ...result.document,
1150
+ review: {
1151
+ ...result.document.review,
1152
+ comments,
1153
+ revisions: toEditorRevisionRecords(result.store)
1154
+ }
1155
+ };
1156
+ const appliedRevisionIds = result.effects.appliedRevisionIds;
1157
+ return createTransaction(
1158
+ {
1159
+ ...state,
1160
+ document: nextDocument,
1161
+ selection,
1162
+ warnings,
1163
+ runtime: {
1164
+ ...state.runtime,
1165
+ activeCommentId
1166
+ }
1167
+ },
1168
+ {
1169
+ historyBoundary: "push",
1170
+ markDirty: true,
1171
+ mapping: combineMappingSteps(mappingSteps),
1172
+ effects: {
1173
+ ...buildWarningEffects(state.warnings, warnings),
1174
+ changeAccepted: command.type === "review.accept-revision" && appliedRevisionIds[0] ? {
1175
+ changeId: appliedRevisionIds[0]
1176
+ } : void 0,
1177
+ changeRejected: command.type === "review.reject-revision" && appliedRevisionIds[0] ? {
1178
+ changeId: appliedRevisionIds[0]
1179
+ } : void 0
1180
+ }
1181
+ }
1182
+ );
1183
+ }
1184
+ function remapReviewStateAfterContentChange(state, nextDocument, mapping) {
1185
+ const mappedWarnings = mapWarningsThroughMapping(state.warnings, mapping);
1186
+ const remappedComments = remapCommentThreads({
1187
+ comments: nextDocument.review.comments,
1188
+ mapping,
1189
+ nextContent: nextDocument.content,
1190
+ existingWarnings: mappedWarnings
1191
+ });
1192
+ const revisions = Object.fromEntries(
1193
+ Object.entries(nextDocument.review.revisions).map(([changeId, revision]) => [
1194
+ changeId,
1195
+ {
1196
+ ...revision,
1197
+ anchor: mapAnchor(revision.anchor, mapping)
1198
+ }
1199
+ ])
1200
+ );
1201
+ const activeCommentId = state.runtime.activeCommentId && remappedComments.comments[state.runtime.activeCommentId] ? state.runtime.activeCommentId : void 0;
1202
+ const warnings = remappedComments.warnings;
1203
+ return {
1204
+ document: {
1205
+ ...nextDocument,
1206
+ review: {
1207
+ ...nextDocument.review,
1208
+ comments: remappedComments.comments,
1209
+ revisions
1210
+ }
1211
+ },
1212
+ warnings,
1213
+ activeCommentId,
1214
+ effects: buildWarningEffects(state.warnings, warnings)
1215
+ };
1216
+ }
1217
+ function createRevisionStoreFromState(state) {
1218
+ return {
1219
+ version: "revision-store/1",
1220
+ revisions: Object.fromEntries(
1221
+ Object.values(state.document.review.revisions).map((revision) => [
1222
+ revision.changeId,
1223
+ {
1224
+ revisionId: revision.changeId,
1225
+ kind: revision.kind,
1226
+ anchor: revision.anchor,
1227
+ authorId: revision.authorId ?? "unknown",
1228
+ createdAt: revision.createdAt,
1229
+ status: revision.status === "open" ? "active" : revision.status,
1230
+ warningIds: [...revision.warningIds ?? []],
1231
+ metadata: {
1232
+ source: revision.metadata?.source ?? "runtime",
1233
+ storyTarget: revision.metadata?.storyTarget,
1234
+ preserveOnlyReason: revision.metadata?.preserveOnlyReason,
1235
+ importedRevisionForm: revision.metadata?.importedRevisionForm,
1236
+ originalRevisionType: revision.metadata?.originalRevisionType,
1237
+ ooxmlRevisionId: revision.metadata?.ooxmlRevisionId
1238
+ }
1239
+ }
1240
+ ])
1241
+ )
1242
+ };
1243
+ }
1244
+ function toEditorRevisionRecords(store) {
1245
+ return Object.fromEntries(
1246
+ Object.values(store.revisions).map((revision) => [
1247
+ revision.revisionId,
1248
+ {
1249
+ changeId: revision.revisionId,
1250
+ kind: revision.kind,
1251
+ anchor: revision.anchor,
1252
+ authorId: revision.authorId,
1253
+ createdAt: revision.createdAt,
1254
+ warningIds: [...revision.warningIds],
1255
+ metadata: {
1256
+ source: revision.metadata.source,
1257
+ storyTarget: revision.metadata.storyTarget,
1258
+ preserveOnlyReason: revision.metadata.preserveOnlyReason,
1259
+ importedRevisionForm: revision.metadata.importedRevisionForm,
1260
+ originalRevisionType: revision.metadata.originalRevisionType,
1261
+ ooxmlRevisionId: revision.metadata.ooxmlRevisionId
1262
+ },
1263
+ status: revision.status === "active" ? "open" : revision.status
1264
+ }
1265
+ ])
1266
+ );
1267
+ }
1268
+ function mapWarningsThroughMapping(warnings, mapping) {
1269
+ return warnings.map(
1270
+ (warning) => warning.affectedAnchor ? {
1271
+ ...warning,
1272
+ affectedAnchor: mapAnchor(warning.affectedAnchor, mapping)
1273
+ } : warning
1274
+ );
1275
+ }
1276
+ function buildWarningEffects(previousWarnings, nextWarnings) {
1277
+ const previousById = new Set(previousWarnings.map((warning) => warning.warningId));
1278
+ const nextById = new Set(nextWarnings.map((warning) => warning.warningId));
1279
+ return {
1280
+ warningsAdded: nextWarnings.filter((warning) => !previousById.has(warning.warningId)),
1281
+ warningsCleared: previousWarnings.filter((warning) => !nextById.has(warning.warningId)).map((warning) => ({
1282
+ warningId: warning.warningId,
1283
+ code: warning.code
1284
+ }))
1285
+ };
1286
+ }
1287
+ function mergeDetachedRevisionWarnings(store, existingWarnings) {
1288
+ const retainedWarnings = existingWarnings.filter(
1289
+ (warning) => warning.code !== "revision_anchor_detached" || !warning.details || typeof warning.details.changeId !== "string" || store.revisions[warning.details.changeId]?.status === "detached"
1290
+ );
1291
+ const knownDetachedIds = new Set(
1292
+ retainedWarnings.filter((warning) => warning.code === "revision_anchor_detached").map(
1293
+ (warning) => typeof warning.details?.changeId === "string" ? warning.details.changeId : void 0
1294
+ ).filter((value) => Boolean(value))
1295
+ );
1296
+ const detachedWarnings = Object.values(store.revisions).filter(
1297
+ (revision) => revision.status === "detached" && !knownDetachedIds.has(revision.revisionId)
1298
+ ).map((revision) => ({
1299
+ warningId: `warning:revision-anchor-detached:${revision.revisionId}`,
1300
+ code: "revision_anchor_detached",
1301
+ severity: "warning",
1302
+ message: `Revision ${revision.revisionId} detached after review remapping.`,
1303
+ source: "review",
1304
+ affectedAnchor: revision.anchor.kind === "detached" ? revision.anchor : void 0,
1305
+ details: {
1306
+ changeId: revision.revisionId,
1307
+ reason: revision.anchor.kind === "detached" ? revision.anchor.reason : void 0
1308
+ }
1309
+ }));
1310
+ return [...retainedWarnings, ...detachedWarnings];
1311
+ }
1312
+ function combineMappingSteps(steps) {
1313
+ return steps.length > 0 ? {
1314
+ steps
1315
+ } : createEmptyMapping();
1316
+ }
1317
+ var suggestingRevisionCounter = 0;
1318
+ function createSuggestingRevisionId(existing, timestamp) {
1319
+ suggestingRevisionCounter += 1;
1320
+ const ts = timestamp.replace(/[^0-9]/gu, "");
1321
+ let id = `change-${ts}-s${suggestingRevisionCounter}`;
1322
+ while (existing[id]) {
1323
+ suggestingRevisionCounter += 1;
1324
+ id = `change-${ts}-s${suggestingRevisionCounter}`;
1325
+ }
1326
+ return id;
1327
+ }
1328
+ function createAuthoredRevision(existing, kind, from, to, authorId, timestamp) {
1329
+ const changeId = createSuggestingRevisionId(existing, timestamp);
1330
+ return {
1331
+ changeId,
1332
+ kind,
1333
+ anchor: {
1334
+ kind: "range",
1335
+ range: { from, to },
1336
+ assoc: { start: 1, end: -1 }
1337
+ },
1338
+ authorId,
1339
+ createdAt: timestamp,
1340
+ warningIds: [],
1341
+ metadata: {
1342
+ source: "runtime"
1343
+ },
1344
+ status: "open"
1345
+ };
1346
+ }
1347
+ function createSuggestingUnsupportedTransaction(state, message) {
1348
+ return createTransaction(state, {
1349
+ historyBoundary: "skip",
1350
+ markDirty: false,
1351
+ effects: {
1352
+ commandBlocked: {
1353
+ code: "suggesting_unsupported",
1354
+ message
1355
+ }
1356
+ }
1357
+ });
1358
+ }
1359
+ function isSingleParagraphSuggestingRange(document, from, to) {
1360
+ const ranges = [];
1361
+ let cursor = 0;
1362
+ let previousWasParagraph = false;
1363
+ for (const block of document.content.children) {
1364
+ if (block.type === "paragraph") {
1365
+ if (previousWasParagraph) {
1366
+ cursor += 1;
1367
+ }
1368
+ const start = cursor;
1369
+ cursor += block.children.reduce((size, child) => {
1370
+ if (child.type === "text") {
1371
+ return size + child.text.length;
1372
+ }
1373
+ if (child.type === "hyperlink") {
1374
+ return size + child.children.reduce((childSize, entry) => {
1375
+ if (entry.type === "text") {
1376
+ return childSize + entry.text.length;
1377
+ }
1378
+ return childSize + 1;
1379
+ }, 0);
1380
+ }
1381
+ return size + 1;
1382
+ }, 0);
1383
+ ranges.push({ start, end: cursor });
1384
+ previousWasParagraph = true;
1385
+ continue;
1386
+ }
1387
+ cursor += 1;
1388
+ previousWasParagraph = false;
1389
+ }
1390
+ return ranges.some((range) => from >= range.start && to <= range.end);
1391
+ }
1392
+ function applySuggestingInsert(state, text, context) {
1393
+ if (state.readOnly) {
1394
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1395
+ }
1396
+ const authorId = context.defaultAuthorId ?? "unknown";
1397
+ const selection = state.selection;
1398
+ const from = Math.min(selection.anchor, selection.head);
1399
+ const to = Math.max(selection.anchor, selection.head);
1400
+ const isCollapsed = from === to;
1401
+ if (!isCollapsed && !isSingleParagraphSuggestingRange(state.document, from, to)) {
1402
+ return createSuggestingUnsupportedTransaction(
1403
+ state,
1404
+ "Suggesting mode does not yet support multi-paragraph replacement ranges."
1405
+ );
1406
+ }
1407
+ if (isCollapsed) {
1408
+ const result2 = insertText(state.document, selection, text, { timestamp: context.timestamp });
1409
+ const insertedFrom2 = from;
1410
+ const insertedTo2 = from + Array.from(text).length;
1411
+ const reviewState2 = remapReviewStateAfterContentChange(
1412
+ state,
1413
+ result2.document,
1414
+ result2.mapping
1415
+ );
1416
+ const adjacentInsertion = findAdjacentAuthoredInsertion(
1417
+ reviewState2.document.review.revisions,
1418
+ insertedFrom2
1419
+ );
1420
+ let finalRevisionId;
1421
+ let finalRevisions;
1422
+ if (adjacentInsertion && adjacentInsertion.anchor.kind === "range") {
1423
+ const extended = {
1424
+ ...adjacentInsertion,
1425
+ anchor: {
1426
+ kind: "range",
1427
+ range: { from: adjacentInsertion.anchor.range.from, to: insertedTo2 },
1428
+ assoc: { start: 1, end: -1 }
1429
+ }
1430
+ };
1431
+ finalRevisionId = extended.changeId;
1432
+ finalRevisions = {
1433
+ ...reviewState2.document.review.revisions,
1434
+ [extended.changeId]: extended
1435
+ };
1436
+ } else {
1437
+ const revision = createAuthoredRevision(
1438
+ reviewState2.document.review.revisions,
1439
+ "insertion",
1440
+ insertedFrom2,
1441
+ insertedTo2,
1442
+ authorId,
1443
+ context.timestamp
1444
+ );
1445
+ finalRevisionId = revision.changeId;
1446
+ finalRevisions = {
1447
+ ...reviewState2.document.review.revisions,
1448
+ [revision.changeId]: revision
1449
+ };
1450
+ }
1451
+ const finalDocument2 = {
1452
+ ...reviewState2.document,
1453
+ review: {
1454
+ ...reviewState2.document.review,
1455
+ revisions: finalRevisions
1456
+ }
1457
+ };
1458
+ return createTransaction(
1459
+ {
1460
+ ...state,
1461
+ document: finalDocument2,
1462
+ selection: result2.selection,
1463
+ warnings: reviewState2.warnings,
1464
+ runtime: {
1465
+ ...state.runtime,
1466
+ activeCommentId: reviewState2.activeCommentId
1467
+ }
1468
+ },
1469
+ {
1470
+ historyBoundary: "push",
1471
+ markDirty: true,
1472
+ mapping: result2.mapping,
1473
+ effects: {
1474
+ ...reviewState2.effects,
1475
+ revisionAuthored: { changeId: finalRevisionId, kind: "insertion" }
1476
+ }
1477
+ }
1478
+ );
1479
+ }
1480
+ const insertSelection = createSelectionSnapshot(to, to);
1481
+ const result = insertText(state.document, insertSelection, text, { timestamp: context.timestamp });
1482
+ const insertedFrom = to;
1483
+ const insertedTo = to + Array.from(text).length;
1484
+ const reviewState = remapReviewStateAfterContentChange(
1485
+ state,
1486
+ result.document,
1487
+ result.mapping
1488
+ );
1489
+ const deletionRevision = createAuthoredRevision(
1490
+ reviewState.document.review.revisions,
1491
+ "deletion",
1492
+ from,
1493
+ to,
1494
+ authorId,
1495
+ context.timestamp
1496
+ );
1497
+ const insertionRevision = createAuthoredRevision(
1498
+ {
1499
+ ...reviewState.document.review.revisions,
1500
+ [deletionRevision.changeId]: deletionRevision
1501
+ },
1502
+ "insertion",
1503
+ insertedFrom,
1504
+ insertedTo,
1505
+ authorId,
1506
+ context.timestamp
1507
+ );
1508
+ const finalDocument = {
1509
+ ...reviewState.document,
1510
+ review: {
1511
+ ...reviewState.document.review,
1512
+ revisions: {
1513
+ ...reviewState.document.review.revisions,
1514
+ [deletionRevision.changeId]: deletionRevision,
1515
+ [insertionRevision.changeId]: insertionRevision
1516
+ }
1517
+ }
1518
+ };
1519
+ const caretPos = insertedTo;
1520
+ return createTransaction(
1521
+ {
1522
+ ...state,
1523
+ document: finalDocument,
1524
+ selection: createSelectionSnapshot(caretPos, caretPos),
1525
+ warnings: reviewState.warnings,
1526
+ runtime: {
1527
+ ...state.runtime,
1528
+ activeCommentId: reviewState.activeCommentId
1529
+ }
1530
+ },
1531
+ {
1532
+ historyBoundary: "push",
1533
+ markDirty: true,
1534
+ mapping: result.mapping,
1535
+ effects: {
1536
+ ...reviewState.effects,
1537
+ revisionAuthored: { changeId: insertionRevision.changeId, kind: "insertion" }
1538
+ }
1539
+ }
1540
+ );
1541
+ }
1542
+ function applySuggestingDelete(state, direction, context) {
1543
+ if (state.readOnly) {
1544
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1545
+ }
1546
+ const authorId = context.defaultAuthorId ?? "unknown";
1547
+ const selection = state.selection;
1548
+ const from = Math.min(selection.anchor, selection.head);
1549
+ const to = Math.max(selection.anchor, selection.head);
1550
+ const isCollapsed = from === to;
1551
+ if (!isCollapsed && !isSingleParagraphSuggestingRange(state.document, from, to)) {
1552
+ return createSuggestingUnsupportedTransaction(
1553
+ state,
1554
+ "Suggesting mode does not yet support multi-paragraph deletion ranges."
1555
+ );
1556
+ }
1557
+ let deleteFrom;
1558
+ let deleteTo;
1559
+ if (isCollapsed) {
1560
+ if (direction === "backward") {
1561
+ deleteFrom = Math.max(0, from - 1);
1562
+ deleteTo = from;
1563
+ } else {
1564
+ deleteFrom = from;
1565
+ deleteTo = from + 1;
1566
+ }
1567
+ } else {
1568
+ deleteFrom = from;
1569
+ deleteTo = to;
1570
+ }
1571
+ if (deleteFrom === deleteTo) {
1572
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1573
+ }
1574
+ const existingDeletion = findOverlappingAuthoredDeletion(
1575
+ state.document.review.revisions,
1576
+ deleteFrom,
1577
+ deleteTo
1578
+ );
1579
+ if (existingDeletion) {
1580
+ const extendedFrom = Math.min(
1581
+ existingDeletion.anchor.kind === "range" ? existingDeletion.anchor.range.from : deleteFrom,
1582
+ deleteFrom
1583
+ );
1584
+ const extendedTo = Math.max(
1585
+ existingDeletion.anchor.kind === "range" ? existingDeletion.anchor.range.to : deleteTo,
1586
+ deleteTo
1587
+ );
1588
+ const updatedRevision = {
1589
+ ...existingDeletion,
1590
+ anchor: {
1591
+ kind: "range",
1592
+ range: { from: extendedFrom, to: extendedTo },
1593
+ assoc: { start: 1, end: -1 }
1594
+ }
1595
+ };
1596
+ const nextDocument2 = {
1597
+ ...state.document,
1598
+ updatedAt: context.timestamp,
1599
+ review: {
1600
+ ...state.document.review,
1601
+ revisions: {
1602
+ ...state.document.review.revisions,
1603
+ [updatedRevision.changeId]: updatedRevision
1604
+ }
1605
+ }
1606
+ };
1607
+ const caretPos2 = direction === "backward" ? deleteFrom : deleteTo;
1608
+ return createTransaction(
1609
+ {
1610
+ ...state,
1611
+ document: nextDocument2,
1612
+ selection: createSelectionSnapshot(caretPos2, caretPos2)
1613
+ },
1614
+ {
1615
+ historyBoundary: "push",
1616
+ markDirty: true,
1617
+ effects: {
1618
+ revisionAuthored: { changeId: updatedRevision.changeId, kind: "deletion" }
1619
+ }
1620
+ }
1621
+ );
1622
+ }
1623
+ const revision = createAuthoredRevision(
1624
+ state.document.review.revisions,
1625
+ "deletion",
1626
+ deleteFrom,
1627
+ deleteTo,
1628
+ authorId,
1629
+ context.timestamp
1630
+ );
1631
+ const nextDocument = {
1632
+ ...state.document,
1633
+ updatedAt: context.timestamp,
1634
+ review: {
1635
+ ...state.document.review,
1636
+ revisions: {
1637
+ ...state.document.review.revisions,
1638
+ [revision.changeId]: revision
1639
+ }
1640
+ }
1641
+ };
1642
+ const caretPos = direction === "backward" ? deleteFrom : deleteTo;
1643
+ return createTransaction(
1644
+ {
1645
+ ...state,
1646
+ document: nextDocument,
1647
+ selection: createSelectionSnapshot(caretPos, caretPos)
1648
+ },
1649
+ {
1650
+ historyBoundary: "push",
1651
+ markDirty: true,
1652
+ effects: {
1653
+ revisionAuthored: { changeId: revision.changeId, kind: "deletion" }
1654
+ }
1655
+ }
1656
+ );
1657
+ }
1658
+ function applySuggestingInsertUnit(state, unitType, context) {
1659
+ if (state.readOnly) {
1660
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1661
+ }
1662
+ const authorId = context.defaultAuthorId ?? "unknown";
1663
+ const selection = state.selection;
1664
+ const from = Math.min(selection.anchor, selection.head);
1665
+ const to = Math.max(selection.anchor, selection.head);
1666
+ if (from !== to && !isSingleParagraphSuggestingRange(state.document, from, to)) {
1667
+ return createSuggestingUnsupportedTransaction(
1668
+ state,
1669
+ "Suggesting mode does not yet support multi-paragraph replacement ranges."
1670
+ );
1671
+ }
1672
+ const insertPos = to;
1673
+ const insertSelection = createSelectionSnapshot(insertPos, insertPos);
1674
+ const applyFn = unitType === "tab" ? insertTab : insertHardBreak;
1675
+ const result = applyFn(state.document, insertSelection, { timestamp: context.timestamp });
1676
+ const reviewState = remapReviewStateAfterContentChange(
1677
+ state,
1678
+ result.document,
1679
+ result.mapping
1680
+ );
1681
+ const deletionRevision = from !== to ? createAuthoredRevision(
1682
+ reviewState.document.review.revisions,
1683
+ "deletion",
1684
+ from,
1685
+ to,
1686
+ authorId,
1687
+ context.timestamp
1688
+ ) : void 0;
1689
+ const insertionRevision = createAuthoredRevision(
1690
+ {
1691
+ ...reviewState.document.review.revisions,
1692
+ ...deletionRevision ? { [deletionRevision.changeId]: deletionRevision } : {}
1693
+ },
1694
+ "insertion",
1695
+ insertPos,
1696
+ insertPos + 1,
1697
+ authorId,
1698
+ context.timestamp
1699
+ );
1700
+ const finalDocument = {
1701
+ ...reviewState.document,
1702
+ review: {
1703
+ ...reviewState.document.review,
1704
+ revisions: {
1705
+ ...reviewState.document.review.revisions,
1706
+ ...deletionRevision ? { [deletionRevision.changeId]: deletionRevision } : {},
1707
+ [insertionRevision.changeId]: insertionRevision
1708
+ }
1709
+ }
1710
+ };
1711
+ return createTransaction(
1712
+ {
1713
+ ...state,
1714
+ document: finalDocument,
1715
+ selection: result.selection,
1716
+ warnings: reviewState.warnings,
1717
+ runtime: {
1718
+ ...state.runtime,
1719
+ activeCommentId: reviewState.activeCommentId
1720
+ }
1721
+ },
1722
+ {
1723
+ historyBoundary: "push",
1724
+ markDirty: true,
1725
+ mapping: result.mapping,
1726
+ effects: {
1727
+ ...reviewState.effects,
1728
+ revisionAuthored: { changeId: insertionRevision.changeId, kind: "insertion" }
1729
+ }
1730
+ }
1731
+ );
1732
+ }
1733
+ function findOverlappingAuthoredDeletion(revisions, from, to) {
1734
+ for (const revision of Object.values(revisions)) {
1735
+ if (revision.kind === "deletion" && revision.status === "open" && revision.metadata?.source === "runtime" && revision.anchor.kind === "range") {
1736
+ const revFrom = revision.anchor.range.from;
1737
+ const revTo = revision.anchor.range.to;
1738
+ if (from >= revFrom && from <= revTo || to >= revFrom && to <= revTo || from === revTo || to === revFrom) {
1739
+ return revision;
1740
+ }
1741
+ }
1742
+ }
1743
+ return void 0;
1744
+ }
1745
+ function findAdjacentAuthoredInsertion(revisions, cursorAt) {
1746
+ for (const revision of Object.values(revisions)) {
1747
+ if (revision.kind === "insertion" && revision.status === "open" && revision.metadata?.source === "runtime" && revision.anchor.kind === "range" && revision.anchor.range.to === cursorAt) {
1748
+ return revision;
1749
+ }
1750
+ }
1751
+ return void 0;
1752
+ }
1753
+
1754
+ // src/validation/compatibility-report.ts
1755
+ var COMPATIBILITY_REPORT_VERSION = "compatibility-report/1";
1756
+ function createCompatibilityReport(input) {
1757
+ const diagnostics = createDiagnostics(input);
1758
+ return Object.freeze({
1759
+ reportVersion: COMPATIBILITY_REPORT_VERSION,
1760
+ generatedAt: input.generatedAt,
1761
+ blockExport: diagnosticsBlockExport(diagnostics, input.blockExport),
1762
+ featureEntries: diagnostics.featureEntries,
1763
+ warnings: diagnostics.warnings,
1764
+ errors: diagnostics.errors
1765
+ });
1766
+ }
1767
+ function mergeCompatibilityReports(reports, options) {
1768
+ return createCompatibilityReport({
1769
+ generatedAt: options.generatedAt,
1770
+ blockExport: options.blockExport ?? reports.some((report) => report.blockExport),
1771
+ featureEntries: flattenUnique(reports.flatMap((report) => report.featureEntries)),
1772
+ warnings: flattenUnique(reports.flatMap((report) => report.warnings)),
1773
+ errors: flattenUnique(reports.flatMap((report) => report.errors))
1774
+ });
1775
+ }
1776
+ function flattenUnique(items) {
1777
+ const deduped = /* @__PURE__ */ new Map();
1778
+ for (const item of items) {
1779
+ deduped.set(JSON.stringify(item), item);
1780
+ }
1781
+ return Object.freeze([...deduped.values()]);
1782
+ }
1783
+
1784
+ // src/runtime/workflow-markup.ts
1785
+ var BLOCKED_IMPORT_FEATURE_KEYS = /* @__PURE__ */ new Set(["alt-chunk", "alternate-content", "custom-xml"]);
1786
+ function collectWorkflowMarkupSnapshot(input) {
1787
+ const highlights = [];
1788
+ const fields = [];
1789
+ const opaqueFragments = [];
1790
+ const surface = input.renderSnapshot.surface;
1791
+ if (surface) {
1792
+ collectSurfaceMarkup(
1793
+ surface.blocks,
1794
+ MAIN_STORY_TARGET,
1795
+ input.preservation,
1796
+ highlights,
1797
+ opaqueFragments
1798
+ );
1799
+ for (const story of surface.secondaryStories) {
1800
+ collectSurfaceMarkup(
1801
+ story.blocks,
1802
+ story.target,
1803
+ input.preservation,
1804
+ highlights,
1805
+ opaqueFragments
1806
+ );
1807
+ }
1808
+ fields.push(...collectFieldMarkup(surface, input.fieldSnapshot));
1809
+ }
1810
+ opaqueFragments.push(...collectOpaqueFragmentMarkup(input.preservation, opaqueFragments));
1811
+ const comments = input.renderSnapshot.comments.threads.map((thread) => ({
1812
+ markupId: `comment:${thread.commentId}`,
1813
+ kind: "comment",
1814
+ commentId: thread.commentId,
1815
+ anchor: thread.anchor,
1816
+ label: thread.anchorLabel,
1817
+ excerpt: thread.excerpt,
1818
+ status: thread.status,
1819
+ warningCount: thread.warningCount,
1820
+ entryCount: thread.entryCount,
1821
+ createdAt: thread.createdAt,
1822
+ createdBy: thread.createdBy
1823
+ }));
1824
+ const revisions = input.renderSnapshot.trackedChanges.revisions.map(
1825
+ (revision) => ({
1826
+ markupId: `revision:${revision.revisionId}`,
1827
+ kind: "revision",
1828
+ revisionId: revision.revisionId,
1829
+ revisionKind: revision.kind,
1830
+ anchor: revision.anchor,
1831
+ label: revision.label,
1832
+ excerpt: revision.excerpt,
1833
+ status: revision.status,
1834
+ actionability: revision.actionability,
1835
+ authorId: revision.authorId,
1836
+ createdAt: revision.createdAt,
1837
+ preserveOnlyReason: revision.preserveOnlyReason
1838
+ })
1839
+ );
1840
+ const protectedRanges = input.protectionSnapshot.ranges.filter(
1841
+ (range) => typeof range.start === "number" && typeof range.end === "number"
1842
+ ).map(
1843
+ (range) => ({
1844
+ markupId: `protected-range:${range.rangeId}`,
1845
+ kind: "protected_range",
1846
+ rangeId: range.rangeId,
1847
+ anchor: createRangeAnchor2(range.start, range.end),
1848
+ storyTarget: MAIN_STORY_TARGET,
1849
+ label: `Protected range ${range.rangeId}`,
1850
+ excerpt: range.enforcementReason,
1851
+ enforced: range.enforced,
1852
+ enforcementReason: range.enforcementReason,
1853
+ editorGroup: range.editorGroup,
1854
+ editor: range.editor
1855
+ })
1856
+ );
1857
+ const items = [
1858
+ ...highlights,
1859
+ ...comments,
1860
+ ...revisions,
1861
+ ...fields,
1862
+ ...protectedRanges,
1863
+ ...opaqueFragments
1864
+ ];
1865
+ return {
1866
+ totalCount: items.length,
1867
+ items,
1868
+ highlights,
1869
+ comments,
1870
+ revisions,
1871
+ fields,
1872
+ protectedRanges,
1873
+ opaqueFragments
1874
+ };
1875
+ }
1876
+ function deriveWorkflowCandidateRangesFromMarkup(snapshot, options = {}) {
1877
+ const allowedKinds = options.kinds ? new Set(options.kinds) : null;
1878
+ const includeDetached = options.includeDetached === true;
1879
+ return snapshot.items.filter((item) => allowedKinds ? allowedKinds.has(item.kind) : true).filter((item) => includeDetached || item.anchor.kind !== "detached").map(
1880
+ (item) => ({
1881
+ candidateId: item.markupId,
1882
+ storyTarget: item.storyTarget,
1883
+ anchor: item.anchor,
1884
+ label: item.label,
1885
+ source: options.source ?? "host"
1886
+ })
1887
+ );
1888
+ }
1889
+ function collectSurfaceMarkup(blocks, storyTarget, preservation, highlights, opaqueFragments) {
1890
+ for (const block of blocks) {
1891
+ if (block.kind === "paragraph") {
1892
+ for (const segment of block.segments) {
1893
+ collectSegmentMarkup(segment, storyTarget, preservation, highlights, opaqueFragments);
1894
+ }
1895
+ continue;
1896
+ }
1897
+ if (block.kind === "table") {
1898
+ for (const row of block.rows) {
1899
+ for (const cell of row.cells) {
1900
+ collectSurfaceMarkup(cell.content, storyTarget, preservation, highlights, opaqueFragments);
1901
+ }
1902
+ }
1903
+ continue;
1904
+ }
1905
+ if (block.kind === "sdt_block") {
1906
+ collectSurfaceMarkup(block.children, storyTarget, preservation, highlights, opaqueFragments);
1907
+ continue;
1908
+ }
1909
+ const fragment = preservation.opaqueFragments[block.fragmentId];
1910
+ const descriptor = fragment ? describeOpaqueFragment(fragment) : null;
1911
+ const blockedReasonCode = fragment && BLOCKED_IMPORT_FEATURE_KEYS.has(descriptor?.featureKey ?? "") ? "workflow_blocked_import" : "workflow_preserve_only";
1912
+ opaqueFragments.push({
1913
+ markupId: `opaque:${block.fragmentId}`,
1914
+ kind: "opaque_fragment",
1915
+ fragmentId: block.fragmentId,
1916
+ warningId: block.warningId,
1917
+ anchor: createRangeAnchor2(block.from, block.to),
1918
+ storyTarget,
1919
+ label: block.label,
1920
+ excerpt: block.detail,
1921
+ detail: block.detail,
1922
+ blockedReasonCode
1923
+ });
1924
+ }
1925
+ }
1926
+ function collectSegmentMarkup(segment, storyTarget, preservation, highlights, opaqueFragments) {
1927
+ if (segment.kind === "text" && segment.markAttrs?.backgroundColor) {
1928
+ highlights.push({
1929
+ markupId: `highlight:${storyTargetKey2(storyTarget)}:${segment.from}:${segment.to}:${segment.markAttrs.backgroundColor}`,
1930
+ kind: "highlight",
1931
+ anchor: createRangeAnchor2(segment.from, segment.to),
1932
+ storyTarget,
1933
+ label: `Highlight ${segment.markAttrs.backgroundColor}`,
1934
+ excerpt: segment.text,
1935
+ color: segment.markAttrs.backgroundColor,
1936
+ text: segment.text
1937
+ });
1938
+ return;
1939
+ }
1940
+ if (segment.kind === "opaque_inline") {
1941
+ const fragment = preservation.opaqueFragments[segment.fragmentId];
1942
+ const descriptor = fragment ? describeOpaqueFragment(fragment) : null;
1943
+ const blockedReasonCode = fragment && BLOCKED_IMPORT_FEATURE_KEYS.has(descriptor?.featureKey ?? "") ? "workflow_blocked_import" : "workflow_preserve_only";
1944
+ opaqueFragments.push({
1945
+ markupId: `opaque:${segment.fragmentId}`,
1946
+ kind: "opaque_fragment",
1947
+ fragmentId: segment.fragmentId,
1948
+ warningId: segment.warningId,
1949
+ anchor: createRangeAnchor2(segment.from, segment.to),
1950
+ storyTarget,
1951
+ label: segment.label,
1952
+ excerpt: segment.detail,
1953
+ detail: segment.detail,
1954
+ blockedReasonCode
1955
+ });
1956
+ }
1957
+ }
1958
+ function collectFieldMarkup(surface, fieldSnapshot) {
1959
+ if (!surface) {
1960
+ return [];
1961
+ }
1962
+ const stories = [
1963
+ { blocks: surface.blocks, storyTarget: MAIN_STORY_TARGET },
1964
+ ...surface.secondaryStories.map((story) => ({
1965
+ blocks: story.blocks,
1966
+ storyTarget: story.target
1967
+ }))
1968
+ ];
1969
+ return fieldSnapshot.fields.flatMap((field) => {
1970
+ const displayText = field.displayText.trim();
1971
+ if (!displayText) {
1972
+ return [];
1973
+ }
1974
+ for (const story of stories) {
1975
+ const matches = searchSurfaceBlocks(story.blocks, displayText, { limit: 2 });
1976
+ if (matches.length === 1) {
1977
+ const match = matches[0];
1978
+ return [
1979
+ {
1980
+ markupId: `field:${field.index}`,
1981
+ kind: "field",
1982
+ anchor: createRangeAnchor2(match.from, match.to),
1983
+ storyTarget: story.storyTarget,
1984
+ label: displayText,
1985
+ excerpt: field.instruction,
1986
+ fieldIndex: field.index,
1987
+ fieldFamily: field.fieldFamily,
1988
+ fieldTarget: field.fieldTarget,
1989
+ refreshStatus: field.refreshStatus,
1990
+ displayText: field.displayText
1991
+ }
1992
+ ];
1993
+ }
1994
+ }
1995
+ return [];
1996
+ });
1997
+ }
1998
+ function collectOpaqueFragmentMarkup(preservation, existing) {
1999
+ const seen = new Set(existing.map((item) => item.fragmentId));
2000
+ return Object.values(preservation.opaqueFragments).filter(
2001
+ (fragment) => !seen.has(fragment.fragmentId) && fragment.packagePartName === "/word/document.xml"
2002
+ ).map((fragment) => {
2003
+ const descriptor = describeOpaqueFragment(fragment);
2004
+ const blockedReasonCode = BLOCKED_IMPORT_FEATURE_KEYS.has(descriptor.featureKey) ? "workflow_blocked_import" : "workflow_preserve_only";
2005
+ return {
2006
+ markupId: `opaque:${fragment.fragmentId}`,
2007
+ kind: "opaque_fragment",
2008
+ fragmentId: fragment.fragmentId,
2009
+ warningId: fragment.warningId,
2010
+ anchor: createRangeAnchor2(fragment.lastKnownRange.from, fragment.lastKnownRange.to),
2011
+ storyTarget: MAIN_STORY_TARGET,
2012
+ label: descriptor.label,
2013
+ excerpt: descriptor.detail,
2014
+ detail: descriptor.detail,
2015
+ blockedReasonCode
2016
+ };
2017
+ });
2018
+ }
2019
+ function createRangeAnchor2(from, to) {
2020
+ return {
2021
+ kind: "range",
2022
+ from,
2023
+ to,
2024
+ assoc: {
2025
+ start: -1,
2026
+ end: 1
2027
+ }
2028
+ };
2029
+ }
2030
+ function storyTargetKey2(storyTarget) {
2031
+ switch (storyTarget.kind) {
2032
+ case "main":
2033
+ return "main";
2034
+ case "header":
2035
+ case "footer":
2036
+ return `${storyTarget.kind}:${storyTarget.relationshipId}:${storyTarget.variant}:${storyTarget.sectionIndex ?? "none"}`;
2037
+ case "footnote":
2038
+ case "endnote":
2039
+ return `${storyTarget.kind}:${storyTarget.noteId}`;
2040
+ }
2041
+ }
2042
+
2043
+ // src/runtime/document-runtime.ts
2044
+ function createDocumentRuntime(options) {
2045
+ const clock = options.clock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2046
+ const editorBuild = options.editorBuild ?? "dev";
2047
+ let defaultAuthorId = options.defaultAuthorId;
2048
+ const sessionId = createSessionId(options.documentId, clock());
2049
+ const listeners = /* @__PURE__ */ new Set();
2050
+ const eventListeners = /* @__PURE__ */ new Set();
2051
+ const history = {
2052
+ past: [],
2053
+ future: []
2054
+ };
2055
+ let activeStory = MAIN_STORY_TARGET;
2056
+ const storySelections = /* @__PURE__ */ new Map();
2057
+ let viewState = createViewState(options.initialViewState);
2058
+ let protectionSnapshot = options.protectionSnapshot ?? options.initialSessionState?.protectionSnapshot ?? options.initialSnapshot?.protectionSnapshot ?? {
2059
+ hasDocumentProtection: false,
2060
+ enforcementActive: false,
2061
+ ranges: [],
2062
+ enforcedRangeCount: 0,
2063
+ preservedRangeCount: 0
2064
+ };
2065
+ let workflowOverlay = null;
2066
+ let hostAnnotationOverlay = null;
2067
+ const initialPersistedSnapshot = options.initialSessionState ? persistedSnapshotFromEditorSessionState(options.initialSessionState, {
2068
+ savedAt: options.initialSessionState.updatedAt
2069
+ }) : options.initialSnapshot;
2070
+ let state = createEditorState({
2071
+ documentId: options.documentId,
2072
+ sessionId,
2073
+ sourceLabel: options.sourceLabel,
2074
+ readOnly: options.readOnly,
2075
+ persistedSnapshot: initialPersistedSnapshot,
2076
+ canonicalDocument: options.initialCanonicalDocument,
2077
+ fatalError: options.fatalError
2078
+ });
2079
+ storySelections.set(storyTargetKey(MAIN_STORY_TARGET), state.selection);
2080
+ let cachedSurface;
2081
+ let cachedCompatibility;
2082
+ let cachedComments;
2083
+ let cachedTrackedChanges;
2084
+ let cachedPageLayout;
2085
+ let cachedNavigation;
2086
+ let cachedViewStateSnapshot;
2087
+ let cachedInteractionGuardSnapshot;
2088
+ let cachedWorkflowScopeSnapshot;
2089
+ let cachedWorkflowMarkupSnapshot;
2090
+ function getCachedSurface(document, nextActiveStory) {
2091
+ const activeStoryKey = storyTargetKey(nextActiveStory);
2092
+ if (cachedSurface && cachedSurface.revisionToken === state.revisionToken && cachedSurface.activeStoryKey === activeStoryKey) {
2093
+ return cachedSurface.snapshot;
2094
+ }
2095
+ const snapshot = createEditorSurfaceSnapshot(document, state.selection, nextActiveStory);
2096
+ recordPerfSample("snapshot.surface");
2097
+ incrementInvalidationCounter("runtime.snapshot.surfaceMisses");
2098
+ cachedSurface = {
2099
+ revisionToken: state.revisionToken,
2100
+ activeStoryKey,
2101
+ snapshot
2102
+ };
2103
+ return snapshot;
2104
+ }
2105
+ function getCachedCompatibilityReport(nextState) {
2106
+ if (cachedCompatibility && cachedCompatibility.revisionToken === nextState.revisionToken && cachedCompatibility.warnings === nextState.warnings && cachedCompatibility.fatalError === nextState.fatalError) {
2107
+ return cachedCompatibility.report;
2108
+ }
2109
+ const derived = createDerivedCompatibility(nextState);
2110
+ recordPerfSample("snapshot.compatibility");
2111
+ incrementInvalidationCounter("runtime.snapshot.compatibilityMisses");
2112
+ const report = {
2113
+ blockExport: derived.blockExport,
2114
+ blockExportReasons: listBlockExportReasons(derived),
2115
+ warningCount: derived.warnings.length,
2116
+ errorCount: derived.errors.length,
2117
+ featureEntries: derived.featureEntries.map(
2118
+ (entry) => toPublicCompatibilityFeatureEntry(entry)
2119
+ )
2120
+ };
2121
+ cachedCompatibility = {
2122
+ revisionToken: nextState.revisionToken,
2123
+ warnings: nextState.warnings,
2124
+ fatalError: nextState.fatalError,
2125
+ report
2126
+ };
2127
+ return report;
2128
+ }
2129
+ function getCachedCommentSidebarSnapshot(nextState) {
2130
+ if (cachedComments && cachedComments.comments === nextState.document.review.comments && cachedComments.activeCommentId === nextState.runtime.activeCommentId) {
2131
+ return cachedComments.snapshot;
2132
+ }
2133
+ const snapshot = toPublicCommentSidebarSnapshot(nextState);
2134
+ cachedComments = {
2135
+ comments: nextState.document.review.comments,
2136
+ activeCommentId: nextState.runtime.activeCommentId,
2137
+ snapshot
2138
+ };
2139
+ return snapshot;
2140
+ }
2141
+ function getCachedTrackedChangesSnapshot(nextState, _surface) {
2142
+ if (cachedTrackedChanges && cachedTrackedChanges.revisions === nextState.document.review.revisions && cachedTrackedChanges.revisionToken === nextState.revisionToken) {
2143
+ return cachedTrackedChanges.snapshot;
2144
+ }
2145
+ const snapshot = toPublicTrackedChangesSnapshot(nextState);
2146
+ cachedTrackedChanges = {
2147
+ revisions: nextState.document.review.revisions,
2148
+ revisionToken: nextState.revisionToken,
2149
+ snapshot
2150
+ };
2151
+ return snapshot;
2152
+ }
2153
+ function getCachedDocumentNavigationSnapshot(nextState, nextActiveStory) {
2154
+ const activeStoryKey = storyTargetKey(nextActiveStory);
2155
+ if (cachedNavigation && cachedNavigation.revisionToken === nextState.revisionToken && cachedNavigation.activeStoryKey === activeStoryKey) {
2156
+ if (cachedNavigation.selectionHead === nextState.selection.head) {
2157
+ return cachedNavigation.snapshot;
2158
+ }
2159
+ const snapshot2 = createDocumentNavigationSnapshot(
2160
+ nextState.document,
2161
+ nextState.selection.head,
2162
+ nextActiveStory
2163
+ );
2164
+ if (snapshot2.activePageIndex === cachedNavigation.snapshot.activePageIndex && snapshot2.activeSectionIndex === cachedNavigation.snapshot.activeSectionIndex) {
2165
+ cachedNavigation = {
2166
+ revisionToken: nextState.revisionToken,
2167
+ activeStoryKey,
2168
+ selectionHead: nextState.selection.head,
2169
+ snapshot: cachedNavigation.snapshot
2170
+ };
2171
+ return cachedNavigation.snapshot;
2172
+ }
2173
+ cachedNavigation = {
2174
+ revisionToken: nextState.revisionToken,
2175
+ activeStoryKey,
2176
+ selectionHead: nextState.selection.head,
2177
+ snapshot: snapshot2
2178
+ };
2179
+ return snapshot2;
2180
+ }
2181
+ const snapshot = createDocumentNavigationSnapshot(
2182
+ nextState.document,
2183
+ nextState.selection.head,
2184
+ nextActiveStory
2185
+ );
2186
+ recordPerfSample("snapshot.navigation");
2187
+ incrementInvalidationCounter("runtime.snapshot.navigationMisses");
2188
+ cachedNavigation = {
2189
+ revisionToken: nextState.revisionToken,
2190
+ activeStoryKey,
2191
+ selectionHead: nextState.selection.head,
2192
+ snapshot
2193
+ };
2194
+ return snapshot;
2195
+ }
2196
+ function resolvePageLayoutActiveSectionIndex(nextState, nextActiveStory) {
2197
+ if (nextActiveStory.kind === "main") {
2198
+ return getCachedDocumentNavigationSnapshot(nextState, nextActiveStory).activeSectionIndex;
2199
+ }
2200
+ if ("sectionIndex" in nextActiveStory && typeof nextActiveStory.sectionIndex === "number") {
2201
+ return nextActiveStory.sectionIndex;
2202
+ }
2203
+ return storyTargetKey(nextActiveStory);
2204
+ }
2205
+ function getCachedPageLayoutSnapshot(nextState, nextActiveStory) {
2206
+ const activeStoryKey = storyTargetKey(nextActiveStory);
2207
+ const activeSectionIndex = resolvePageLayoutActiveSectionIndex(
2208
+ nextState,
2209
+ nextActiveStory
2210
+ );
2211
+ if (cachedPageLayout && cachedPageLayout.revisionToken === nextState.revisionToken && cachedPageLayout.activeStoryKey === activeStoryKey && cachedPageLayout.activeSectionIndex === activeSectionIndex) {
2212
+ return cachedPageLayout.snapshot;
2213
+ }
2214
+ const snapshot = derivePageLayoutSnapshot(nextState, nextActiveStory, storySelections);
2215
+ cachedPageLayout = {
2216
+ revisionToken: nextState.revisionToken,
2217
+ activeStoryKey,
2218
+ activeSectionIndex,
2219
+ snapshot
2220
+ };
2221
+ return snapshot;
2222
+ }
2223
+ function evaluateWorkflowBlockedReasons(selection, commandType) {
2224
+ const reasons = [];
2225
+ const selectionBounds = {
2226
+ from: Math.min(selection.anchor, selection.head),
2227
+ to: Math.max(selection.anchor, selection.head)
2228
+ };
2229
+ const selectionRange = expandSelectionRange(selectionBounds);
2230
+ const opaqueReason = deriveOpaqueWorkflowBlockedReason(selectionRange);
2231
+ if (opaqueReason) {
2232
+ reasons.push(opaqueReason);
2233
+ }
2234
+ if (state.readOnly) {
2235
+ reasons.push({
2236
+ code: "document_read_only",
2237
+ message: "Document is in read-only mode."
2238
+ });
2239
+ }
2240
+ if (viewState.documentMode === "viewing") {
2241
+ reasons.push({
2242
+ code: "document_viewing_mode",
2243
+ message: "Document is in viewing mode."
2244
+ });
2245
+ }
2246
+ if (isBlockedByProtection(protectionSnapshot, selection)) {
2247
+ reasons.push({
2248
+ code: "protected_range",
2249
+ message: "Selection falls within a protected range."
2250
+ });
2251
+ }
2252
+ const effectiveDocumentMode = getEffectiveDocumentMode(selection);
2253
+ if (effectiveDocumentMode === "suggesting" && commandType) {
2254
+ if (activeStory.kind !== "main" && SUGGESTING_SECONDARY_STORY_UNSUPPORTED_COMMANDS.has(commandType)) {
2255
+ reasons.push({
2256
+ code: "suggesting_unsupported",
2257
+ message: "Suggesting mode is not yet export-safe in this story."
2258
+ });
2259
+ }
2260
+ if (SUGGESTING_UNSUPPORTED_COMMANDS.has(commandType)) {
2261
+ reasons.push({
2262
+ code: "suggesting_unsupported",
2263
+ message: `"${commandType}" is not supported in suggesting mode.`
2264
+ });
2265
+ }
2266
+ }
2267
+ if (workflowOverlay) {
2268
+ const matchingScope = getMatchingWorkflowScope(selection);
2269
+ if (!matchingScope && workflowOverlay.scopes.length > 0) {
2270
+ reasons.push({
2271
+ code: "outside_workflow_scope",
2272
+ message: "Selection is outside any active workflow scope."
2273
+ });
2274
+ } else if (matchingScope) {
2275
+ if (matchingScope.mode === "comment") {
2276
+ const isCommentCommand = commandType?.startsWith("comment.") ?? false;
2277
+ if (!isCommentCommand) {
2278
+ reasons.push({
2279
+ code: "workflow_comment_only",
2280
+ message: `Scope "${matchingScope.label ?? matchingScope.scopeId}" allows comments only.`,
2281
+ scopeId: matchingScope.scopeId,
2282
+ workItemId: matchingScope.workItemId
2283
+ });
2284
+ }
2285
+ } else if (matchingScope.mode === "view") {
2286
+ reasons.push({
2287
+ code: "workflow_view_only",
2288
+ message: `Scope "${matchingScope.label ?? matchingScope.scopeId}" is view-only.`,
2289
+ scopeId: matchingScope.scopeId,
2290
+ workItemId: matchingScope.workItemId
2291
+ });
2292
+ }
2293
+ }
2294
+ }
2295
+ return reasons;
2296
+ }
2297
+ function getMatchingWorkflowScope(selection) {
2298
+ if (!workflowOverlay) {
2299
+ return null;
2300
+ }
2301
+ const selectionBounds = {
2302
+ from: Math.min(selection.anchor, selection.head),
2303
+ to: Math.max(selection.anchor, selection.head)
2304
+ };
2305
+ const activeScopes = getEffectiveWorkflowScopes(workflowOverlay);
2306
+ return activeScopes.find((scope) => {
2307
+ if (scope.anchor.kind === "detached") return false;
2308
+ const scopeFrom = scope.anchor.kind === "range" ? scope.anchor.from : scope.anchor.at;
2309
+ const scopeTo = scope.anchor.kind === "range" ? scope.anchor.to : scope.anchor.at;
2310
+ return selectionBounds.from >= scopeFrom && selectionBounds.to <= scopeTo;
2311
+ }) ?? null;
2312
+ }
2313
+ function getEffectiveDocumentMode(selection) {
2314
+ if (viewState.documentMode === "viewing") {
2315
+ return "viewing";
2316
+ }
2317
+ const matchingScope = getMatchingWorkflowScope(selection);
2318
+ if (matchingScope?.mode === "suggest") {
2319
+ return "suggesting";
2320
+ }
2321
+ return viewState.documentMode;
2322
+ }
2323
+ function expandSelectionRange(range) {
2324
+ return {
2325
+ from: range.from,
2326
+ to: range.to > range.from ? range.to : range.from + 1
2327
+ };
2328
+ }
2329
+ function deriveOpaqueWorkflowBlockedReason(range) {
2330
+ const targetPartPath = getStoryTargetOpaquePartPath(activeStory);
2331
+ if (!targetPartPath) {
2332
+ return null;
2333
+ }
2334
+ const fragments = findOpaqueFragmentsIntersectingRange(
2335
+ state.document.preservation,
2336
+ range
2337
+ ).filter((fragment2) => fragment2.packagePartName === targetPartPath);
2338
+ if (fragments.length === 0) {
2339
+ return null;
2340
+ }
2341
+ const blockedImportFeatureKeys = /* @__PURE__ */ new Set([
2342
+ "alt-chunk",
2343
+ "alternate-content",
2344
+ "custom-xml"
2345
+ ]);
2346
+ const blockedImportFragment = fragments.find(
2347
+ (fragment2) => blockedImportFeatureKeys.has(describeOpaqueFragment(fragment2).featureKey)
2348
+ ) ?? null;
2349
+ const fragment = blockedImportFragment ?? fragments[0];
2350
+ const descriptor = describeOpaqueFragment(fragment);
2351
+ const isBlockedImport = blockedImportFragment !== null;
2352
+ return {
2353
+ code: isBlockedImport ? "workflow_blocked_import" : "workflow_preserve_only",
2354
+ message: isBlockedImport ? `${descriptor.label} remains a blocked import and cannot be edited.` : `${descriptor.label} remains preserve-only and cannot be edited.`,
2355
+ anchor: toPublicAnchorProjection(
2356
+ createRangeAnchor(fragment.lastKnownRange.from, fragment.lastKnownRange.to, {
2357
+ start: -1,
2358
+ end: 1
2359
+ })
2360
+ ),
2361
+ storyTarget: activeStory
2362
+ };
2363
+ }
2364
+ function getStoryTargetOpaquePartPath(storyTarget) {
2365
+ if (storyTarget.kind === "main") {
2366
+ return "/word/document.xml";
2367
+ }
2368
+ if (storyTarget.kind === "header") {
2369
+ return state.document.subParts?.headers.find(
2370
+ (header) => header.relationshipId === storyTarget.relationshipId && header.variant === storyTarget.variant && header.sectionIndex === storyTarget.sectionIndex
2371
+ )?.partPath ?? null;
2372
+ }
2373
+ if (storyTarget.kind === "footer") {
2374
+ return state.document.subParts?.footers.find(
2375
+ (footer) => footer.relationshipId === storyTarget.relationshipId && footer.variant === storyTarget.variant && footer.sectionIndex === storyTarget.sectionIndex
2376
+ )?.partPath ?? null;
2377
+ }
2378
+ if (storyTarget.kind === "footnote") {
2379
+ return "/word/footnotes.xml";
2380
+ }
2381
+ if (storyTarget.kind === "endnote") {
2382
+ return "/word/endnotes.xml";
2383
+ }
2384
+ return null;
2385
+ }
2386
+ function deriveWorkflowScopeSnapshot() {
2387
+ if (!workflowOverlay) return null;
2388
+ const blockedReasons = getCachedInteractionGuardSnapshot().blockedReasons;
2389
+ const activeItem = workflowOverlay.activeWorkItemId ? workflowOverlay.workItems?.find(
2390
+ (item) => item.workItemId === workflowOverlay.activeWorkItemId
2391
+ ) : void 0;
2392
+ return {
2393
+ overlayPresent: true,
2394
+ activeWorkItemId: workflowOverlay.activeWorkItemId ?? null,
2395
+ activeWorkItem: activeItem,
2396
+ scopes: workflowOverlay.scopes,
2397
+ candidates: workflowOverlay.candidates ?? [],
2398
+ blockedReasons
2399
+ };
2400
+ }
2401
+ function deriveHostAnnotationSnapshot() {
2402
+ return {
2403
+ totalCount: hostAnnotationOverlay?.annotations.length ?? 0,
2404
+ annotations: structuredClone(hostAnnotationOverlay?.annotations ?? [])
2405
+ };
2406
+ }
2407
+ function getEffectiveWorkflowScopes(overlay) {
2408
+ const activeWorkItemId = overlay.activeWorkItemId ?? null;
2409
+ const activeWorkItemScopeIds = activeWorkItemId === null ? null : new Set(
2410
+ overlay.workItems?.find((item) => item.workItemId === activeWorkItemId)?.scopeIds ?? []
2411
+ );
2412
+ return overlay.scopes.filter((scope) => {
2413
+ const scopeStoryTarget = scope.storyTarget ?? MAIN_STORY_TARGET;
2414
+ if (!storyTargetsEqual(scopeStoryTarget, activeStory)) {
2415
+ return false;
2416
+ }
2417
+ if (activeWorkItemId === null) {
2418
+ return true;
2419
+ }
2420
+ return scope.workItemId === activeWorkItemId || activeWorkItemScopeIds?.has(scope.scopeId) === true;
2421
+ });
2422
+ }
2423
+ function getCachedInteractionGuardSnapshot() {
2424
+ const activeStoryKey = storyTargetKey(activeStory);
2425
+ if (cachedInteractionGuardSnapshot && cachedInteractionGuardSnapshot.revisionToken === state.revisionToken && cachedInteractionGuardSnapshot.activeStoryKey === activeStoryKey && cachedInteractionGuardSnapshot.selection === state.selection && cachedInteractionGuardSnapshot.readOnly === state.readOnly && cachedInteractionGuardSnapshot.documentMode === viewState.documentMode && cachedInteractionGuardSnapshot.protectionSnapshot === protectionSnapshot && cachedInteractionGuardSnapshot.workflowOverlay === workflowOverlay) {
2426
+ return cachedInteractionGuardSnapshot.snapshot;
2427
+ }
2428
+ const blockedReasons = evaluateWorkflowBlockedReasons(state.selection);
2429
+ const matchingScope = getMatchingWorkflowScope(state.selection);
2430
+ const primaryBlockedReason = blockedReasons[0];
2431
+ const snapshot = {
2432
+ effectiveMode: primaryBlockedReason ? primaryBlockedReason.code === "workflow_comment_only" ? "comment" : primaryBlockedReason.code === "workflow_view_only" ? "view" : "blocked" : getEffectiveDocumentMode(state.selection) === "suggesting" ? "suggest" : matchingScope?.mode ?? "edit",
2433
+ ...matchingScope?.scopeId ? { matchedScopeId: matchingScope.scopeId } : {},
2434
+ ...matchingScope?.mode ? { matchedScopeMode: matchingScope.mode } : {},
2435
+ ...primaryBlockedReason ? { disabledReason: primaryBlockedReason.message } : {},
2436
+ blockedReasons
2437
+ };
2438
+ cachedInteractionGuardSnapshot = {
2439
+ revisionToken: state.revisionToken,
2440
+ activeStoryKey,
2441
+ selection: state.selection,
2442
+ readOnly: state.readOnly,
2443
+ documentMode: viewState.documentMode,
2444
+ protectionSnapshot,
2445
+ workflowOverlay,
2446
+ snapshot
2447
+ };
2448
+ return snapshot;
2449
+ }
2450
+ function getCachedWorkflowScopeSnapshot() {
2451
+ if (!workflowOverlay) {
2452
+ return null;
2453
+ }
2454
+ const interactionGuardSnapshot = getCachedInteractionGuardSnapshot();
2455
+ if (cachedWorkflowScopeSnapshot && cachedWorkflowScopeSnapshot.workflowOverlay === workflowOverlay && cachedWorkflowScopeSnapshot.interactionGuardSnapshot === interactionGuardSnapshot) {
2456
+ return cachedWorkflowScopeSnapshot.snapshot;
2457
+ }
2458
+ const snapshot = deriveWorkflowScopeSnapshot();
2459
+ cachedWorkflowScopeSnapshot = {
2460
+ workflowOverlay,
2461
+ interactionGuardSnapshot,
2462
+ snapshot
2463
+ };
2464
+ return snapshot;
2465
+ }
2466
+ function getCachedWorkflowMarkupSnapshot() {
2467
+ const activeStoryKey = storyTargetKey(activeStory);
2468
+ if (cachedWorkflowMarkupSnapshot && cachedWorkflowMarkupSnapshot.revisionToken === state.revisionToken && cachedWorkflowMarkupSnapshot.activeStoryKey === activeStoryKey && cachedWorkflowMarkupSnapshot.protectionSnapshot === protectionSnapshot && cachedWorkflowMarkupSnapshot.preservation === state.document.preservation) {
2469
+ return cachedWorkflowMarkupSnapshot.snapshot;
2470
+ }
2471
+ const snapshot = collectWorkflowMarkupSnapshot({
2472
+ renderSnapshot: cachedRenderSnapshot,
2473
+ fieldSnapshot: buildFieldSnapshot(state.document),
2474
+ protectionSnapshot,
2475
+ preservation: state.document.preservation
2476
+ });
2477
+ cachedWorkflowMarkupSnapshot = {
2478
+ revisionToken: state.revisionToken,
2479
+ activeStoryKey,
2480
+ protectionSnapshot,
2481
+ preservation: state.document.preservation,
2482
+ snapshot
2483
+ };
2484
+ return snapshot;
2485
+ }
2486
+ function refreshRenderSnapshot() {
2487
+ const surface = getCachedSurface(state.document, activeStory);
2488
+ return {
2489
+ documentId: state.documentId,
2490
+ sessionId: state.sessionId,
2491
+ sourceLabel: state.sourceLabel,
2492
+ revisionToken: state.revisionToken,
2493
+ isReady: state.phase === "ready",
2494
+ isDirty: state.isDirty,
2495
+ readOnly: state.readOnly,
2496
+ documentMode: viewState.documentMode,
2497
+ selection: toPublicSelectionSnapshot(state.selection, activeStory),
2498
+ activeStory,
2499
+ pageLayout: getCachedPageLayoutSnapshot(state, activeStory) ?? void 0,
2500
+ documentStats: toPublicDocumentStats(state),
2501
+ comments: getCachedCommentSidebarSnapshot(state),
2502
+ trackedChanges: getCachedTrackedChangesSnapshot(state, surface),
2503
+ compatibility: getCachedCompatibilityReport(state),
2504
+ warnings: state.warnings.map((warning) => toPublicWarning(warning)),
2505
+ fatalError: state.fatalError ? toPublicError(state.fatalError) : void 0,
2506
+ commandState: {
2507
+ canUndo: history.past.length > 0,
2508
+ canRedo: history.future.length > 0,
2509
+ readOnly: state.readOnly
2510
+ },
2511
+ surface,
2512
+ protectionSnapshot
2513
+ };
2514
+ }
2515
+ function getCachedViewStateSnapshot() {
2516
+ const activeStoryKey = storyTargetKey(activeStory);
2517
+ const pageLayout = cachedRenderSnapshot.pageLayout;
2518
+ if (cachedViewStateSnapshot && cachedViewStateSnapshot.revisionToken === state.revisionToken && cachedViewStateSnapshot.activeStoryKey === activeStoryKey && cachedViewStateSnapshot.selection === state.selection && cachedViewStateSnapshot.viewStateRef === viewState && cachedViewStateSnapshot.pageLayout === pageLayout) {
2519
+ return cachedViewStateSnapshot.snapshot;
2520
+ }
2521
+ const surface = cachedRenderSnapshot.surface;
2522
+ const mainSurface = activeStory.kind === "main" ? surface : getCachedSurface(state.document, MAIN_STORY_TARGET);
2523
+ const snapshot = createEditorViewStateSnapshot(
2524
+ viewState,
2525
+ activeStory,
2526
+ toPublicSelectionSnapshot(state.selection, activeStory),
2527
+ surface,
2528
+ mainSurface,
2529
+ pageLayout,
2530
+ state.document.numbering
2531
+ );
2532
+ cachedViewStateSnapshot = {
2533
+ revisionToken: state.revisionToken,
2534
+ activeStoryKey,
2535
+ selection: state.selection,
2536
+ viewStateRef: viewState,
2537
+ pageLayout,
2538
+ snapshot
2539
+ };
2540
+ return snapshot;
2541
+ }
2542
+ let cachedRenderSnapshot = refreshRenderSnapshot();
2543
+ emit({
2544
+ type: "ready",
2545
+ documentId: state.documentId,
2546
+ sessionId: state.sessionId,
2547
+ source: options.sourceKind ?? (options.initialSessionState ? "session" : options.initialSnapshot ? "snapshot" : "canonical"),
2548
+ stats: toPublicDocumentStats(state),
2549
+ compatibility: toPublicCompatibilityReport(createDerivedCompatibility(state)),
2550
+ comments: cachedRenderSnapshot.comments,
2551
+ trackedChanges: cachedRenderSnapshot.trackedChanges
2552
+ });
2553
+ if (options.fatalError) {
2554
+ emit({
2555
+ type: "error",
2556
+ documentId: state.documentId,
2557
+ error: options.fatalError
2558
+ });
2559
+ }
2560
+ return {
2561
+ subscribe(listener) {
2562
+ listeners.add(listener);
2563
+ return () => {
2564
+ listeners.delete(listener);
2565
+ };
2566
+ },
2567
+ subscribeToEvents(listener) {
2568
+ eventListeners.add(listener);
2569
+ return () => {
2570
+ eventListeners.delete(listener);
2571
+ };
2572
+ },
2573
+ getRenderSnapshot() {
2574
+ return cachedRenderSnapshot;
2575
+ },
2576
+ getCanonicalDocument() {
2577
+ return state.document;
2578
+ },
2579
+ getSourcePackage() {
2580
+ return state.sourcePackage;
2581
+ },
2582
+ emitBlockedCommand(command, reasons) {
2583
+ emit({
2584
+ type: "command_blocked",
2585
+ documentId: state.documentId,
2586
+ command,
2587
+ reasons
2588
+ });
2589
+ },
2590
+ dispatch(command) {
2591
+ const commandSelection = getCommandSelection(command, state.selection);
2592
+ if (isMutationCommand(command)) {
2593
+ const blockedReasons = evaluateWorkflowBlockedReasons(
2594
+ commandSelection,
2595
+ command.type
2596
+ );
2597
+ if (blockedReasons.length > 0) {
2598
+ emit({
2599
+ type: "command_blocked",
2600
+ documentId: state.documentId,
2601
+ command: command.type,
2602
+ reasons: blockedReasons
2603
+ });
2604
+ return;
2605
+ }
2606
+ }
2607
+ if (command.type === "history.undo") {
2608
+ if (viewState.documentMode === "viewing") return;
2609
+ applyHistory("undo");
2610
+ return;
2611
+ }
2612
+ if (command.type === "history.redo") {
2613
+ if (viewState.documentMode === "viewing") return;
2614
+ applyHistory("redo");
2615
+ return;
2616
+ }
2617
+ try {
2618
+ const transaction = executeEditorCommand(state, command, {
2619
+ timestamp: command.origin?.timestamp ?? clock(),
2620
+ documentMode: getEffectiveDocumentMode(commandSelection),
2621
+ defaultAuthorId: defaultAuthorId ?? void 0
2622
+ });
2623
+ commit(transaction);
2624
+ } catch (error) {
2625
+ emitError(toRuntimeError(error));
2626
+ }
2627
+ },
2628
+ undo() {
2629
+ this.dispatch({
2630
+ type: "history.undo",
2631
+ origin: createOrigin("runtime", clock())
2632
+ });
2633
+ },
2634
+ redo() {
2635
+ this.dispatch({
2636
+ type: "history.redo",
2637
+ origin: createOrigin("runtime", clock())
2638
+ });
2639
+ },
2640
+ focus() {
2641
+ viewState = setFocused(viewState, true);
2642
+ this.dispatch({
2643
+ type: "runtime.focus",
2644
+ focused: true,
2645
+ origin: createOrigin("api", clock())
2646
+ });
2647
+ },
2648
+ blur() {
2649
+ viewState = setFocused(viewState, false);
2650
+ this.dispatch({
2651
+ type: "runtime.focus",
2652
+ focused: false,
2653
+ origin: createOrigin("api", clock())
2654
+ });
2655
+ },
2656
+ setDefaultAuthorId(authorId) {
2657
+ defaultAuthorId = authorId;
2658
+ },
2659
+ replaceText(text, target) {
2660
+ try {
2661
+ const timestamp = clock();
2662
+ applyTextCommandInActiveStory(
2663
+ {
2664
+ type: "text.insert",
2665
+ text,
2666
+ origin: createOrigin("api", timestamp)
2667
+ },
2668
+ {
2669
+ selection: target ? createSelectionFromPublicAnchor(target) : state.selection,
2670
+ blockedCommandName: "replaceText"
2671
+ }
2672
+ );
2673
+ } catch (error) {
2674
+ emitError(toRuntimeError(error));
2675
+ }
2676
+ },
2677
+ applyActiveStoryTextCommand(command) {
2678
+ try {
2679
+ applyTextCommandInActiveStory(command);
2680
+ } catch (error) {
2681
+ emitError(toRuntimeError(error));
2682
+ }
2683
+ },
2684
+ addComment(params) {
2685
+ if (viewState.documentMode === "viewing") {
2686
+ throw new Error("Cannot add comments in viewing mode.");
2687
+ }
2688
+ const commentId = createEntityId("comment", state.document.review.comments, clock());
2689
+ const anchor = params.anchor ? toInternalAnchorProjection(params.anchor) : state.selection.activeRange;
2690
+ const selection = params.anchor ? createSelectionFromPublicAnchor(params.anchor) : state.selection;
2691
+ if (!canCreateDocxCommentAnchor(state.document.content, anchor)) {
2692
+ const message = "DOCX comments must use a non-empty range that stays within a single paragraph.";
2693
+ emitError({
2694
+ errorId: createSessionId("comment-anchor", clock()),
2695
+ code: "validation_failed",
2696
+ isFatal: false,
2697
+ message,
2698
+ source: "runtime"
2699
+ });
2700
+ throw new Error(message);
2701
+ }
2702
+ const authorId = params.authorId ?? defaultAuthorId ?? "unknown";
2703
+ const createdAt = clock();
2704
+ const entries = [
2705
+ {
2706
+ entryId: `${commentId}-entry-1`,
2707
+ authorId,
2708
+ body: params.body ?? "",
2709
+ createdAt
2710
+ }
2711
+ ];
2712
+ const comment = {
2713
+ commentId,
2714
+ anchor,
2715
+ createdAt,
2716
+ createdBy: authorId,
2717
+ authorId,
2718
+ body: params.body ?? "",
2719
+ entries,
2720
+ status: anchor.kind === "detached" ? "detached" : "open",
2721
+ warningIds: [],
2722
+ isResolved: false,
2723
+ metadata: {
2724
+ source: "runtime"
2725
+ }
2726
+ };
2727
+ this.dispatch({
2728
+ type: "comment.add",
2729
+ comment,
2730
+ selection,
2731
+ origin: createOrigin("api", clock())
2732
+ });
2733
+ return commentId;
2734
+ },
2735
+ openComment(commentId) {
2736
+ this.dispatch({
2737
+ type: "comment.open",
2738
+ commentId,
2739
+ origin: createOrigin("api", clock())
2740
+ });
2741
+ },
2742
+ resolveComment(commentId) {
2743
+ this.dispatch({
2744
+ type: "comment.resolve",
2745
+ commentId,
2746
+ resolvedBy: defaultAuthorId ?? "unknown",
2747
+ origin: createOrigin("api", clock())
2748
+ });
2749
+ },
2750
+ reopenComment(commentId) {
2751
+ this.dispatch({
2752
+ type: "comment.reopen",
2753
+ commentId,
2754
+ origin: createOrigin("api", clock())
2755
+ });
2756
+ },
2757
+ addCommentReply(commentId, body, authorId) {
2758
+ this.dispatch({
2759
+ type: "comment.add-reply",
2760
+ commentId,
2761
+ body,
2762
+ authorId: authorId ?? defaultAuthorId,
2763
+ origin: createOrigin("api", clock())
2764
+ });
2765
+ },
2766
+ editCommentBody(commentId, body) {
2767
+ this.dispatch({
2768
+ type: "comment.edit-body",
2769
+ commentId,
2770
+ body,
2771
+ origin: createOrigin("api", clock())
2772
+ });
2773
+ },
2774
+ acceptChange(changeId) {
2775
+ this.dispatch({
2776
+ type: "change.accept",
2777
+ changeId,
2778
+ origin: createOrigin("api", clock())
2779
+ });
2780
+ },
2781
+ rejectChange(changeId) {
2782
+ this.dispatch({
2783
+ type: "change.reject",
2784
+ changeId,
2785
+ origin: createOrigin("api", clock())
2786
+ });
2787
+ },
2788
+ acceptAllChanges() {
2789
+ this.dispatch({
2790
+ type: "change.accept-all",
2791
+ origin: createOrigin("api", clock())
2792
+ });
2793
+ },
2794
+ rejectAllChanges() {
2795
+ this.dispatch({
2796
+ type: "change.reject-all",
2797
+ origin: createOrigin("api", clock())
2798
+ });
2799
+ },
2800
+ openStory(target) {
2801
+ const normalizedTarget = target.kind === "header" || target.kind === "footer" ? normalizeHeaderFooterTarget(
2802
+ state.document,
2803
+ target,
2804
+ cachedRenderSnapshot.pageLayout?.sectionIndex
2805
+ ) ?? target : target;
2806
+ if (storyTargetsEqual(activeStory, normalizedTarget)) {
2807
+ return true;
2808
+ }
2809
+ if (!isValidStoryTarget(state, normalizedTarget)) {
2810
+ return false;
2811
+ }
2812
+ switchActiveStory(normalizedTarget);
2813
+ return true;
2814
+ },
2815
+ closeStory() {
2816
+ if (activeStory.kind === "main") {
2817
+ return;
2818
+ }
2819
+ switchActiveStory(MAIN_STORY_TARGET);
2820
+ },
2821
+ getActiveStory() {
2822
+ return activeStory;
2823
+ },
2824
+ getViewState() {
2825
+ return getCachedViewStateSnapshot();
2826
+ },
2827
+ setViewMode(mode) {
2828
+ viewState = setViewMode(viewState, mode);
2829
+ cachedRenderSnapshot = refreshRenderSnapshot();
2830
+ for (const listener of listeners) {
2831
+ listener();
2832
+ }
2833
+ },
2834
+ setDocumentMode(mode) {
2835
+ viewState = setDocumentMode(viewState, mode);
2836
+ cachedRenderSnapshot = refreshRenderSnapshot();
2837
+ for (const listener of listeners) {
2838
+ listener();
2839
+ }
2840
+ },
2841
+ getProtectionSnapshot() {
2842
+ return cachedRenderSnapshot.protectionSnapshot;
2843
+ },
2844
+ setWorkspaceMode(mode) {
2845
+ viewState = setWorkspaceMode(viewState, mode);
2846
+ cachedRenderSnapshot = refreshRenderSnapshot();
2847
+ for (const listener of listeners) {
2848
+ listener();
2849
+ }
2850
+ },
2851
+ setZoom(level) {
2852
+ viewState = setZoomLevel(viewState, level);
2853
+ cachedRenderSnapshot = refreshRenderSnapshot();
2854
+ for (const listener of listeners) {
2855
+ listener();
2856
+ }
2857
+ },
2858
+ getPageLayoutSnapshot() {
2859
+ return getCachedPageLayoutSnapshot(state, activeStory);
2860
+ },
2861
+ getDocumentNavigationSnapshot() {
2862
+ return getCachedDocumentNavigationSnapshot(state, activeStory);
2863
+ },
2864
+ getFieldSnapshot() {
2865
+ return buildFieldSnapshot(state.document);
2866
+ },
2867
+ updateFields(options2) {
2868
+ const refreshed = refreshDocumentFields(
2869
+ state.document,
2870
+ state.selection.head,
2871
+ activeStory,
2872
+ options2
2873
+ );
2874
+ if (refreshed.changed) {
2875
+ this.dispatch({
2876
+ type: "document.replace",
2877
+ document: refreshed.document,
2878
+ mapping: createEmptyMapping(),
2879
+ protectionSelection: refreshed.protectionSelection,
2880
+ origin: createOrigin("api", clock())
2881
+ });
2882
+ }
2883
+ const snapshot = buildFieldSnapshot(refreshed.document);
2884
+ return {
2885
+ totalCount: snapshot.totalCount,
2886
+ updatedCount: refreshed.updatedCount,
2887
+ preserveOnlyCount: snapshot.preserveOnlyCount
2888
+ };
2889
+ },
2890
+ updateTableOfContents(options2) {
2891
+ const refreshed = refreshDocumentTableOfContents(
2892
+ state.document,
2893
+ state.selection.head,
2894
+ activeStory,
2895
+ options2
2896
+ );
2897
+ if (refreshed.changed) {
2898
+ this.dispatch({
2899
+ type: "document.replace",
2900
+ document: refreshed.document,
2901
+ mapping: createEmptyMapping(),
2902
+ protectionSelection: refreshed.protectionSelection,
2903
+ origin: createOrigin("api", clock())
2904
+ });
2905
+ }
2906
+ return refreshed.result;
2907
+ },
2908
+ getSessionState() {
2909
+ const compatibility = createDerivedCompatibility(state);
2910
+ return editorSessionStateFromPersistedSnapshot(
2911
+ createPersistedEditorSnapshot(state, {
2912
+ editorBuild,
2913
+ savedAt: clock(),
2914
+ compatibility,
2915
+ protectionSnapshot
2916
+ })
2917
+ );
2918
+ },
2919
+ getPersistedSnapshot() {
2920
+ return persistedSnapshotFromEditorSessionState(this.getSessionState(), {
2921
+ savedAt: clock()
2922
+ });
2923
+ },
2924
+ getCompatibilityReport() {
2925
+ return toPublicCompatibilityReport(createDerivedCompatibility(state));
2926
+ },
2927
+ getWarnings() {
2928
+ return state.warnings.map((warning) => toPublicWarning(warning));
2929
+ },
2930
+ async exportDocx(exportOptions) {
2931
+ if (!options.exportDocx) {
2932
+ const error = {
2933
+ errorId: createEntityId("error", {}, clock()),
2934
+ code: "export_failed",
2935
+ isFatal: false,
2936
+ message: "DOCX export requires an injected exporter until the IO substrate lands.",
2937
+ source: "export",
2938
+ details: {
2939
+ requestedOptions: exportOptions ?? {}
2940
+ }
2941
+ };
2942
+ emitError(error);
2943
+ throw new Error(error.message);
2944
+ }
2945
+ const result = await options.exportDocx(this.getSessionState(), exportOptions);
2946
+ emit({
2947
+ type: "export_completed",
2948
+ documentId: state.documentId,
2949
+ result
2950
+ });
2951
+ return result;
2952
+ },
2953
+ setWorkflowOverlay(overlay) {
2954
+ workflowOverlay = structuredClone(overlay);
2955
+ cachedRenderSnapshot = refreshRenderSnapshot();
2956
+ const snapshot = deriveWorkflowScopeSnapshot();
2957
+ emit({
2958
+ type: "workflow_overlay_changed",
2959
+ documentId: state.documentId,
2960
+ snapshot
2961
+ });
2962
+ if (workflowOverlay.activeWorkItemId !== void 0) {
2963
+ emit({
2964
+ type: "workflow_active_work_item_changed",
2965
+ documentId: state.documentId,
2966
+ activeWorkItemId: workflowOverlay.activeWorkItemId ?? null
2967
+ });
2968
+ }
2969
+ for (const listener of listeners) {
2970
+ listener();
2971
+ }
2972
+ },
2973
+ clearWorkflowOverlay() {
2974
+ workflowOverlay = null;
2975
+ cachedRenderSnapshot = refreshRenderSnapshot();
2976
+ emit({
2977
+ type: "workflow_active_work_item_changed",
2978
+ documentId: state.documentId,
2979
+ activeWorkItemId: null
2980
+ });
2981
+ emit({
2982
+ type: "workflow_overlay_changed",
2983
+ documentId: state.documentId,
2984
+ snapshot: {
2985
+ overlayPresent: false,
2986
+ activeWorkItemId: null,
2987
+ scopes: [],
2988
+ candidates: [],
2989
+ blockedReasons: []
2990
+ }
2991
+ });
2992
+ for (const listener of listeners) {
2993
+ listener();
2994
+ }
2995
+ },
2996
+ getWorkflowScopeSnapshot() {
2997
+ return getCachedWorkflowScopeSnapshot();
2998
+ },
2999
+ getInteractionGuardSnapshot() {
3000
+ return getCachedInteractionGuardSnapshot();
3001
+ },
3002
+ getWorkflowMarkupSnapshot() {
3003
+ return getCachedWorkflowMarkupSnapshot();
3004
+ },
3005
+ setHostAnnotationOverlay(overlay) {
3006
+ hostAnnotationOverlay = structuredClone(overlay);
3007
+ emit({
3008
+ type: "host_annotation_overlay_changed",
3009
+ documentId: state.documentId,
3010
+ snapshot: deriveHostAnnotationSnapshot()
3011
+ });
3012
+ for (const listener of listeners) {
3013
+ listener();
3014
+ }
3015
+ },
3016
+ clearHostAnnotationOverlay() {
3017
+ hostAnnotationOverlay = null;
3018
+ emit({
3019
+ type: "host_annotation_overlay_changed",
3020
+ documentId: state.documentId,
3021
+ snapshot: deriveHostAnnotationSnapshot()
3022
+ });
3023
+ for (const listener of listeners) {
3024
+ listener();
3025
+ }
3026
+ },
3027
+ getHostAnnotationSnapshot() {
3028
+ return deriveHostAnnotationSnapshot();
3029
+ },
3030
+ getWorkflowCandidateRanges(options2) {
3031
+ return deriveWorkflowCandidateRangesFromMarkup(this.getWorkflowMarkupSnapshot(), options2);
3032
+ },
3033
+ replaceWorkflowMarkupText(markupId, text) {
3034
+ const target = this.getWorkflowMarkupSnapshot().items.find((item) => item.markupId === markupId);
3035
+ if (!target || target.anchor.kind === "detached") {
3036
+ return;
3037
+ }
3038
+ const targetStory = target.storyTarget ?? MAIN_STORY_TARGET;
3039
+ if (!storyTargetsEqual(activeStory, targetStory)) {
3040
+ if (targetStory.kind === "main") {
3041
+ this.closeStory();
3042
+ } else if (!this.openStory(targetStory)) {
3043
+ return;
3044
+ }
3045
+ }
3046
+ this.replaceText(text, target.anchor);
3047
+ }
3048
+ };
3049
+ function applyHistory(direction) {
3050
+ const source = direction === "undo" ? history.past : history.future;
3051
+ const target = source.pop();
3052
+ if (!target) {
3053
+ return;
3054
+ }
3055
+ const counterpart = direction === "undo" ? history.future : history.past;
3056
+ counterpart.push(state);
3057
+ const previous = state;
3058
+ state = finalizeState(target, true, clock(), previous.revision);
3059
+ storySelections.set(storyTargetKey(activeStory), state.selection);
3060
+ cachedRenderSnapshot = refreshRenderSnapshot();
3061
+ notify(previous, state, {
3062
+ nextState: state,
3063
+ mapping: { steps: [] },
3064
+ effects: {
3065
+ warningsAdded: [],
3066
+ warningsCleared: []
3067
+ },
3068
+ historyBoundary: "skip",
3069
+ markDirty: true
3070
+ });
3071
+ }
3072
+ function commit(transaction) {
3073
+ const previous = state;
3074
+ if (transaction.historyBoundary === "push") {
3075
+ history.past.push(state);
3076
+ history.future = [];
3077
+ }
3078
+ protectionSnapshot = remapProtectionSnapshot(protectionSnapshot, transaction.mapping);
3079
+ state = finalizeState(transaction.nextState, transaction.markDirty, clock());
3080
+ storySelections.set(storyTargetKey(activeStory), state.selection);
3081
+ cachedRenderSnapshot = refreshRenderSnapshot();
3082
+ notify(previous, state, transaction);
3083
+ }
3084
+ function notify(previous, next, transaction) {
3085
+ if (previous.isDirty !== next.isDirty) {
3086
+ emit({
3087
+ type: "dirty_changed",
3088
+ documentId: next.documentId,
3089
+ isDirty: next.isDirty
3090
+ });
3091
+ }
3092
+ if (selectionChanged(previous.selection, next.selection)) {
3093
+ emit({
3094
+ type: "selection_changed",
3095
+ documentId: next.documentId,
3096
+ selection: toPublicSelectionSnapshot(next.selection, activeStory)
3097
+ });
3098
+ }
3099
+ if (transaction.effects.commentAdded) {
3100
+ emit({
3101
+ type: "comment_added",
3102
+ documentId: next.documentId,
3103
+ commentId: transaction.effects.commentAdded.commentId,
3104
+ anchor: toPublicAnchorProjection(transaction.effects.commentAdded.anchor)
3105
+ });
3106
+ }
3107
+ if (transaction.effects.commentResolved) {
3108
+ emit({
3109
+ type: "comment_resolved",
3110
+ documentId: next.documentId,
3111
+ commentId: transaction.effects.commentResolved.commentId
3112
+ });
3113
+ }
3114
+ if (transaction.effects.changeAccepted) {
3115
+ emit({
3116
+ type: "change_accepted",
3117
+ documentId: next.documentId,
3118
+ changeId: transaction.effects.changeAccepted.changeId
3119
+ });
3120
+ }
3121
+ if (transaction.effects.changeRejected) {
3122
+ emit({
3123
+ type: "change_rejected",
3124
+ documentId: next.documentId,
3125
+ changeId: transaction.effects.changeRejected.changeId
3126
+ });
3127
+ }
3128
+ if (transaction.effects.revisionAuthored) {
3129
+ emit({
3130
+ type: "change_authored",
3131
+ documentId: next.documentId,
3132
+ changeId: transaction.effects.revisionAuthored.changeId,
3133
+ kind: transaction.effects.revisionAuthored.kind
3134
+ });
3135
+ }
3136
+ if (transaction.effects.commandBlocked) {
3137
+ emit({
3138
+ type: "command_blocked",
3139
+ documentId: next.documentId,
3140
+ command: transaction.effects.commandBlocked.code,
3141
+ reasons: [{
3142
+ code: transaction.effects.commandBlocked.code,
3143
+ message: transaction.effects.commandBlocked.message
3144
+ }]
3145
+ });
3146
+ }
3147
+ for (const warning of transaction.effects.warningsAdded) {
3148
+ const publicWarning = toPublicWarning(warning);
3149
+ emit({
3150
+ type: "warning_added",
3151
+ documentId: next.documentId,
3152
+ warning: publicWarning
3153
+ });
3154
+ options.onWarning?.(publicWarning);
3155
+ }
3156
+ for (const cleared of transaction.effects.warningsCleared) {
3157
+ emit({
3158
+ type: "warning_cleared",
3159
+ documentId: next.documentId,
3160
+ warningId: cleared.warningId,
3161
+ code: cleared.code
3162
+ });
3163
+ }
3164
+ for (const listener of listeners) {
3165
+ listener();
3166
+ }
3167
+ }
3168
+ function applyTextCommandInActiveStory(command, options2 = {}) {
3169
+ const selection = options2.selection ?? state.selection;
3170
+ const blockedReasons = evaluateWorkflowBlockedReasons(selection, command.type);
3171
+ if (blockedReasons.length > 0) {
3172
+ emit({
3173
+ type: "command_blocked",
3174
+ documentId: state.documentId,
3175
+ command: options2.blockedCommandName ?? command.type,
3176
+ reasons: blockedReasons
3177
+ });
3178
+ return;
3179
+ }
3180
+ const timestamp = command.origin?.timestamp ?? clock();
3181
+ const context = {
3182
+ timestamp,
3183
+ documentMode: getEffectiveDocumentMode(selection),
3184
+ defaultAuthorId: defaultAuthorId ?? void 0
3185
+ };
3186
+ const baseState = selection === state.selection ? state : {
3187
+ ...state,
3188
+ selection
3189
+ };
3190
+ if (activeStory.kind === "main") {
3191
+ commit(executeEditorCommand(baseState, command, context));
3192
+ return;
3193
+ }
3194
+ const localState = createEditorState({
3195
+ documentId: state.documentId,
3196
+ sessionId,
3197
+ sourceLabel: state.sourceLabel,
3198
+ readOnly: state.readOnly,
3199
+ canonicalDocument: {
3200
+ ...state.document,
3201
+ content: {
3202
+ type: "doc",
3203
+ children: [...getStoryBlocks(state.document, activeStory)]
3204
+ },
3205
+ review: createSecondaryStoryLocalReviewState(state.document.review, activeStory)
3206
+ },
3207
+ compatibility: state.compatibility,
3208
+ warnings: state.warnings,
3209
+ fatalError: state.fatalError
3210
+ });
3211
+ localState.selection = selection;
3212
+ const localTransaction = executeEditorCommand(localState, command, context);
3213
+ if (!localTransaction.markDirty) {
3214
+ notify(state, state, {
3215
+ nextState: state,
3216
+ mapping: createEmptyMapping(),
3217
+ effects: localTransaction.effects,
3218
+ historyBoundary: "skip",
3219
+ markDirty: false
3220
+ });
3221
+ return;
3222
+ }
3223
+ const nextDocument = replaceStoryBlocks(
3224
+ state.document,
3225
+ activeStory,
3226
+ localTransaction.nextState.document.content.children
3227
+ );
3228
+ const nextDocumentWithReview = {
3229
+ ...nextDocument,
3230
+ review: mergeSecondaryStoryReviewState(
3231
+ state.document.review,
3232
+ localTransaction.nextState.document.review,
3233
+ localTransaction.effects,
3234
+ activeStory
3235
+ )
3236
+ };
3237
+ const fullTransaction = executeEditorCommand(
3238
+ baseState,
3239
+ {
3240
+ type: "document.replace",
3241
+ document: nextDocumentWithReview,
3242
+ selection: localTransaction.nextState.selection,
3243
+ mapping: createEmptyMapping(),
3244
+ protectionSelection: selection,
3245
+ origin: command.origin
3246
+ },
3247
+ context
3248
+ );
3249
+ commit({
3250
+ ...fullTransaction,
3251
+ effects: mergeTransactionEffects(fullTransaction.effects, localTransaction.effects)
3252
+ });
3253
+ }
3254
+ function mergeTransactionEffects(base, local) {
3255
+ return {
3256
+ warningsAdded: [...base.warningsAdded, ...local.warningsAdded],
3257
+ warningsCleared: [...base.warningsCleared, ...local.warningsCleared],
3258
+ commentAdded: base.commentAdded ?? local.commentAdded,
3259
+ commentResolved: base.commentResolved ?? local.commentResolved,
3260
+ commentReopened: base.commentReopened ?? local.commentReopened,
3261
+ commentReplyAdded: base.commentReplyAdded ?? local.commentReplyAdded,
3262
+ commentBodyEdited: base.commentBodyEdited ?? local.commentBodyEdited,
3263
+ changeAccepted: base.changeAccepted ?? local.changeAccepted,
3264
+ changeRejected: base.changeRejected ?? local.changeRejected,
3265
+ revisionAuthored: base.revisionAuthored ?? local.revisionAuthored,
3266
+ commandBlocked: base.commandBlocked ?? local.commandBlocked
3267
+ };
3268
+ }
3269
+ function mergeSecondaryStoryReviewState(currentReview, localReview, effects, storyTarget) {
3270
+ const nextReview = {
3271
+ comments: { ...currentReview.comments },
3272
+ revisions: { ...currentReview.revisions }
3273
+ };
3274
+ const currentStoryRevisionIds = Object.values(currentReview.revisions).filter((revision) => storyTargetsEqual(getRevisionStoryTarget(revision), storyTarget)).map((revision) => revision.changeId);
3275
+ for (const revisionId of currentStoryRevisionIds) {
3276
+ delete nextReview.revisions[revisionId];
3277
+ }
3278
+ for (const revision of Object.values(localReview.revisions)) {
3279
+ nextReview.revisions[revision.changeId] = {
3280
+ ...revision,
3281
+ metadata: {
3282
+ ...revision.metadata,
3283
+ storyTarget: createRevisionStoryTargetRecord(storyTarget)
3284
+ }
3285
+ };
3286
+ }
3287
+ if (effects.commentAdded) {
3288
+ const commentId = effects.commentAdded.commentId;
3289
+ const comment = localReview.comments[commentId];
3290
+ if (comment) {
3291
+ nextReview.comments[commentId] = comment;
3292
+ }
3293
+ }
3294
+ return nextReview;
3295
+ }
3296
+ function emit(event) {
3297
+ options.onEvent?.(event);
3298
+ for (const listener of eventListeners) {
3299
+ listener(event);
3300
+ }
3301
+ }
3302
+ function emitError(error) {
3303
+ const nextState = {
3304
+ ...state,
3305
+ phase: error.isFatal ? "error" : state.phase,
3306
+ fatalError: error.isFatal ? error : state.fatalError
3307
+ };
3308
+ state = nextState;
3309
+ storySelections.set(storyTargetKey(activeStory), state.selection);
3310
+ cachedRenderSnapshot = refreshRenderSnapshot();
3311
+ const publicError = toPublicError(error);
3312
+ options.onError?.(publicError);
3313
+ emit({
3314
+ type: "error",
3315
+ documentId: state.documentId,
3316
+ error: publicError
3317
+ });
3318
+ for (const listener of listeners) {
3319
+ listener();
3320
+ }
3321
+ }
3322
+ function switchActiveStory(target) {
3323
+ const previousStory = activeStory;
3324
+ const previousSelection = state.selection;
3325
+ storySelections.set(storyTargetKey(previousStory), previousSelection);
3326
+ const restoredSelection = storySelections.get(storyTargetKey(target)) ?? createSelectionSnapshot(0, 0);
3327
+ activeStory = target;
3328
+ state = {
3329
+ ...state,
3330
+ selection: restoredSelection
3331
+ };
3332
+ storySelections.set(storyTargetKey(target), restoredSelection);
3333
+ cachedRenderSnapshot = refreshRenderSnapshot();
3334
+ if (selectionChanged(previousSelection, restoredSelection)) {
3335
+ emit({
3336
+ type: "selection_changed",
3337
+ documentId: state.documentId,
3338
+ selection: toPublicSelectionSnapshot(restoredSelection, activeStory)
3339
+ });
3340
+ }
3341
+ emit({
3342
+ type: "story_changed",
3343
+ documentId: state.documentId,
3344
+ activeStory
3345
+ });
3346
+ for (const listener of listeners) {
3347
+ listener();
3348
+ }
3349
+ }
3350
+ }
3351
+ function createSessionId(documentId, timestamp) {
3352
+ return `session-${documentId}-${timestamp.replace(/[^0-9]/gu, "")}`;
3353
+ }
3354
+ function createOrigin(source, timestamp) {
3355
+ return {
3356
+ source,
3357
+ timestamp
3358
+ };
3359
+ }
3360
+ function createEntityId(prefix, existing, timestamp) {
3361
+ let counter = Object.keys(existing).length;
3362
+ let nextId = `${prefix}-${timestamp.replace(/[^0-9]/gu, "")}-${counter}`;
3363
+ while (existing[nextId]) {
3364
+ counter += 1;
3365
+ nextId = `${prefix}-${timestamp.replace(/[^0-9]/gu, "")}-${counter}`;
3366
+ }
3367
+ return nextId;
3368
+ }
3369
+ function finalizeState(state, markDirty, timestamp, baseRevision) {
3370
+ const revision = markDirty ? (baseRevision ?? state.revision) + 1 : state.revision;
3371
+ return {
3372
+ ...state,
3373
+ document: {
3374
+ ...state.document,
3375
+ updatedAt: markDirty ? timestamp : state.document.updatedAt
3376
+ },
3377
+ selection: state.selection,
3378
+ revision,
3379
+ revisionToken: `${state.sessionId}:${revision}`,
3380
+ isDirty: state.isDirty || markDirty
3381
+ };
3382
+ }
3383
+ function toRuntimeError(error) {
3384
+ if (typeof error === "object" && error && "message" in error) {
3385
+ return {
3386
+ errorId: createSessionId("runtime-error", (/* @__PURE__ */ new Date()).toISOString()),
3387
+ code: "internal_invariant",
3388
+ isFatal: false,
3389
+ message: String(error.message ?? "Runtime error"),
3390
+ source: "runtime"
3391
+ };
3392
+ }
3393
+ return {
3394
+ errorId: createSessionId("runtime-error", (/* @__PURE__ */ new Date()).toISOString()),
3395
+ code: "internal_invariant",
3396
+ isFatal: false,
3397
+ message: "Runtime error",
3398
+ source: "runtime"
3399
+ };
3400
+ }
3401
+ function toPublicDocumentStats(state) {
3402
+ const stats = deriveDocumentStats(state);
3403
+ return {
3404
+ storyLength: stats.characterCount,
3405
+ commentCount: stats.commentCount,
3406
+ revisionCount: stats.revisionCount,
3407
+ opaqueFragmentCount: countOpaqueFragments(state.document.preservation.opaqueFragments)
3408
+ };
3409
+ }
3410
+ function toPublicSelectionSnapshot(selection, storyTarget) {
3411
+ return {
3412
+ anchor: selection.anchor,
3413
+ head: selection.head,
3414
+ isCollapsed: selection.isCollapsed,
3415
+ activeRange: toPublicAnchorProjection(selection.activeRange),
3416
+ ...storyTarget && storyTarget.kind !== "main" ? { storyTarget } : {}
3417
+ };
3418
+ }
3419
+ function toPublicAnchorProjection(anchor) {
3420
+ switch (anchor.kind) {
3421
+ case "range":
3422
+ return {
3423
+ kind: "range",
3424
+ from: anchor.range.from,
3425
+ to: anchor.range.to,
3426
+ assoc: anchor.assoc
3427
+ };
3428
+ case "node":
3429
+ return {
3430
+ kind: "node",
3431
+ at: anchor.at,
3432
+ assoc: anchor.assoc
3433
+ };
3434
+ case "detached":
3435
+ return {
3436
+ kind: "detached",
3437
+ lastKnownRange: anchor.lastKnownRange,
3438
+ reason: anchor.reason
3439
+ };
3440
+ }
3441
+ }
3442
+ function toInternalAnchorProjection(anchor) {
3443
+ switch (anchor.kind) {
3444
+ case "range":
3445
+ return createRangeAnchor(anchor.from, anchor.to, anchor.assoc);
3446
+ case "node":
3447
+ return createNodeAnchor(anchor.at, anchor.assoc);
3448
+ case "detached":
3449
+ return createDetachedAnchor(anchor.lastKnownRange, anchor.reason);
3450
+ }
3451
+ }
3452
+ function createSelectionFromPublicAnchor(anchor) {
3453
+ switch (anchor.kind) {
3454
+ case "range":
3455
+ return createSelectionSnapshot(anchor.from, anchor.to);
3456
+ case "node":
3457
+ return createSelectionSnapshot(anchor.at, anchor.at);
3458
+ case "detached":
3459
+ return createSelectionSnapshot(
3460
+ anchor.lastKnownRange.from,
3461
+ anchor.lastKnownRange.to
3462
+ );
3463
+ }
3464
+ }
3465
+ function toPublicCompatibilityReport(report) {
3466
+ return {
3467
+ reportVersion: report.reportVersion,
3468
+ generatedAt: report.generatedAt,
3469
+ blockExport: report.blockExport,
3470
+ featureEntries: report.featureEntries.map(
3471
+ (entry) => toPublicCompatibilityFeatureEntry(entry)
3472
+ ),
3473
+ warnings: report.warnings.map((warning) => toPublicWarning(warning)),
3474
+ errors: report.errors.map((error) => toPublicError(error))
3475
+ };
3476
+ }
3477
+ function toPublicCompatibilityFeatureEntry(entry) {
3478
+ return {
3479
+ ...entry,
3480
+ affectedAnchor: entry.affectedAnchor ? toPublicAnchorProjection(entry.affectedAnchor) : void 0
3481
+ };
3482
+ }
3483
+ function toPublicWarning(warning) {
3484
+ return {
3485
+ ...warning,
3486
+ affectedAnchor: warning.affectedAnchor ? toPublicAnchorProjection(warning.affectedAnchor) : void 0
3487
+ };
3488
+ }
3489
+ function toPublicError(error) {
3490
+ return { ...error };
3491
+ }
3492
+ function countOpaqueFragments(opaqueFragments) {
3493
+ return Object.keys(opaqueFragments).length;
3494
+ }
3495
+ function createDerivedCompatibility(state) {
3496
+ const derived = buildCompatibilityReport({
3497
+ document: state.document,
3498
+ warnings: state.warnings,
3499
+ fatalError: state.fatalError,
3500
+ generatedAt: state.document.updatedAt
3501
+ });
3502
+ return mergeCompatibilityReports([state.compatibility, derived], {
3503
+ generatedAt: state.document.updatedAt,
3504
+ blockExport: state.compatibility.blockExport || derived.blockExport
3505
+ });
3506
+ }
3507
+ function toPublicCommentSidebarSnapshot(state) {
3508
+ const projection = createCommentSidebarProjection(
3509
+ createCommentStoreFromRuntimeComments(state.document.review.comments),
3510
+ state.runtime.activeCommentId
3511
+ );
3512
+ return {
3513
+ activeCommentId: state.runtime.activeCommentId,
3514
+ openCommentIds: projection.openCommentIds,
3515
+ resolvedCommentIds: projection.resolvedCommentIds,
3516
+ detachedCommentIds: projection.detachedCommentIds,
3517
+ totalCount: projection.totalCount,
3518
+ threads: projection.threads.map((thread) => {
3519
+ const sourceThread = state.document.review.comments[thread.commentId];
3520
+ const projectedEntries = sourceThread?.entries?.map((entry) => ({
3521
+ entryId: entry.entryId,
3522
+ authorId: entry.authorId,
3523
+ body: entry.body,
3524
+ createdAt: entry.createdAt
3525
+ })) ?? (sourceThread?.body ? [
3526
+ {
3527
+ entryId: `${thread.commentId}-entry-1`,
3528
+ authorId: sourceThread.authorId ?? sourceThread.createdBy ?? "unknown",
3529
+ body: sourceThread.body,
3530
+ createdAt: sourceThread.createdAt
3531
+ }
3532
+ ] : []);
3533
+ return {
3534
+ commentId: thread.commentId,
3535
+ status: thread.status,
3536
+ anchor: toPublicAnchorProjection(
3537
+ sourceThread?.anchor ?? createDetachedAnchor({ from: 0, to: 0 }, "importAmbiguity")
3538
+ ),
3539
+ excerpt: thread.excerpt,
3540
+ entryCount: thread.entryCount,
3541
+ createdAt: thread.createdAt,
3542
+ createdBy: thread.createdBy,
3543
+ warningCount: thread.warningCount,
3544
+ anchorLabel: thread.anchorLabel,
3545
+ isActive: thread.isActive,
3546
+ resolvedAt: thread.resolvedAt,
3547
+ resolvedBy: thread.resolvedBy,
3548
+ entries: projectedEntries
3549
+ };
3550
+ })
3551
+ };
3552
+ }
3553
+ function toPublicTrackedChangesSnapshot(state) {
3554
+ const projection = createRevisionSidebarProjection(
3555
+ createRevisionStoreFromDocument(state)
3556
+ );
3557
+ const storyPlainTextCache = /* @__PURE__ */ new Map();
3558
+ return {
3559
+ pendingChangeIds: projection.activeRevisionIds,
3560
+ acceptedChangeIds: projection.acceptedRevisionIds,
3561
+ rejectedChangeIds: projection.rejectedRevisionIds,
3562
+ detachedChangeIds: projection.detachedRevisionIds,
3563
+ actionableChangeIds: projection.actionableRevisionIds,
3564
+ preserveOnlyChangeIds: projection.preserveOnlyRevisionIds,
3565
+ totalCount: projection.totalCount,
3566
+ revisions: projection.revisions.map((revision) => {
3567
+ const sourceRevision = state.document.review.revisions[revision.revisionId];
3568
+ const storyTarget = getRevisionStoryTarget(sourceRevision);
3569
+ const preview = describeRevisionPreview(
3570
+ revision,
3571
+ sourceRevision?.anchor ?? createDetachedAnchor({ from: 0, to: 0 }, "importAmbiguity"),
3572
+ getStoryPlainText(state.document, storyTarget, storyPlainTextCache)
3573
+ );
3574
+ return {
3575
+ revisionId: revision.revisionId,
3576
+ kind: revision.kind,
3577
+ label: revision.label,
3578
+ status: revision.status,
3579
+ actionability: revision.actionability,
3580
+ storyTarget,
3581
+ anchor: toPublicAnchorProjection(
3582
+ sourceRevision?.anchor ?? createDetachedAnchor({ from: 0, to: 0 }, "importAmbiguity")
3583
+ ),
3584
+ anchorLabel: revision.anchorLabel,
3585
+ createdAt: revision.createdAt,
3586
+ authorId: revision.authorId,
3587
+ warningCount: revision.warningCount,
3588
+ canAccept: revision.canAccept,
3589
+ canReject: revision.canReject,
3590
+ importedRevisionForm: sourceRevision?.metadata?.importedRevisionForm,
3591
+ preserveOnlyReason: revision.preserveOnlyReason,
3592
+ excerpt: preview.excerpt,
3593
+ detail: preview.detail
3594
+ };
3595
+ })
3596
+ };
3597
+ }
3598
+ function createRevisionStoreFromDocument(state) {
3599
+ return {
3600
+ version: "revision-store/1",
3601
+ revisions: Object.fromEntries(
3602
+ Object.values(state.document.review.revisions).map((revision) => [
3603
+ revision.changeId,
3604
+ {
3605
+ revisionId: revision.changeId,
3606
+ kind: revision.kind,
3607
+ anchor: revision.anchor,
3608
+ authorId: revision.authorId ?? "unknown",
3609
+ createdAt: revision.createdAt,
3610
+ status: revision.status === "open" ? "active" : revision.status,
3611
+ warningIds: [...revision.warningIds ?? []],
3612
+ metadata: {
3613
+ source: revision.metadata?.source ?? "runtime",
3614
+ storyTarget: revision.metadata?.storyTarget,
3615
+ preserveOnlyReason: revision.metadata?.preserveOnlyReason,
3616
+ importedRevisionForm: revision.metadata?.importedRevisionForm,
3617
+ originalRevisionType: revision.metadata?.originalRevisionType,
3618
+ ooxmlRevisionId: revision.metadata?.ooxmlRevisionId
3619
+ }
3620
+ }
3621
+ ])
3622
+ )
3623
+ };
3624
+ }
3625
+ function getRevisionStoryTarget(revision) {
3626
+ const storyTarget = revision?.metadata?.storyTarget;
3627
+ return storyTarget ? { ...storyTarget } : MAIN_STORY_TARGET;
3628
+ }
3629
+ function createSecondaryStoryLocalReviewState(review, storyTarget) {
3630
+ return {
3631
+ comments: {},
3632
+ revisions: Object.fromEntries(
3633
+ Object.values(review.revisions).filter((revision) => storyTargetsEqual(getRevisionStoryTarget(revision), storyTarget)).map((revision) => [
3634
+ revision.changeId,
3635
+ {
3636
+ ...revision,
3637
+ metadata: {
3638
+ ...revision.metadata,
3639
+ storyTarget: createRevisionStoryTargetRecord(storyTarget)
3640
+ }
3641
+ }
3642
+ ])
3643
+ )
3644
+ };
3645
+ }
3646
+ function getStoryPlainText(document, storyTarget, cache) {
3647
+ const key = storyTargetKey(storyTarget);
3648
+ const cached = cache.get(key);
3649
+ if (cached !== void 0) {
3650
+ return cached;
3651
+ }
3652
+ const plainText = createEditorSurfaceSnapshot(
3653
+ document,
3654
+ createSelectionSnapshot(0, 0),
3655
+ storyTarget
3656
+ ).plainText;
3657
+ cache.set(key, plainText);
3658
+ return plainText;
3659
+ }
3660
+ function createRevisionStoryTargetRecord(storyTarget) {
3661
+ return { ...storyTarget };
3662
+ }
3663
+ function listBlockExportReasons(report) {
3664
+ return [
3665
+ ...report.featureEntries.filter((entry) => entry.featureClass === "unsupported-fatal").map((entry) => entry.message),
3666
+ ...report.errors.filter((error) => error.isFatal).map((error) => error.message)
3667
+ ];
3668
+ }
3669
+ function describeRevisionPreview(revision, anchor, plainText) {
3670
+ const { from, to } = toAnchorBounds(anchor);
3671
+ const excerpt = summarizeRevisionExcerpt(plainText, from, to, revision.label);
3672
+ if (revision.actionability === "preserve-only") {
3673
+ return {
3674
+ excerpt,
3675
+ detail: revision.preserveOnlyReason ?? "Visible for review, but this change remains preserve-only in the current runtime."
3676
+ };
3677
+ }
3678
+ if (revision.status === "accepted") {
3679
+ return {
3680
+ excerpt,
3681
+ detail: "Accepted in the live review runtime and retained here for audit visibility."
3682
+ };
3683
+ }
3684
+ if (revision.status === "rejected") {
3685
+ return {
3686
+ excerpt,
3687
+ detail: "Rejected in the live review runtime and retained here for audit visibility."
3688
+ };
3689
+ }
3690
+ return {
3691
+ excerpt,
3692
+ detail: revision.kind === "deletion" ? "Deleted content stays reviewable here until it is accepted or rejected." : "Runtime-backed change. Review it here or reopen the anchor in the canvas."
3693
+ };
3694
+ }
3695
+ function toAnchorBounds(anchor) {
3696
+ switch (anchor.kind) {
3697
+ case "range":
3698
+ return {
3699
+ from: Math.min(anchor.range.from, anchor.range.to),
3700
+ to: Math.max(anchor.range.from, anchor.range.to)
3701
+ };
3702
+ case "node":
3703
+ return {
3704
+ from: anchor.at,
3705
+ to: anchor.at + 1
3706
+ };
3707
+ case "detached":
3708
+ return {
3709
+ from: Math.min(anchor.lastKnownRange.from, anchor.lastKnownRange.to),
3710
+ to: Math.max(anchor.lastKnownRange.from, anchor.lastKnownRange.to)
3711
+ };
3712
+ }
3713
+ }
3714
+ function summarizeRevisionExcerpt(plainText, from, to, fallback) {
3715
+ const normalizedFrom = Math.max(0, Math.min(from, plainText.length));
3716
+ const normalizedTo = Math.max(normalizedFrom, Math.min(to, plainText.length));
3717
+ const collapsed = plainText.slice(normalizedFrom, normalizedTo).replace(/\s+/g, " ").trim();
3718
+ if (!collapsed) {
3719
+ return fallback;
3720
+ }
3721
+ return collapsed.length > 96 ? `${collapsed.slice(0, 93)}...` : collapsed;
3722
+ }
3723
+ function isValidStoryTarget(state, target) {
3724
+ if (target.kind === "main") return true;
3725
+ const subParts = state.document.subParts;
3726
+ if (!subParts) return false;
3727
+ switch (target.kind) {
3728
+ case "header":
3729
+ return Boolean(normalizeHeaderFooterTarget(state.document, target));
3730
+ case "footer":
3731
+ return Boolean(normalizeHeaderFooterTarget(state.document, target));
3732
+ case "footnote":
3733
+ return Boolean(subParts.footnoteCollection?.footnotes?.[target.noteId]);
3734
+ case "endnote":
3735
+ return Boolean(subParts.footnoteCollection?.endnotes?.[target.noteId]);
3736
+ }
3737
+ }
3738
+ function derivePageLayoutSnapshot(state, activeStory, storySelections) {
3739
+ const subParts = state.document.subParts;
3740
+ const sections = buildResolvedSections(state.document);
3741
+ if (!subParts && sections.length === 0) {
3742
+ return null;
3743
+ }
3744
+ const activeSection = resolveActiveSection(
3745
+ state,
3746
+ activeStory,
3747
+ sections,
3748
+ storySelections
3749
+ );
3750
+ return buildPageLayoutSnapshot(
3751
+ activeSection?.index ?? 0,
3752
+ activeSection?.properties ?? subParts?.finalSectionProperties,
3753
+ subParts
3754
+ );
3755
+ }
3756
+ var NON_MUTATION_COMMANDS = /* @__PURE__ */ new Set([
3757
+ "selection.set",
3758
+ "runtime.set-read-only",
3759
+ "runtime.focus",
3760
+ "warning.add",
3761
+ "warning.clear",
3762
+ "comment.open"
3763
+ ]);
3764
+ var SUGGESTING_UNSUPPORTED_COMMANDS = /* @__PURE__ */ new Set([
3765
+ "paragraph.split"
3766
+ ]);
3767
+ var SUGGESTING_SECONDARY_STORY_UNSUPPORTED_COMMANDS = /* @__PURE__ */ new Set([
3768
+ "text.insert",
3769
+ "text.delete-backward",
3770
+ "text.delete-forward",
3771
+ "text.insert-tab",
3772
+ "text.insert-hard-break"
3773
+ ]);
3774
+ function isMutationCommand(command) {
3775
+ return !NON_MUTATION_COMMANDS.has(command.type);
3776
+ }
3777
+ function buildFieldSnapshot(document) {
3778
+ const entries = [];
3779
+ let index = 0;
3780
+ for (const block of document.content.children) {
3781
+ index = collectFieldsFromBlock(block, entries, index);
3782
+ }
3783
+ index = collectFieldsFromSubParts(document.subParts, entries, index);
3784
+ const supportedCount = entries.filter((e) => e.supported).length;
3785
+ return {
3786
+ totalCount: entries.length,
3787
+ supportedCount,
3788
+ preserveOnlyCount: entries.length - supportedCount,
3789
+ fields: entries
3790
+ };
3791
+ }
3792
+ function collectFieldsFromBlock(block, entries, index) {
3793
+ if (block.type === "paragraph") {
3794
+ for (const child of block.children) {
3795
+ index = collectFieldsFromInline(child, entries, index);
3796
+ }
3797
+ } else if (block.type === "table") {
3798
+ for (const row of block.rows) {
3799
+ for (const cell of row.cells) {
3800
+ for (const child of cell.children) {
3801
+ index = collectFieldsFromBlock(child, entries, index);
3802
+ }
3803
+ }
3804
+ }
3805
+ } else if (block.type === "sdt" || block.type === "custom_xml") {
3806
+ for (const child of block.children) {
3807
+ index = collectFieldsFromBlock(child, entries, index);
3808
+ }
3809
+ }
3810
+ return index;
3811
+ }
3812
+ function collectFieldsFromInline(node, entries, index) {
3813
+ if (node.type === "field") {
3814
+ const fieldFamily = node.fieldFamily ?? "UNKNOWN";
3815
+ const supported = isSupportedFieldFamily(fieldFamily);
3816
+ const displayText = extractFieldDisplayText(node);
3817
+ entries.push({
3818
+ index,
3819
+ fieldFamily,
3820
+ supported,
3821
+ instruction: node.instruction,
3822
+ fieldTarget: node.fieldTarget,
3823
+ refreshStatus: node.refreshStatus ?? (supported ? "stale" : "preserve-only"),
3824
+ displayText
3825
+ });
3826
+ index++;
3827
+ for (const child of node.children) {
3828
+ index = collectFieldsFromInline(child, entries, index);
3829
+ }
3830
+ } else if (node.type === "hyperlink") {
3831
+ for (const child of node.children) {
3832
+ index = collectFieldsFromInline(child, entries, index);
3833
+ }
3834
+ }
3835
+ return index;
3836
+ }
3837
+ function extractFieldDisplayText(field) {
3838
+ return flattenInlineDisplayText(field.children);
3839
+ }
3840
+ function flattenInlineDisplayText(children) {
3841
+ return children.map((child) => {
3842
+ switch (child.type) {
3843
+ case "text":
3844
+ return child.text;
3845
+ case "tab":
3846
+ return " ";
3847
+ case "hard_break":
3848
+ case "column_break":
3849
+ return "\n";
3850
+ case "hyperlink":
3851
+ case "field":
3852
+ return flattenInlineDisplayText(child.children);
3853
+ case "footnote_ref":
3854
+ return child.noteId;
3855
+ default:
3856
+ return "";
3857
+ }
3858
+ }).join("");
3859
+ }
3860
+ function refreshDocumentFields(document, selectionHead, activeStory, options) {
3861
+ const supportedOnly = options?.supportedOnly ?? true;
3862
+ const bookmarkMap = buildBookmarkNameMap(document);
3863
+ const paragraphs = collectParagraphContexts(document.content.children);
3864
+ const navigation = createDocumentNavigationSnapshot(document, selectionHead, activeStory);
3865
+ let updatedCount = 0;
3866
+ let changed = false;
3867
+ let changedFrom;
3868
+ let changedTo;
3869
+ const nextChildren = refreshBlocksWithCursor(document.content.children, (field, range) => {
3870
+ if (!field.fieldFamily || !isSupportedFieldFamily(field.fieldFamily)) {
3871
+ return field;
3872
+ }
3873
+ if (supportedOnly && field.fieldFamily === "TOC") {
3874
+ return field;
3875
+ }
3876
+ const display = resolveSupportedFieldDisplay(
3877
+ field,
3878
+ document,
3879
+ bookmarkMap,
3880
+ paragraphs,
3881
+ navigation
3882
+ );
3883
+ if (!display) {
3884
+ return field;
3885
+ }
3886
+ updatedCount += 1;
3887
+ const nextField = {
3888
+ ...field,
3889
+ children: buildInlineNodesFromDisplayText(display.displayText),
3890
+ refreshStatus: display.refreshStatus
3891
+ };
3892
+ if (nextField.refreshStatus !== field.refreshStatus || flattenInlineDisplayText(nextField.children) !== flattenInlineDisplayText(field.children)) {
3893
+ changed = true;
3894
+ changedFrom = changedFrom === void 0 ? range.from : Math.min(changedFrom, range.from);
3895
+ changedTo = changedTo === void 0 ? range.to : Math.max(changedTo, range.to);
3896
+ }
3897
+ return nextField;
3898
+ }).blocks;
3899
+ if (!changed) {
3900
+ return { document, updatedCount, changed: false };
3901
+ }
3902
+ const nextDocument = {
3903
+ ...document,
3904
+ content: {
3905
+ ...document.content,
3906
+ children: nextChildren
3907
+ }
3908
+ };
3909
+ const nextRegistry = buildFieldRegistry({
3910
+ content: nextDocument.content,
3911
+ styles: nextDocument.styles,
3912
+ subParts: nextDocument.subParts
3913
+ });
3914
+ nextDocument.fieldRegistry = nextRegistry;
3915
+ let protectionSelection;
3916
+ if (changedFrom !== void 0 && changedTo !== void 0) {
3917
+ protectionSelection = createSelectionSnapshot(changedFrom, changedTo);
3918
+ }
3919
+ return {
3920
+ document: nextDocument,
3921
+ updatedCount,
3922
+ changed: true,
3923
+ ...protectionSelection ? { protectionSelection } : {}
3924
+ };
3925
+ }
3926
+ function refreshDocumentTableOfContents(document, selectionHead, activeStory, options) {
3927
+ const navigation = createDocumentNavigationSnapshot(document, selectionHead, activeStory);
3928
+ let changed = false;
3929
+ let resultEntries = [];
3930
+ let changedFrom;
3931
+ let changedTo;
3932
+ const nextChildren = refreshBlocksWithCursor(document.content.children, (field, range) => {
3933
+ if (field.fieldFamily !== "TOC") {
3934
+ return field;
3935
+ }
3936
+ const levelRange = options?.maxLevel ? { from: 1, to: options.maxLevel } : parseTocLevelRange(field.instruction);
3937
+ const entries = navigation.headings.filter((heading) => heading.level >= levelRange.from && heading.level <= levelRange.to).map((heading) => ({
3938
+ level: heading.level,
3939
+ text: heading.text,
3940
+ pageIndex: heading.pageIndex
3941
+ }));
3942
+ if (resultEntries.length === 0) {
3943
+ resultEntries = entries;
3944
+ }
3945
+ const nextField = {
3946
+ ...field,
3947
+ children: buildTocInlineNodes(entries),
3948
+ refreshStatus: "current"
3949
+ };
3950
+ if (flattenInlineDisplayText(nextField.children) !== flattenInlineDisplayText(field.children)) {
3951
+ changed = true;
3952
+ changedFrom = changedFrom === void 0 ? range.from : Math.min(changedFrom, range.from);
3953
+ changedTo = changedTo === void 0 ? range.to : Math.max(changedTo, range.to);
3954
+ }
3955
+ return nextField;
3956
+ }).blocks;
3957
+ if (!changed) {
3958
+ return {
3959
+ document,
3960
+ result: { entryCount: resultEntries.length, entries: resultEntries },
3961
+ changed: false
3962
+ };
3963
+ }
3964
+ const nextDocument = {
3965
+ ...document,
3966
+ content: {
3967
+ ...document.content,
3968
+ children: nextChildren
3969
+ }
3970
+ };
3971
+ const nextRegistry = buildFieldRegistry({
3972
+ content: nextDocument.content,
3973
+ styles: nextDocument.styles,
3974
+ subParts: nextDocument.subParts
3975
+ });
3976
+ nextDocument.fieldRegistry = nextRegistry.tocStructure ? {
3977
+ ...nextRegistry,
3978
+ tocStructure: {
3979
+ ...nextRegistry.tocStructure,
3980
+ status: "current"
3981
+ }
3982
+ } : nextRegistry;
3983
+ let protectionSelection;
3984
+ if (changedFrom !== void 0 && changedTo !== void 0) {
3985
+ protectionSelection = createSelectionSnapshot(changedFrom, changedTo);
3986
+ }
3987
+ return {
3988
+ document: nextDocument,
3989
+ result: { entryCount: resultEntries.length, entries: resultEntries },
3990
+ changed: true,
3991
+ ...protectionSelection ? { protectionSelection } : {}
3992
+ };
3993
+ }
3994
+ function refreshBlocksWithCursor(blocks, visitField, cursor = 0, previousParagraph = false) {
3995
+ const nextBlocks = blocks.map((block) => {
3996
+ if (block.type === "paragraph") {
3997
+ const paragraphStart = previousParagraph ? cursor + 1 : cursor;
3998
+ const refreshedChildren = refreshInlineNodesWithCursor(
3999
+ block.children,
4000
+ visitField,
4001
+ paragraphStart
4002
+ );
4003
+ cursor = paragraphStart + refreshedChildren.cursor;
4004
+ previousParagraph = true;
4005
+ return {
4006
+ ...block,
4007
+ children: refreshedChildren.nodes
4008
+ };
4009
+ }
4010
+ if (block.type === "table") {
4011
+ cursor += 1;
4012
+ previousParagraph = false;
4013
+ return {
4014
+ ...block,
4015
+ rows: block.rows.map((row) => ({
4016
+ ...row,
4017
+ cells: row.cells.map((cell) => ({
4018
+ ...cell,
4019
+ children: (() => {
4020
+ const refreshed = refreshBlocksWithCursor(cell.children, visitField, cursor, false);
4021
+ cursor = refreshed.cursor;
4022
+ return refreshed.blocks;
4023
+ })()
4024
+ }))
4025
+ }))
4026
+ };
4027
+ }
4028
+ if (block.type === "sdt" || block.type === "custom_xml") {
4029
+ const refreshed = refreshBlocksWithCursor(
4030
+ block.children,
4031
+ visitField,
4032
+ cursor,
4033
+ previousParagraph
4034
+ );
4035
+ cursor = refreshed.cursor;
4036
+ previousParagraph = refreshed.previousParagraph;
4037
+ return {
4038
+ ...block,
4039
+ children: refreshed.blocks
4040
+ };
4041
+ }
4042
+ cursor += 1;
4043
+ previousParagraph = false;
4044
+ return block;
4045
+ });
4046
+ return { blocks: nextBlocks, cursor, previousParagraph };
4047
+ }
4048
+ function refreshInlineNodesWithCursor(nodes, visitField, cursor = 0) {
4049
+ const nextNodes = nodes.map((node) => {
4050
+ if (node.type === "field") {
4051
+ const fieldStart = cursor;
4052
+ const refreshedChildren = refreshInlineNodesWithCursor(node.children, visitField, cursor);
4053
+ const fieldLength = measureInlineNodes(node.children);
4054
+ cursor = fieldStart + fieldLength;
4055
+ return visitField({
4056
+ ...node,
4057
+ children: refreshedChildren.nodes
4058
+ }, {
4059
+ from: fieldStart,
4060
+ to: fieldStart + fieldLength
4061
+ });
4062
+ }
4063
+ if (node.type === "hyperlink") {
4064
+ cursor += measureInlineNodes(node.children);
4065
+ return {
4066
+ ...node,
4067
+ // Hyperlinks only contain text-like children in the canonical model.
4068
+ children: [...node.children]
4069
+ };
4070
+ }
4071
+ cursor += measureInlineNode(node);
4072
+ return node;
4073
+ });
4074
+ return { nodes: nextNodes, cursor };
4075
+ }
4076
+ function buildInlineNodesFromDisplayText(text) {
4077
+ if (text.length === 0) {
4078
+ return [];
4079
+ }
4080
+ const children = [];
4081
+ let buffer = "";
4082
+ const flushBuffer = () => {
4083
+ if (buffer.length > 0) {
4084
+ children.push({ type: "text", text: buffer });
4085
+ buffer = "";
4086
+ }
4087
+ };
4088
+ for (const character of text) {
4089
+ if (character === " ") {
4090
+ flushBuffer();
4091
+ children.push({ type: "tab" });
4092
+ continue;
4093
+ }
4094
+ if (character === "\n") {
4095
+ flushBuffer();
4096
+ children.push({ type: "hard_break" });
4097
+ continue;
4098
+ }
4099
+ buffer += character;
4100
+ }
4101
+ flushBuffer();
4102
+ return children;
4103
+ }
4104
+ function buildTocInlineNodes(entries) {
4105
+ const children = [];
4106
+ entries.forEach((entry, index) => {
4107
+ children.push({ type: "text", text: entry.text });
4108
+ children.push({ type: "tab" });
4109
+ children.push({ type: "text", text: String(entry.pageIndex + 1) });
4110
+ if (index < entries.length - 1) {
4111
+ children.push({ type: "hard_break" });
4112
+ }
4113
+ });
4114
+ return children;
4115
+ }
4116
+ function collectFieldsFromSubParts(subParts, entries, index) {
4117
+ if (!subParts) {
4118
+ return index;
4119
+ }
4120
+ let nextIndex = index;
4121
+ for (const header of subParts.headers ?? []) {
4122
+ for (const block of header.blocks) {
4123
+ nextIndex = collectFieldsFromBlock(block, entries, nextIndex);
4124
+ }
4125
+ }
4126
+ for (const footer of subParts.footers ?? []) {
4127
+ for (const block of footer.blocks) {
4128
+ nextIndex = collectFieldsFromBlock(block, entries, nextIndex);
4129
+ }
4130
+ }
4131
+ if (subParts.footnoteCollection) {
4132
+ for (const note of Object.values(subParts.footnoteCollection.footnotes)) {
4133
+ for (const block of note.blocks) {
4134
+ nextIndex = collectFieldsFromBlock(block, entries, nextIndex);
4135
+ }
4136
+ }
4137
+ for (const note of Object.values(subParts.footnoteCollection.endnotes)) {
4138
+ for (const block of note.blocks) {
4139
+ nextIndex = collectFieldsFromBlock(block, entries, nextIndex);
4140
+ }
4141
+ }
4142
+ }
4143
+ return nextIndex;
4144
+ }
4145
+ function resolveSupportedFieldDisplay(field, document, bookmarkMap, paragraphs, navigation) {
4146
+ if (!field.fieldFamily || !isSupportedFieldFamily(field.fieldFamily)) {
4147
+ return void 0;
4148
+ }
4149
+ if (!field.fieldTarget) {
4150
+ return field.fieldFamily === "TOC" ? void 0 : { displayText: "", refreshStatus: "unresolvable" };
4151
+ }
4152
+ if (field.fieldFamily === "REF") {
4153
+ const result = resolveRefFieldText(document, bookmarkMap, field.fieldTarget);
4154
+ return result ? { displayText: result.text, refreshStatus: result.refreshStatus } : { displayText: "", refreshStatus: "unresolvable" };
4155
+ }
4156
+ const bookmark = bookmarkMap.get(field.fieldTarget);
4157
+ if (!bookmark) {
4158
+ return { displayText: "", refreshStatus: "unresolvable" };
4159
+ }
4160
+ if (field.fieldFamily === "PAGEREF") {
4161
+ const paragraph = paragraphs[bookmark.paragraphIndex];
4162
+ if (!paragraph) {
4163
+ return { displayText: "", refreshStatus: "unresolvable" };
4164
+ }
4165
+ const pageIndex = findPageForOffset(navigation.pages, paragraph.startOffset);
4166
+ return { displayText: String(pageIndex + 1), refreshStatus: "current" };
4167
+ }
4168
+ if (field.fieldFamily === "NOTEREF") {
4169
+ const paragraph = paragraphs[bookmark.paragraphIndex]?.paragraph;
4170
+ if (!paragraph) {
4171
+ return { displayText: "", refreshStatus: "unresolvable" };
4172
+ }
4173
+ const noteText = resolveNoteReferenceText(paragraph, bookmark.bookmarkId);
4174
+ return noteText ? { displayText: noteText, refreshStatus: "current" } : { displayText: "", refreshStatus: "unresolvable" };
4175
+ }
4176
+ return void 0;
4177
+ }
4178
+ function collectParagraphContexts(blocks) {
4179
+ const paragraphs = [];
4180
+ collectParagraphContextsFromBlocks(blocks, paragraphs, 0, false);
4181
+ return paragraphs;
4182
+ }
4183
+ function collectParagraphContextsFromBlocks(blocks, paragraphs, cursor, previousParagraph) {
4184
+ let nextCursor = cursor;
4185
+ let nextPreviousParagraph = previousParagraph;
4186
+ for (const block of blocks) {
4187
+ if (block.type === "paragraph") {
4188
+ if (nextPreviousParagraph) {
4189
+ nextCursor += 1;
4190
+ }
4191
+ paragraphs.push({ paragraph: block, startOffset: nextCursor });
4192
+ nextCursor += measureInlineNodes(block.children);
4193
+ nextPreviousParagraph = true;
4194
+ continue;
4195
+ }
4196
+ if (block.type === "table") {
4197
+ nextCursor += 1;
4198
+ nextPreviousParagraph = false;
4199
+ for (const row of block.rows) {
4200
+ for (const cell of row.cells) {
4201
+ const result = collectParagraphContextsFromBlocks(
4202
+ cell.children,
4203
+ paragraphs,
4204
+ nextCursor,
4205
+ false
4206
+ );
4207
+ nextCursor = result.cursor;
4208
+ }
4209
+ }
4210
+ continue;
4211
+ }
4212
+ if (block.type === "sdt" || block.type === "custom_xml") {
4213
+ const result = collectParagraphContextsFromBlocks(
4214
+ block.children,
4215
+ paragraphs,
4216
+ nextCursor,
4217
+ nextPreviousParagraph
4218
+ );
4219
+ nextCursor = result.cursor;
4220
+ nextPreviousParagraph = result.previousParagraph;
4221
+ continue;
4222
+ }
4223
+ nextCursor += 1;
4224
+ nextPreviousParagraph = false;
4225
+ }
4226
+ return { cursor: nextCursor, previousParagraph: nextPreviousParagraph };
4227
+ }
4228
+ function measureInlineNodes(nodes) {
4229
+ return nodes.reduce((size, node) => size + measureInlineNode(node), 0);
4230
+ }
4231
+ function measureInlineNode(node) {
4232
+ switch (node.type) {
4233
+ case "text":
4234
+ return node.text.length;
4235
+ case "tab":
4236
+ case "hard_break":
4237
+ case "column_break":
4238
+ case "footnote_ref":
4239
+ case "image":
4240
+ case "opaque_inline":
4241
+ case "bookmark_start":
4242
+ case "bookmark_end":
4243
+ return 1;
4244
+ case "hyperlink":
4245
+ case "field":
4246
+ return measureInlineNodes(node.children);
4247
+ default:
4248
+ return 1;
4249
+ }
4250
+ }
4251
+ function resolveNoteReferenceText(paragraph, bookmarkId) {
4252
+ let inside = false;
4253
+ let sawBoundary = false;
4254
+ for (const child of paragraph.children) {
4255
+ if (child.type === "bookmark_start" && child.bookmarkId === bookmarkId) {
4256
+ inside = true;
4257
+ sawBoundary = true;
4258
+ continue;
4259
+ }
4260
+ if (child.type === "bookmark_end" && child.bookmarkId === bookmarkId) {
4261
+ break;
4262
+ }
4263
+ if (!inside) {
4264
+ continue;
4265
+ }
4266
+ if (child.type === "footnote_ref") {
4267
+ return child.noteId;
4268
+ }
4269
+ }
4270
+ return sawBoundary ? void 0 : void 0;
4271
+ }
4272
+ function getCommandSelection(command, fallbackSelection) {
4273
+ if ("protectionSelection" in command && command.protectionSelection) {
4274
+ return command.protectionSelection;
4275
+ }
4276
+ if ("selection" in command && command.selection) {
4277
+ return command.selection;
4278
+ }
4279
+ return fallbackSelection;
4280
+ }
4281
+ function isBlockedByProtection(protection, selection) {
4282
+ const enforcedRanges = protection.ranges.filter(
4283
+ (range) => range.enforced && typeof range.start === "number" && typeof range.end === "number"
4284
+ );
4285
+ if (enforcedRanges.length === 0) {
4286
+ return false;
4287
+ }
4288
+ const from = Math.min(selection.anchor, selection.head);
4289
+ const to = Math.max(selection.anchor, selection.head);
4290
+ return !enforcedRanges.some(
4291
+ (range) => from >= range.start && to <= range.end
4292
+ );
4293
+ }
4294
+ function remapProtectionSnapshot(protection, mapping) {
4295
+ if (mapping.steps.length === 0 || protection.ranges.length === 0) {
4296
+ return protection;
4297
+ }
4298
+ let changed = false;
4299
+ const nextRanges = protection.ranges.map((range) => {
4300
+ if (!range.enforced || typeof range.start !== "number" || typeof range.end !== "number") {
4301
+ return range;
4302
+ }
4303
+ const mapped = mapRange(
4304
+ { from: range.start, to: range.end },
4305
+ { start: -1, end: 1 },
4306
+ mapping
4307
+ );
4308
+ if (mapped.kind === "detached") {
4309
+ changed = true;
4310
+ return {
4311
+ ...range,
4312
+ start: void 0,
4313
+ end: void 0,
4314
+ enforced: false,
4315
+ enforcementReason: "preserve-only: permission range could not be remapped after runtime edits"
4316
+ };
4317
+ }
4318
+ if (mapped.range.from !== range.start || mapped.range.to !== range.end) {
4319
+ changed = true;
4320
+ return {
4321
+ ...range,
4322
+ start: mapped.range.from,
4323
+ end: mapped.range.to
4324
+ };
4325
+ }
4326
+ return range;
4327
+ });
4328
+ if (!changed) {
4329
+ return protection;
4330
+ }
4331
+ return {
4332
+ ...protection,
4333
+ ranges: nextRanges,
4334
+ enforcedRangeCount: nextRanges.filter((range) => range.enforced).length,
4335
+ preservedRangeCount: nextRanges.filter((range) => !range.enforced).length
4336
+ };
4337
+ }
4338
+
4339
+ export {
4340
+ createDocumentRuntime
4341
+ };
4342
+ //# sourceMappingURL=chunk-KD2QRQPY.js.map