@stll/folio-core 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/ai-edits/apply.js +214 -52
  2. package/dist/ai-edits/batch-claims.d.ts +3 -1
  3. package/dist/ai-edits/batch-claims.js +25 -1
  4. package/dist/ai-edits/headless.js +2 -2
  5. package/dist/ai-edits/table-row-column-mutations.js +17 -3
  6. package/dist/ai-edits/types.d.ts +7 -1
  7. package/dist/ai-suggestions/apply.js +20 -1
  8. package/dist/compare/compare.js +1 -0
  9. package/dist/compare/inline-atoms.js +40 -16
  10. package/dist/compare/inline-provenance.js +12 -5
  11. package/dist/compare/reproducible-package.d.ts +0 -17
  12. package/dist/compare/reproducible-package.js +12 -18
  13. package/dist/compat/eigenpal.d.ts +2 -1
  14. package/dist/compat/eigenpal.js +2 -1
  15. package/dist/controller/canonicalClipboard.d.ts +23 -0
  16. package/dist/controller/canonicalClipboard.js +482 -0
  17. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  18. package/dist/controller/canonicalClipboardResources.js +382 -0
  19. package/dist/controller/canonicalComments.d.ts +41 -0
  20. package/dist/controller/canonicalComments.js +152 -0
  21. package/dist/controller/canonicalComposition.d.ts +24 -3
  22. package/dist/controller/canonicalComposition.js +208 -48
  23. package/dist/controller/canonicalInlineProjection.d.ts +118 -0
  24. package/dist/controller/canonicalInlineProjection.js +476 -0
  25. package/dist/controller/canonicalInput.d.ts +13 -4
  26. package/dist/controller/canonicalInput.js +62 -14
  27. package/dist/controller/canonicalInputTimer.d.ts +16 -0
  28. package/dist/controller/canonicalInputTimer.js +34 -0
  29. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  30. package/dist/controller/canonicalPublicOperations.js +648 -0
  31. package/dist/controller/canonicalSelection.d.ts +18 -0
  32. package/dist/controller/canonicalSelection.js +32 -0
  33. package/dist/controller/canonicalSession.d.ts +27 -6
  34. package/dist/controller/canonicalSession.js +298 -116
  35. package/dist/controller/canonicalStoryEditor.d.ts +7 -3
  36. package/dist/controller/canonicalStoryEditor.js +27 -2
  37. package/dist/controller/canonicalStructure.js +57 -4
  38. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  39. package/dist/controller/canonicalTocSelection.js +43 -0
  40. package/dist/controller/folioEditor.d.ts +2 -9
  41. package/dist/controller/folioEditor.js +18 -4
  42. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  43. package/dist/controller/headerFooterEditorManager.js +26 -11
  44. package/dist/controller/hiddenEditorApi.d.ts +24 -3
  45. package/dist/controller/hiddenEditorApi.js +8 -0
  46. package/dist/controller/hiddenEditorManager.d.ts +5 -1
  47. package/dist/controller/hiddenEditorManager.js +378 -43
  48. package/dist/controller/noteEditorManager.d.ts +2 -1
  49. package/dist/controller/noteEditorManager.js +29 -13
  50. package/dist/document-operations.d.ts +8 -1
  51. package/dist/document-operations.js +6 -4
  52. package/dist/docx/blockPlainText.d.ts +1 -1
  53. package/dist/docx/blockPlainText.js +11 -3
  54. package/dist/docx/builtInStyles.js +13 -6
  55. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  56. package/dist/docx/canonicalResourceSave.js +27 -0
  57. package/dist/docx/canonicalSave.d.ts +29 -0
  58. package/dist/docx/canonicalSave.js +101 -0
  59. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  60. package/dist/docx/canonicalStoryRepack.js +38 -5
  61. package/dist/docx/containerChildren.gen.d.ts +3 -1
  62. package/dist/docx/containerChildren.gen.js +33 -1
  63. package/dist/docx/documentParser.d.ts +1 -13
  64. package/dist/docx/documentParser.js +1 -47
  65. package/dist/docx/ensureParaIds.js +10 -4
  66. package/dist/docx/fieldParser.js +14 -2
  67. package/dist/docx/footnoteParser.d.ts +1 -11
  68. package/dist/docx/footnoteParser.js +1 -16
  69. package/dist/docx/headerFooterParser.d.ts +1 -9
  70. package/dist/docx/headerFooterParser.js +1 -12
  71. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  72. package/dist/docx/headerFooterVerbatim.js +81 -3
  73. package/dist/docx/hyperlinkParser.d.ts +1 -1
  74. package/dist/docx/hyperlinkParser.js +20 -1
  75. package/dist/docx/imageParser.d.ts +13 -1
  76. package/dist/docx/imageParser.js +20 -11
  77. package/dist/docx/inlineWrapperContent.d.ts +4 -4
  78. package/dist/docx/inlineWrapperContent.js +4 -4
  79. package/dist/docx/linkedSdtPreservation.d.ts +10 -0
  80. package/dist/docx/linkedSdtPreservation.js +37 -0
  81. package/dist/docx/metadataPrivacy.js +6 -1
  82. package/dist/docx/packageParts.js +9 -3
  83. package/dist/docx/paragraphParser.js +88 -67
  84. package/dist/docx/paragraphPropertySource.d.ts +22 -1
  85. package/dist/docx/paragraphPropertySource.js +109 -2
  86. package/dist/docx/parser.js +2 -1
  87. package/dist/docx/preservedRunContent.js +2 -1
  88. package/dist/docx/relsParser.js +26 -2
  89. package/dist/docx/removeHeaderFooterParts.js +17 -6
  90. package/dist/docx/rezip.d.ts +49 -5
  91. package/dist/docx/rezip.js +754 -235
  92. package/dist/docx/saveDiagnostics.d.ts +11 -0
  93. package/dist/docx/selectiveSave.d.ts +2 -1
  94. package/dist/docx/selectiveSave.js +42 -11
  95. package/dist/docx/selectiveXmlPatch.js +7 -5
  96. package/dist/docx/serializer/commentSerializer.d.ts +4 -1
  97. package/dist/docx/serializer/commentSerializer.js +7 -3
  98. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  99. package/dist/docx/serializer/documentSerializer.js +29 -10
  100. package/dist/docx/serializer/paragraphSerializer.js +207 -94
  101. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  102. package/dist/docx/server/materializeYjsDocx.js +14 -6
  103. package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
  104. package/dist/docx/storyBlockReplay.d.ts +21 -1
  105. package/dist/docx/storyBlockReplay.js +113 -9
  106. package/dist/docx/storyPlainText.d.ts +20 -0
  107. package/dist/docx/storyPlainText.js +23 -0
  108. package/dist/docx/structuralXmlPatch.js +10 -22
  109. package/dist/docx/styleParser.d.ts +5 -1
  110. package/dist/docx/styleParser.js +1 -1
  111. package/dist/docx/tableParser.d.ts +1 -8
  112. package/dist/docx/tableParser.js +1 -28
  113. package/dist/docx/textBoxParser.d.ts +1 -5
  114. package/dist/docx/textBoxParser.js +1 -20
  115. package/dist/docx/unzip.js +6 -1
  116. package/dist/i18n/messages/ar.gen.js +4 -0
  117. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  118. package/dist/i18n/messages/catalogs.gen.js +68 -0
  119. package/dist/i18n/messages/cs.gen.js +4 -0
  120. package/dist/i18n/messages/de.gen.js +4 -0
  121. package/dist/i18n/messages/en.gen.js +4 -0
  122. package/dist/i18n/messages/es.gen.js +4 -0
  123. package/dist/i18n/messages/et.gen.js +4 -0
  124. package/dist/i18n/messages/fr.gen.js +4 -0
  125. package/dist/i18n/messages/he.gen.js +4 -0
  126. package/dist/i18n/messages/hi.gen.js +4 -0
  127. package/dist/i18n/messages/hu.gen.js +4 -0
  128. package/dist/i18n/messages/lt.gen.js +4 -0
  129. package/dist/i18n/messages/lv.gen.js +4 -0
  130. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  131. package/dist/i18n/messages/pl.gen.js +4 -0
  132. package/dist/i18n/messages/pt-BR.gen.js +4 -0
  133. package/dist/i18n/messages/sk.gen.js +4 -0
  134. package/dist/i18n/messages/tr.gen.js +4 -0
  135. package/dist/i18n/messages/zh-CN.gen.js +4 -0
  136. package/dist/index.d.ts +2 -1
  137. package/dist/index.js +2 -1
  138. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  139. package/dist/internal/acceptedBlockProjection.js +45 -0
  140. package/dist/internal/revisionResolutionInline.js +25 -3
  141. package/dist/managers/DocumentLoaderManager.js +2 -1
  142. package/dist/markdown/escape.d.ts +3 -1
  143. package/dist/markdown/escape.js +9 -2
  144. package/dist/markdown/renderBlock.js +5 -120
  145. package/dist/markdown/renderParagraph.js +4 -2
  146. package/dist/markdown/renderRuns.d.ts +7 -1
  147. package/dist/markdown/renderRuns.js +33 -24
  148. package/dist/panel-layout.d.ts +11 -9
  149. package/dist/panel-layout.js +13 -9
  150. package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
  151. package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
  152. package/dist/prosemirror/attrs/index.js +5 -0
  153. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  154. package/dist/prosemirror/canonicalCommands.js +11 -4
  155. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  156. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  157. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  158. package/dist/prosemirror/clipboardIntent.js +9 -0
  159. package/dist/prosemirror/commands/comments.js +60 -35
  160. package/dist/prosemirror/commands/pageBreak.js +2 -2
  161. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  162. package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
  163. package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
  164. package/dist/prosemirror/commentOps.js +15 -1
  165. package/dist/prosemirror/conversion/fromProseDoc.js +22 -3
  166. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  167. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  168. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  169. package/dist/prosemirror/conversion/toProseDoc.js +124 -22
  170. package/dist/prosemirror/extensions/core/ParagraphExtension.js +61 -35
  171. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
  172. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
  173. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +65 -29
  174. package/dist/prosemirror/extensions/marks/markUtils.js +11 -8
  175. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
  176. package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
  177. package/dist/prosemirror/fieldRepresentation.js +18 -0
  178. package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
  179. package/dist/prosemirror/hyperlinkRemoval.js +28 -9
  180. package/dist/prosemirror/insertOperations.js +2 -1
  181. package/dist/prosemirror/listNumbering.js +11 -5
  182. package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
  183. package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
  184. package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
  185. package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
  186. package/dist/prosemirror/noteReferenceReview.js +14 -1
  187. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  188. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  189. package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
  190. package/dist/prosemirror/plugins/documentStyles.js +17 -10
  191. package/dist/prosemirror/plugins/suggestionMode.js +48 -48
  192. package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
  193. package/dist/prosemirror/preservedXmlReview.js +35 -0
  194. package/dist/prosemirror/runFormattingFromMarks.js +2 -2
  195. package/dist/prosemirror/runFormattingReconciliation.js +5 -4
  196. package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
  197. package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
  198. package/dist/prosemirror/runStyleFormatting.d.ts +3 -1
  199. package/dist/prosemirror/runStyleFormatting.js +3 -1
  200. package/dist/prosemirror/schema/marks.d.ts +1 -0
  201. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  202. package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
  203. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
  204. package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
  205. package/dist/prosemirror/textInput.d.ts +5 -1
  206. package/dist/prosemirror/textInput.js +3 -3
  207. package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
  208. package/dist/prosemirror/trackedRevisionPath.js +39 -1
  209. package/dist/prosemirror/validation.js +3 -5
  210. package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
  211. package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
  212. package/dist/types/canonicalCapabilities.d.ts +201 -0
  213. package/dist/types/canonicalCapabilities.js +198 -0
  214. package/dist/types/canonicalComments.d.ts +51 -0
  215. package/dist/types/canonicalComments.js +0 -0
  216. package/dist/types/canonicalSave.d.ts +11 -0
  217. package/dist/types/canonicalSave.js +0 -0
  218. package/dist/types/canonicalSections.d.ts +12 -0
  219. package/dist/types/canonicalSections.js +0 -0
  220. package/dist/types/docxSerialization.d.ts +14 -0
  221. package/dist/types/docxSerialization.js +7 -0
  222. package/dist/utils/headingCollector.js +45 -4
  223. package/dist/utils/outlineDepth.d.ts +9 -0
  224. package/dist/utils/outlineDepth.js +9 -0
  225. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  import { paragraphNumberingReference, paragraphNumberingReferenceId } from "../docx/numberingReference.js";
2
2
  import { formattingEquals } from "../docx/runConsolidator.js";
3
3
  import { isStyleSourcedParagraphNumbering } from "../internal/paragraphFormattingSerialization.js";
4
+ import { addTrackedDeletionMark } from "../prosemirror/addTrackedDeletionMark.js";
4
5
  import { deleteTextBoxesAnchoredAt, droppedTextBoxAnchorIds, endOfParagraphWithItsTextBoxes, gatherTextBoxesAfterParagraph, textBoxAnchorIdOf, textBoxAnchorIdsBetween } from "../prosemirror/anchoredTextBoxes.js";
5
6
  import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs, expectTableRowAttrs } from "../prosemirror/attrs/index.js";
6
7
  import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "../prosemirror/commands/propertyChangeScope.js";
@@ -17,7 +18,7 @@ import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
17
18
  import { styleResolvedParagraphFormatting } from "../prosemirror/paragraphFormattingProvenance.js";
18
19
  import { directParagraphIndentation, paragraphIndentationAttrPatch, paragraphIndentationEqual, paragraphIndentationFromFormatting, withDirectParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
19
20
  import { joinAtParagraphMark } from "../prosemirror/paragraphMarkJoin.js";
20
- import { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties } from "../prosemirror/paragraphPropertyCarry.js";
21
+ import { carriedParagraphProperties, carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties } from "../prosemirror/paragraphPropertyCarry.js";
21
22
  import { directParagraphSpacing, paragraphSpacingAttrPatch, paragraphSpacingEqual, paragraphSpacingFromFormatting, withDirectParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
22
23
  import { getDocumentNumbering, getDocumentNumberingInstanceIds } from "../prosemirror/plugins/documentNumbering.js";
23
24
  import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
@@ -33,7 +34,8 @@ import { listAttrsFromNumbering, listLevelAttrPatch } from "../prosemirror/style
33
34
  import { removeRowsWithoutCellsAfterBatch } from "../prosemirror/tableGridMutation.js";
34
35
  import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
35
36
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
36
- import { BatchClaims } from "./batch-claims.js";
37
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
38
+ import { BatchClaims, compareBatchTieOrder } from "./batch-claims.js";
37
39
  import { stripBlockIdentityAttrs } from "./block-identity.js";
38
40
  import { describeCharacterSplit } from "./character-boundaries.js";
39
41
  import { buildCleanBlockText, resolveCleanTextRange } from "./clean-text.js";
@@ -436,6 +438,10 @@ const preserveRunFormattingAcrossTrackedStyleChange = ({ allocateRevisionInfo, n
436
438
  revisionIds
437
439
  };
438
440
  };
441
+ const appendParagraphPropertyChange = (existing, change) => {
442
+ if (change.info.provenance !== "suggested" && hasSerializableParagraphPropertyChange(existing)) panic("A paragraph-property write must resolve its occupied revision slot first");
443
+ return [...existing ?? [], change];
444
+ };
439
445
  /**
440
446
  * Apply one paragraph-property patch to a live node. A tracked patch stores
441
447
  * the complete previous pPr: a partial snapshot cannot distinguish a property
@@ -482,7 +488,7 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
482
488
  const nextAttrs = {
483
489
  ...node.attrs,
484
490
  ...patch,
485
- ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
491
+ ...change ? { _propertyChanges: appendParagraphPropertyChange(existing, change) } : {}
486
492
  };
487
493
  const bridgeResult = preserveRunFormatting && revisionInfo ? preserveRunFormattingAcrossTrackedStyleChange({
488
494
  allocateRevisionInfo: revisionInfo,
@@ -560,7 +566,7 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
560
566
  const nextAttrs = {
561
567
  ...block.attrs,
562
568
  ...patch,
563
- ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
569
+ ...change ? { _propertyChanges: appendParagraphPropertyChange(existing, change) } : {}
564
570
  };
565
571
  const bridgeResult = preserveRunFormatting && revisionInfo ? preserveRunFormattingAcrossTrackedStyleChange({
566
572
  allocateRevisionInfo: revisionInfo,
@@ -1280,6 +1286,11 @@ const isInsertedPPrMark = (value) => typeof value === "object" && value !== null
1280
1286
  const isSuggestedParagraphInsert = (paragraph) => typeof paragraph.attrs["_suggestedInsert"] === "object" && paragraph.attrs["_suggestedInsert"] !== null;
1281
1287
  /** A paragraph mark that is a pending deletion, or a relocation's source. */
1282
1288
  const isDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && (value.kind === "del" || value.kind === "moveFrom");
1289
+ const pendingParagraphMarkPropertyRefusal = {
1290
+ reason: "pendingParagraphMarkDeletion",
1291
+ canonicalRefusal: { gap: CANONICAL_GAP.publicPendingParagraphMarkProperties },
1292
+ message: "Resolve the pending paragraph mark deletion before editing its properties, or provide mergedParagraphProperties on the merge operation for atomic intent."
1293
+ };
1283
1294
  const hasRevisionMarker = (value) => typeof value === "object" && value !== null && "revisionId" in value;
1284
1295
  const isDeletedCell = (cell) => {
1285
1296
  const marker = cell.attrs["cellMarker"];
@@ -1501,6 +1512,10 @@ const crossesBatchDeletion = ({ doc, from, to, batchRevisionIds }) => {
1501
1512
  }
1502
1513
  return false;
1503
1514
  };
1515
+ const canRetractFinalBreak = ({ predecessor, target }) => {
1516
+ if (target.type !== predecessor.type) return false;
1517
+ return !(JSON.stringify(paragraphPropertiesBeforePendingChanges(target)) !== JSON.stringify(paragraphPropertiesSnapshot(predecessor)) && hasSerializableParagraphPropertyChange(expectParagraphAttrs(predecessor)._propertyChanges));
1518
+ };
1504
1519
  /** Pending retirements, read after the merge has mapped any inserted separator. */
1505
1520
  const pendingRemovedFinalBreakPositions = (tr, deletions) => {
1506
1521
  const positions = /* @__PURE__ */ new Set();
@@ -1512,6 +1527,92 @@ const pendingRemovedFinalBreakPositions = (tr, deletions) => {
1512
1527
  }
1513
1528
  return positions;
1514
1529
  };
1530
+ /** An unoccupied final carrier takes admissible writes from the finished batch. */
1531
+ const finalParagraphCarry = ({ tr, at, target, predecessor, batchRevisionIds, styleResolver, numbering }) => {
1532
+ const { node: previous, position } = predecessor;
1533
+ const beforeBatch = hasSerializableParagraphPropertyChange(expectParagraphAttrs(target)._propertyChanges) ? resolveParagraphChangeAttrs({
1534
+ node: previous,
1535
+ mode: "reject",
1536
+ boundaryCovered: true,
1537
+ revisionSet: batchRevisionIds,
1538
+ styleResolver,
1539
+ numbering
1540
+ }) : null;
1541
+ return {
1542
+ source: beforeBatch ? previous.type.create(beforeBatch, previous.content, previous.marks) : previous,
1543
+ keep: crossesBatchDeletion({
1544
+ doc: tr.doc,
1545
+ from: position + previous.nodeSize,
1546
+ to: at,
1547
+ batchRevisionIds
1548
+ }) ? /* @__PURE__ */ new Set() : propertiesSetInBatch(target, batchRevisionIds)
1549
+ };
1550
+ };
1551
+ /** Deferred retirement still owns a slot before a later explicit formatting merge. */
1552
+ const hasDeferredParagraphPropertyWrite = ({ tr, deletions, targets, mergedPropertyTargets, batchRevisionIds, styleResolver, numbering }) => {
1553
+ for (const deletion of deletions) {
1554
+ const at = tr.mapping.slice(deletion.mappedFrom).map(deletion.position);
1555
+ const target = tr.doc.nodeAt(at);
1556
+ if (!target) continue;
1557
+ const predecessor = finalParagraphPredecessor(tr.doc, at);
1558
+ if (!predecessor) continue;
1559
+ const mark = predecessor.node.attrs["pPrMark"];
1560
+ if (mark == null && mergedPropertyTargets.some(({ position, mappedFrom }) => {
1561
+ const mapped = tr.mapping.slice(mappedFrom).mapResult(position);
1562
+ return !mapped.deleted && mapped.pos === at;
1563
+ })) continue;
1564
+ const writeTarget = mark == null ? target : tr.doc.nodeAt(predecessor.position + predecessor.node.nodeSize);
1565
+ if (!writeTarget || !targets.includes(writeTarget)) continue;
1566
+ if (mark != null && !(isInsertedPPrMark(mark) && canRetractFinalBreak({
1567
+ predecessor: predecessor.node,
1568
+ target: writeTarget
1569
+ }))) continue;
1570
+ const transfer = mark == null ? finalParagraphCarry({
1571
+ tr,
1572
+ at,
1573
+ target,
1574
+ predecessor,
1575
+ batchRevisionIds,
1576
+ styleResolver,
1577
+ numbering
1578
+ }) : {
1579
+ source: predecessor.node,
1580
+ keep: propertiesSetInBatch(writeTarget, batchRevisionIds)
1581
+ };
1582
+ if (JSON.stringify(paragraphPropertiesSnapshot(writeTarget)) !== JSON.stringify(carriedParagraphProperties({
1583
+ ...transfer,
1584
+ target: writeTarget
1585
+ }))) return true;
1586
+ }
1587
+ return false;
1588
+ };
1589
+ /** The paragraph a merge leaves owns its properties, even across pending deleted breaks. */
1590
+ const paragraphPropertyRevisionTargets = ({ item, tr, deletedFinalParagraphs }) => {
1591
+ const position = tr.mapping.map(item.blockFrom, item.operation.type === "replaceBlock" ? -1 : 1);
1592
+ const source = tr.doc.nodeAt(position) ?? item.blockNode;
1593
+ const targets = source.type.name === "paragraph" ? [source] : [];
1594
+ if (item.operation.type !== "mergeBlockWithNext") return targets;
1595
+ const removedBreakPositions = pendingRemovedFinalBreakPositions(tr, deletedFinalParagraphs);
1596
+ for (const deletion of deletedFinalParagraphs) {
1597
+ const at = tr.mapping.slice(deletion.mappedFrom).map(deletion.position);
1598
+ const predecessor = finalParagraphPredecessor(tr.doc, at);
1599
+ if (!predecessor || !isInsertedPPrMark(predecessor.node.attrs["pPrMark"])) continue;
1600
+ const target = tr.doc.nodeAt(predecessor.position + predecessor.node.nodeSize);
1601
+ if (!target) continue;
1602
+ if (canRetractFinalBreak({
1603
+ predecessor: predecessor.node,
1604
+ target
1605
+ })) removedBreakPositions.add(predecessor.position);
1606
+ }
1607
+ const survivorPosition = paragraphLeftAfter({
1608
+ doc: tr.doc,
1609
+ paragraphPos: position,
1610
+ removedBreakPositions
1611
+ });
1612
+ const survivor = survivorPosition === null ? null : tr.doc.nodeAt(survivorPosition);
1613
+ if (survivor?.type.name === "paragraph") targets.push(survivor);
1614
+ return targets;
1615
+ };
1515
1616
  /**
1516
1617
  * Finish the tracked deletion of every paragraph a container ends with.
1517
1618
  *
@@ -1562,21 +1663,21 @@ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appl
1562
1663
  ...revisionExtras
1563
1664
  }
1564
1665
  });
1565
- const beforeBatch = resolveParagraphChangeAttrs({
1566
- node: previous,
1567
- mode: "reject",
1568
- boundaryCovered: true,
1569
- revisionSet: batchRevisionIds,
1570
- styleResolver,
1571
- numbering
1572
- });
1573
1666
  if (!mergedPropertyTargets.some(({ position: target, mappedFrom }) => {
1574
1667
  const mapped = tr.mapping.slice(mappedFrom).mapResult(target);
1575
1668
  return !mapped.deleted && mapped.pos === at;
1576
1669
  })) carryParagraphProperties({
1577
1670
  tr,
1578
1671
  position: at,
1579
- source: beforeBatch ? previous.type.create(beforeBatch, previous.content, previous.marks) : previous,
1672
+ ...finalParagraphCarry({
1673
+ tr,
1674
+ at,
1675
+ target: emptied,
1676
+ predecessor,
1677
+ batchRevisionIds,
1678
+ styleResolver,
1679
+ numbering
1680
+ }),
1580
1681
  styleResolver,
1581
1682
  numbering,
1582
1683
  revision: {
@@ -1584,13 +1685,7 @@ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appl
1584
1685
  author,
1585
1686
  date,
1586
1687
  ...revisionExtras
1587
- },
1588
- keep: crossesBatchDeletion({
1589
- doc: tr.doc,
1590
- from: position + previous.nodeSize,
1591
- to: at,
1592
- batchRevisionIds
1593
- }) ? /* @__PURE__ */ new Set() : propertiesSetInBatch(emptied, batchRevisionIds)
1688
+ }
1594
1689
  });
1595
1690
  resolvedOperationIds.add(operationId);
1596
1691
  addedRevisions.push({
@@ -1601,11 +1696,10 @@ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appl
1601
1696
  }
1602
1697
  if (isInsertedPPrMark(mark)) {
1603
1698
  const following = tr.doc.nodeAt(position + previous.nodeSize);
1604
- if (following?.type.name !== emptied.type.name) continue;
1605
- const previousFormatting = paragraphPropertiesBeforePendingChanges(following);
1606
- const formattingChanges = JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(previous));
1607
- const existing = expectParagraphAttrs(previous)._propertyChanges;
1608
- if (formattingChanges && hasSerializableParagraphPropertyChange(existing)) continue;
1699
+ if (!following || !canRetractFinalBreak({
1700
+ predecessor: previous,
1701
+ target: following
1702
+ })) continue;
1609
1703
  joinAtParagraphMark({
1610
1704
  tr,
1611
1705
  paragraphPos: position,
@@ -2148,6 +2242,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2148
2242
  skipped.push({
2149
2243
  id: operation.id,
2150
2244
  reason: resolution.reason,
2245
+ ...resolution.canonicalRefusal !== void 0 && { canonicalRefusal: resolution.canonicalRefusal },
2151
2246
  ...resolution.message !== void 0 && { message: resolution.message }
2152
2247
  });
2153
2248
  continue;
@@ -2323,7 +2418,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2323
2418
  if (leftCellShape.tablePosition !== rightCellShape.tablePosition) return rightCellShape.tablePosition - leftCellShape.tablePosition;
2324
2419
  if (leftCellShape.rectangle.bottom !== rightCellShape.rectangle.bottom) return rightCellShape.rectangle.bottom - leftCellShape.rectangle.bottom;
2325
2420
  if (leftCellShape.rectangle.right !== rightCellShape.rectangle.right) return rightCellShape.rectangle.right - leftCellShape.rectangle.right;
2326
- return right.originalIndex - left.originalIndex;
2421
+ return compareBatchTieOrder(left.originalIndex, right.originalIndex);
2327
2422
  }
2328
2423
  const leftColumn = left.tableColumnInsertion ?? left.tableColumnDeletion;
2329
2424
  const rightColumn = right.tableColumnInsertion ?? right.tableColumnDeletion;
@@ -2334,19 +2429,32 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2334
2429
  if (leftColumn.columnIndex !== rightColumn.columnIndex) return rightColumn.columnIndex - leftColumn.columnIndex;
2335
2430
  const leftIsInsertion = left.tableColumnInsertion !== void 0;
2336
2431
  if (leftIsInsertion !== (right.tableColumnInsertion !== void 0)) return leftIsInsertion ? -1 : 1;
2337
- return right.originalIndex - left.originalIndex;
2432
+ return compareBatchTieOrder(left.originalIndex, right.originalIndex);
2338
2433
  }
2339
2434
  if (left.from !== right.from) return right.from - left.from;
2340
- return right.originalIndex - left.originalIndex;
2435
+ return compareBatchTieOrder(left.originalIndex, right.originalIndex);
2341
2436
  });
2342
2437
  for (let executionIndex = 0; executionIndex < executionOrder.length; executionIndex++) {
2343
2438
  const item = executionOrder[executionIndex];
2344
2439
  if (!item) panic("The operation execution index exceeded the resolved plan", { executionIndex });
2345
2440
  continueSharedRevisionIds();
2346
2441
  if (mode === "tracked-changes" && writesParagraphPropertyChange(item)) {
2347
- const livePosition = tr.mapping.map(item.blockFrom);
2348
- const liveBlock = tr.doc.nodeAt(livePosition) ?? item.blockNode;
2349
- if (hasSerializableParagraphPropertyChange(liveBlock.type.name === "paragraph" ? expectParagraphAttrs(liveBlock)._propertyChanges : void 0)) {
2442
+ const appliedIds = new Set(applied.map(({ id }) => id));
2443
+ const pendingDeletions = deletedFinalParagraphs.filter(({ operationId }) => appliedIds.has(operationId));
2444
+ const targets = paragraphPropertyRevisionTargets({
2445
+ item,
2446
+ tr,
2447
+ deletedFinalParagraphs: pendingDeletions
2448
+ });
2449
+ if (targets.some((target) => hasSerializableParagraphPropertyChange(expectParagraphAttrs(target)._propertyChanges)) || item.operation.type === "mergeBlockWithNext" && hasDeferredParagraphPropertyWrite({
2450
+ tr,
2451
+ deletions: pendingDeletions,
2452
+ targets,
2453
+ mergedPropertyTargets,
2454
+ batchRevisionIds: new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? [])),
2455
+ styleResolver,
2456
+ numbering
2457
+ })) {
2350
2458
  skipped.push({
2351
2459
  id: item.operation.id,
2352
2460
  reason: "pendingParagraphPropertyChange"
@@ -2580,6 +2688,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2580
2688
  case "replaceBlock": {
2581
2689
  if (!isResolvedReplaceBlockOperation(item)) panic("Resolved replaceBlock operation lost its impact discriminator");
2582
2690
  const { changesStyle, changesText } = REPLACE_BLOCK_IMPACT[item.replaceBlockImpact];
2691
+ if (changesStyle && isDeletedPPrMark(tr.doc.nodeAt(tr.mapping.map(item.blockFrom, -1))?.attrs["pPrMark"])) {
2692
+ skipped.push({
2693
+ id: item.operation.id,
2694
+ ...pendingParagraphMarkPropertyRefusal
2695
+ });
2696
+ continue;
2697
+ }
2583
2698
  const revisionIdDelete = operationRevisionSeed;
2584
2699
  const revisionIdInsert = changesText ? operationRevisionSeed + 1 : operationRevisionSeed;
2585
2700
  const revisionIdBackgroundSeed = changesText ? operationRevisionSeed + 2 : operationRevisionSeed;
@@ -3072,9 +3187,21 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3072
3187
  ...isPairedMove(item.operation.moveId) && { moveKind: "moveFrom" },
3073
3188
  ...trackedRevisionExtras
3074
3189
  });
3075
- tr = tr.addMark(item.from, item.to, deletionMark);
3190
+ addTrackedDeletionMark({
3191
+ insertionPolicy: "preserve-pending",
3192
+ tr,
3193
+ from: item.from,
3194
+ to: item.to,
3195
+ mark: deletionMark
3196
+ });
3076
3197
  const atomRanges = undeletedContentAtomRanges(tr, item.blockFrom);
3077
- for (const { from, to } of atomRanges) tr = tr.addMark(from, to, deletionMark);
3198
+ for (const { from, to } of atomRanges) addTrackedDeletionMark({
3199
+ insertionPolicy: "preserve-pending",
3200
+ tr,
3201
+ from,
3202
+ to,
3203
+ mark: deletionMark
3204
+ });
3078
3205
  for (const anchorId of textBoxAnchorIdsBetween(tr.doc, tr.mapping.map(item.blockFrom), tr.mapping.map(item.blockTo))) deletedTextBoxAnchorIds.add(anchorId);
3079
3206
  encloseWholeControls({
3080
3207
  tr,
@@ -3312,6 +3439,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3312
3439
  continue;
3313
3440
  }
3314
3441
  const blockPosition = mapped.pos;
3442
+ if (isDeletedPPrMark(liveBlock.attrs["pPrMark"])) {
3443
+ skipped.push({
3444
+ id: item.operation.id,
3445
+ ...pendingParagraphMarkPropertyRefusal
3446
+ });
3447
+ continue;
3448
+ }
3315
3449
  const appliedProperties = applyBlockParagraphProperties({
3316
3450
  tr,
3317
3451
  position: blockPosition,
@@ -3360,12 +3494,18 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3360
3494
  appliedRevisionIds = [revisionIdMark];
3361
3495
  if (item.to > item.from && deletionType) {
3362
3496
  const revisionIdSeparator = operationRevisionSeed++;
3363
- tr = tr.addMark(item.from, item.to, deletionType.create({
3364
- revisionId: revisionIdSeparator,
3365
- author,
3366
- date,
3367
- ...trackedRevisionExtras
3368
- }));
3497
+ addTrackedDeletionMark({
3498
+ insertionPolicy: "preserve-pending",
3499
+ tr,
3500
+ from: item.from,
3501
+ to: item.to,
3502
+ mark: deletionType.create({
3503
+ revisionId: revisionIdSeparator,
3504
+ author,
3505
+ date,
3506
+ ...trackedRevisionExtras
3507
+ })
3508
+ });
3369
3509
  appliedRevisionIds.push(revisionIdSeparator);
3370
3510
  }
3371
3511
  tr = tr.split(item.from);
@@ -3861,7 +4001,13 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
3861
4001
  if (commentMark) nextTr = nextTr.addMark(inserted.start, inserted.end, commentMark);
3862
4002
  }
3863
4003
  if (item.to > item.from && deletionType) {
3864
- nextTr = nextTr.addMark(item.from, item.to, deletionType.create(delAttrs));
4004
+ addTrackedDeletionMark({
4005
+ insertionPolicy: "preserve-pending",
4006
+ tr: nextTr,
4007
+ from: item.from,
4008
+ to: item.to,
4009
+ mark: deletionType.create(delAttrs)
4010
+ });
3865
4011
  if (commentMark && replacement.length === 0) nextTr = nextTr.addMark(item.from, item.to, commentMark);
3866
4012
  }
3867
4013
  return nextTr;
@@ -4353,7 +4499,13 @@ const applyMinimalTrackedReplacement = ({ tr, item, noteReferences, replacement,
4353
4499
  removedFrom = first.from;
4354
4500
  marks = addCarriedMarks(inheritedReplacementMarks(doc, styleSource.from, styleSource.to), surveyReplacedAnnotations(doc, first.from, last.to).carried);
4355
4501
  for (const piece of change.pieces) {
4356
- nextTr = nextTr.addMark(piece.from, piece.to, deletion);
4502
+ addTrackedDeletionMark({
4503
+ insertionPolicy: "preserve-pending",
4504
+ tr: nextTr,
4505
+ from: piece.from,
4506
+ to: piece.to,
4507
+ mark: deletion
4508
+ });
4357
4509
  if (commentMark) nextTr = nextTr.addMark(piece.from, piece.to, commentMark);
4358
4510
  }
4359
4511
  }
@@ -4889,17 +5041,23 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
4889
5041
  }
4890
5042
  };
4891
5043
  }
4892
- if (operation.type === "setBlockParagraphProperties") return {
4893
- type: "resolved",
4894
- operation: {
4895
- operation,
4896
- from: blockFrom,
4897
- to: blockTo,
4898
- blockFrom,
4899
- blockTo,
4900
- blockNode
4901
- }
4902
- };
5044
+ if (operation.type === "setBlockParagraphProperties") {
5045
+ if (isDeletedPPrMark(blockNode.attrs["pPrMark"])) return {
5046
+ type: "skip",
5047
+ ...pendingParagraphMarkPropertyRefusal
5048
+ };
5049
+ return {
5050
+ type: "resolved",
5051
+ operation: {
5052
+ operation,
5053
+ from: blockFrom,
5054
+ to: blockTo,
5055
+ blockFrom,
5056
+ blockTo,
5057
+ blockNode
5058
+ }
5059
+ };
5060
+ }
4903
5061
  if (operation.type === "splitBlock") {
4904
5062
  const separator = operation.separator ?? "";
4905
5063
  const startOffset = operation.offset;
@@ -4965,6 +5123,10 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
4965
5123
  if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
4966
5124
  const replaceChangesText = operation.type === "replaceBlock" && operation.text !== currentText;
4967
5125
  const replaceChangesStyle = operation.type === "replaceBlock" && operation.styleId !== void 0 && operation.styleId !== (expectParagraphAttrs(blockNode).styleId ?? null);
5126
+ if (replaceChangesStyle && isDeletedPPrMark(blockNode.attrs["pPrMark"])) return {
5127
+ type: "skip",
5128
+ ...pendingParagraphMarkPropertyRefusal
5129
+ };
4968
5130
  if (replaceChangesText && cleanBlock.structuralBoundaries.some((boundary) => boundary.type !== "field" && boundary.type !== "noteReference")) return {
4969
5131
  type: "skip",
4970
5132
  reason: "unsupportedBlock"
@@ -21,6 +21,8 @@
21
21
  * Positions are the ones the batch resolved against. A block is keyed by the
22
22
  * position before its node.
23
23
  */
24
+ /** Equal snapshot coordinates execute in reverse input order. */
25
+ declare const compareBatchTieOrder: (leftIndex: number, rightIndex: number) => number;
24
26
  type PositionRange = {
25
27
  from: number;
26
28
  to: number;
@@ -142,4 +144,4 @@ declare class BatchClaims {
142
144
  add(operationId: string, claim: BatchClaim): void;
143
145
  }
144
146
  //#endregion
145
- export { BatchClaim, BatchClaims };
147
+ export { BatchClaim, BatchClaims, compareBatchTieOrder };
@@ -1,4 +1,28 @@
1
1
  //#region src/ai-edits/batch-claims.ts
2
+ /**
3
+ * Which operations of one batch may not share their targets.
4
+ *
5
+ * A batch resolves every operation against the document as it was read and
6
+ * applies them from the end of the document backwards, so an operation never
7
+ * moves the positions an earlier-placed one resolved to. That holds only while
8
+ * the operations' targets are disjoint. Two operations on the same stretch of
9
+ * text, or an operation inside a paragraph another one deletes, rewrites,
10
+ * splits or merges away, are applied against coordinates the first one has
11
+ * already invalidated — and the second then edits whatever now sits there,
12
+ * which can be a paragraph neither operation named.
13
+ *
14
+ * Each operation therefore states what it claims before anything is applied,
15
+ * and the later of two operations whose claims conflict is refused
16
+ * (`overlappingOperation`). The caller re-reads the document after the batch
17
+ * and sends the refused operation again against the result. What remains
18
+ * applies exactly as the same operations applied one at a time, from the end
19
+ * of the document backwards, would.
20
+ *
21
+ * Positions are the ones the batch resolved against. A block is keyed by the
22
+ * position before its node.
23
+ */
24
+ /** Equal snapshot coordinates execute in reverse input order. */
25
+ const compareBatchTieOrder = (leftIndex, rightIndex) => rightIndex - leftIndex;
2
26
  const blockRolesOf = (claim) => {
3
27
  switch (claim.type) {
4
28
  case "text":
@@ -272,4 +296,4 @@ var BatchClaims = class {
272
296
  }
273
297
  };
274
298
  //#endregion
275
- export { BatchClaims };
299
+ export { BatchClaims, compareBatchTieOrder };
@@ -5,8 +5,7 @@ import { importReferencedStyleDefinitions } from "../compare/style-resources.js"
5
5
  import { applyFolioDocumentOperations } from "../document-operations.js";
6
6
  import { applyReplyThreadMarkers } from "../docx/commentReplyMarkers.js";
7
7
  import { rebindDrawingImageRelationship } from "../docx/drawingRelationships.js";
8
- import { getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
9
- import { getHeaderFooterText } from "../docx/headerFooterParser.js";
8
+ import { isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
10
9
  import { createNumberingIdAllocator } from "../docx/numberingIds.js";
11
10
  import { getCachedNumberingMap } from "../docx/numberingParser.js";
12
11
  import { recreateProseNodeWithParagraphPropertySource, transferProseParagraphPropertySource } from "../docx/paragraphPropertySource.js";
@@ -15,6 +14,7 @@ import { createReply } from "../docx/replyToComment.js";
15
14
  import { repackDocx } from "../docx/rezip.js";
16
15
  import { sectionReferenceHistory } from "../docx/sectionReferenceHistory.js";
17
16
  import { attemptSelectiveSave } from "../docx/selectiveSave.js";
17
+ import { getEndnoteText, getFootnoteText, getHeaderFooterText } from "../docx/storyPlainText.js";
18
18
  import { pluginsForHeadlessRevisionResolution } from "../internal/headlessRevisionResolutionGuard.js";
19
19
  import { withTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
20
20
  import { captureSectionReferenceInventory, resolvedSectionReferenceLosses, withSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
@@ -1,3 +1,4 @@
1
+ import { addTrackedDeletionMark } from "../prosemirror/addTrackedDeletionMark.js";
1
2
  import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
2
3
  import { markRowsEmptiedInBatch, reconcileTableGridAfterColumnInsertion, reconcileTableGridAfterColumnRemoval, removeRowsWithoutCells, removeTableRow } from "../prosemirror/tableGridMutation.js";
3
4
  import { stripBlockIdentityAttrs } from "./block-identity.js";
@@ -130,7 +131,7 @@ const markTableRowContent = ({ tr, rowPosition, kind, revision }) => {
130
131
  if (!node.isInline) return true;
131
132
  if (node.marks.some(isTrackedChangeMark)) {
132
133
  wholeRowMarkable = false;
133
- if (kind === "insertion" || node.marks.some(({ type }) => type.name === "deletion")) return false;
134
+ if (kind === "insertion") return false;
134
135
  }
135
136
  const from = contentFrom + offset;
136
137
  markable.push({
@@ -140,10 +141,23 @@ const markTableRowContent = ({ tr, rowPosition, kind, revision }) => {
140
141
  return false;
141
142
  });
142
143
  if (wholeRowMarkable) {
143
- tr.addMark(contentFrom, rowPosition + row.nodeSize - 1, mark);
144
+ if (kind === "deletion") addTrackedDeletionMark({
145
+ insertionPolicy: "preserve-pending",
146
+ tr,
147
+ from: contentFrom,
148
+ to: rowPosition + row.nodeSize - 1,
149
+ mark
150
+ });
151
+ else tr.addMark(contentFrom, rowPosition + row.nodeSize - 1, mark);
144
152
  return;
145
153
  }
146
- for (const range of markable) tr.addMark(range.from, range.to, mark);
154
+ for (const range of markable) if (kind === "deletion") addTrackedDeletionMark({
155
+ insertionPolicy: "preserve-pending",
156
+ tr,
157
+ ...range,
158
+ mark
159
+ });
160
+ else tr.addMark(range.from, range.to, mark);
147
161
  };
148
162
  const isTrackedChangeMark = (mark) => mark.type.name === "insertion" || mark.type.name === "deletion";
149
163
  const isRowSpanningCell = (node) => {
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { CanonicalGap } from "../types/canonicalCapabilities.js";
2
3
  import { FolioContentBlock, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentListReference, FolioContentParagraphIndentation, FolioContentParagraphKind, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
3
4
  import { ListKind } from "../docx/listNumberingInstances.js";
4
5
  //#region src/ai-edits/types.d.ts
@@ -656,7 +657,9 @@ type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" |
656
657
  * deletion is accepted. Reject that deletion first, or insert a new block
657
658
  * next to it.
658
659
  */
659
- "pendingDeletion";
660
+ "pendingDeletion" |
661
+ /** Resolve the removed paragraph mark before editing its properties separately. */
662
+ "pendingParagraphMarkDeletion";
660
663
  type FolioAIEditAppliedOperation = {
661
664
  id: string;
662
665
  commentId?: number;
@@ -689,6 +692,9 @@ type FolioAIEditAppliedOperation = {
689
692
  type FolioAIEditSkippedOperation = {
690
693
  id: string;
691
694
  reason: FolioAIEditSkipReason;
695
+ canonicalRefusal?: {
696
+ gap: CanonicalGap;
697
+ };
692
698
  /**
693
699
  * What exactly was wrong when the reason alone does not say: the values a
694
700
  * `payloadDoesNotFit` skip could not place, the earlier operation an
@@ -1,6 +1,19 @@
1
+ import { addTrackedDeletionMark } from "../prosemirror/addTrackedDeletionMark.js";
1
2
  import { mintRevisionId, seedRevisionIdsFromDoc } from "../prosemirror/plugins/revisionIds.js";
2
3
  import { resolveSuggestionAnchor } from "./conflict.js";
3
4
  //#region src/ai-suggestions/apply.ts
5
+ /**
6
+ * Apply AI suggestions to the document.
7
+ *
8
+ * Two modes:
9
+ * - "direct": replace text in place.
10
+ * - "tracked-changes": insertion + deletion marks, attributed to the
11
+ * configured author. Reuses the editor's existing tracked-change
12
+ * schema and resolution commands.
13
+ *
14
+ * Apply is always preceded by anchor resolution; stale suggestions are
15
+ * skipped and reported back to the caller.
16
+ */
4
17
  function applySuggestions(options) {
5
18
  const { view, suggestions, mode, author } = options;
6
19
  const applied = [];
@@ -51,7 +64,13 @@ function applySuggestions(options) {
51
64
  tr.insertText(suggestion.suggestedText, to, to);
52
65
  tr.addMark(to, to + suggestion.suggestedText.length, insertionType.create(attrs));
53
66
  }
54
- if (to > from) tr.addMark(from, to, deletionType.create(attrs));
67
+ if (to > from) addTrackedDeletionMark({
68
+ insertionPolicy: "preserve-pending",
69
+ tr,
70
+ from,
71
+ to,
72
+ mark: deletionType.create(attrs)
73
+ });
55
74
  applied.push(suggestion.id);
56
75
  }
57
76
  }
@@ -486,6 +486,7 @@ const COMPARE_SKIP_DISPOSITION = {
486
486
  staleRange: "fatal",
487
487
  emptyOperation: "unwritable",
488
488
  pendingParagraphPropertyChange: "unwritable",
489
+ pendingParagraphMarkDeletion: "unwritable",
489
490
  pendingRunPropertyChange: "unwritable",
490
491
  noopOperation: "unwritable",
491
492
  documentVersionMismatch: "fatal",