@stll/folio-core 0.36.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +59 -1
  2. package/dist/ai-edits/apply.js +689 -367
  3. package/dist/ai-edits/block-identity.d.ts +4 -1
  4. package/dist/ai-edits/block-identity.js +4 -1
  5. package/dist/ai-edits/blockRange.js +14 -14
  6. package/dist/ai-edits/clean-text.d.ts +40 -1
  7. package/dist/ai-edits/clean-text.js +52 -2
  8. package/dist/ai-edits/headless.d.ts +3 -1
  9. package/dist/ai-edits/headless.js +133 -71
  10. package/dist/ai-edits/index.d.ts +3 -3
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.js +38 -20
  13. package/dist/ai-edits/snapshot.d.ts +14 -6
  14. package/dist/ai-edits/snapshot.js +99 -19
  15. package/dist/ai-edits/table-cell-mutations.js +1 -1
  16. package/dist/ai-edits/table-row-column-mutations.js +7 -0
  17. package/dist/ai-edits/table-template.d.ts +11 -1
  18. package/dist/ai-edits/table-template.js +49 -17
  19. package/dist/ai-edits/types.d.ts +60 -57
  20. package/dist/ai-suggestions/conflict.js +1 -4
  21. package/dist/ai-suggestions/text-positions.d.ts +8 -0
  22. package/dist/ai-suggestions/text-positions.js +27 -5
  23. package/dist/compare/__fixtures__/body-sequence.js +3 -4
  24. package/dist/compare/column-alignment.d.ts +13 -13
  25. package/dist/compare/column-alignment.js +78 -39
  26. package/dist/compare/compare.d.ts +4 -3
  27. package/dist/compare/compare.js +41 -5
  28. package/dist/compare/content-alignment.d.ts +95 -0
  29. package/dist/compare/content-alignment.js +1450 -0
  30. package/dist/compare/content-types.d.ts +100 -0
  31. package/dist/compare/content-types.js +0 -0
  32. package/dist/compare/content.d.ts +209 -0
  33. package/dist/compare/content.js +885 -0
  34. package/dist/compare/formatting.d.ts +5 -5
  35. package/dist/compare/formatting.js +6 -4
  36. package/dist/compare/plan.d.ts +6 -1
  37. package/dist/compare/plan.js +201 -569
  38. package/dist/compare/scenario.d.ts +2 -2
  39. package/dist/compare/types.d.ts +3 -3
  40. package/dist/compare/verification.d.ts +2 -2
  41. package/dist/compare/verification.js +25 -8
  42. package/dist/compat/eigenpal.d.ts +5 -3
  43. package/dist/compat/eigenpal.js +3 -2
  44. package/dist/controller/headerFooterEditorManager.js +14 -6
  45. package/dist/controller/hiddenEditorManager.d.ts +7 -2
  46. package/dist/controller/hiddenEditorManager.js +15 -6
  47. package/dist/controller/layoutPipeline.js +5 -3
  48. package/dist/controller/noteEditorManager.js +12 -7
  49. package/dist/display-list/build/headerFooterPrimitives.js +1 -0
  50. package/dist/display-list/build/storyPrimitives.d.ts +5 -2
  51. package/dist/display-list/build/storyPrimitives.js +60 -3
  52. package/dist/display-list/build/tablePrimitives.js +30 -0
  53. package/dist/display-list/primitives.d.ts +1 -1
  54. package/dist/document-operations.d.ts +8 -6
  55. package/dist/document-operations.js +121 -26
  56. package/dist/docx/blockContentParser.js +6 -27
  57. package/dist/docx/ensureParaIds.js +9 -4
  58. package/dist/docx/numberingParser.d.ts +3 -1
  59. package/dist/docx/numberingParser.js +4 -1
  60. package/dist/docx/paragraphParser.js +7 -6
  61. package/dist/docx/paragraphTraversal.js +30 -34
  62. package/dist/docx/rezip.d.ts +0 -8
  63. package/dist/docx/rezip.js +41 -16
  64. package/dist/docx/runParser.js +4 -2
  65. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  66. package/dist/docx/serializer/runSerializer.js +5 -3
  67. package/dist/docx/serializer/trackedChangeAttributes.d.ts +3 -1
  68. package/dist/docx/serializer/trackedChangeAttributes.js +11 -1
  69. package/dist/docx/server/build.d.ts +1 -1
  70. package/dist/headless-layout.js +5 -4
  71. package/dist/index.d.ts +5 -3
  72. package/dist/index.js +3 -2
  73. package/dist/internal/headlessRevisionResolution.d.ts +23 -0
  74. package/dist/internal/headlessRevisionResolution.js +176 -0
  75. package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
  76. package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
  77. package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
  78. package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
  79. package/dist/internal/pageBreakRunPartition.d.ts +26 -0
  80. package/dist/internal/pageBreakRunPartition.js +35 -0
  81. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
  82. package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
  83. package/dist/internal/sectionEndpointResolution.d.ts +32 -0
  84. package/dist/internal/sectionEndpointResolution.js +38 -0
  85. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  86. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
  87. package/dist/layout-bridge/convert/headerFooterLayout.js +48 -5
  88. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
  89. package/dist/layout-bridge/convert/toFlowBlocks.js +379 -75
  90. package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
  91. package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
  92. package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
  93. package/dist/layout-bridge/dom/textStreamDom.js +75 -0
  94. package/dist/layout-bridge/headerFooterLayout.js +16 -30
  95. package/dist/layout-engine/index.js +12 -3
  96. package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
  97. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
  98. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  99. package/dist/layout-engine/types.d.ts +7 -0
  100. package/dist/layout-painter/imageLayout.d.ts +1 -1
  101. package/dist/layout-painter/renderTable.js +20 -0
  102. package/dist/model.d.ts +1 -1
  103. package/dist/paged-layout/rangeProjection.js +6 -9
  104. package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
  105. package/dist/paged-layout/transactionDirtyRange.js +23 -1
  106. package/dist/prosemirror/attrs/index.d.ts +7 -3
  107. package/dist/prosemirror/attrs/index.js +113 -7
  108. package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
  109. package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
  110. package/dist/prosemirror/commands/comments.d.ts +10 -1
  111. package/dist/prosemirror/commands/comments.js +167 -59
  112. package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
  113. package/dist/prosemirror/commands/formatPainter.js +319 -63
  114. package/dist/prosemirror/commands/index.d.ts +2 -2
  115. package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
  116. package/dist/prosemirror/commands/pageBreak.js +18 -36
  117. package/dist/prosemirror/commands/propertyChangeScope.d.ts +7 -5
  118. package/dist/prosemirror/commands/propertyChangeScope.js +27 -18
  119. package/dist/prosemirror/conversion/fromProseDoc.d.ts +7 -2
  120. package/dist/prosemirror/conversion/fromProseDoc.js +809 -209
  121. package/dist/prosemirror/conversion/runShadingMark.js +2 -2
  122. package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
  123. package/dist/prosemirror/conversion/toProseDoc.js +416 -283
  124. package/dist/prosemirror/extensions/StarterKit.js +4 -0
  125. package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -8
  126. package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
  127. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -1
  128. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
  129. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
  130. package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
  131. package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
  132. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
  133. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
  134. package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
  135. package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
  136. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
  137. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
  138. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
  139. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
  140. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
  141. package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
  142. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
  143. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  144. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
  145. package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
  146. package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
  147. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
  148. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
  149. package/dist/prosemirror/findReplaceSelection.js +8 -3
  150. package/dist/prosemirror/index.d.ts +3 -3
  151. package/dist/prosemirror/listMarker.d.ts +26 -1
  152. package/dist/prosemirror/listMarker.js +108 -14
  153. package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
  154. package/dist/prosemirror/pageBreakRunProjection.js +47 -0
  155. package/dist/prosemirror/paraText.js +6 -0
  156. package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
  157. package/dist/prosemirror/paragraphSpacing.js +125 -0
  158. package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
  159. package/dist/prosemirror/plugins/documentNumbering.js +18 -0
  160. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
  161. package/dist/prosemirror/plugins/pmTextScan.js +14 -4
  162. package/dist/prosemirror/plugins/suggestionMode.js +8 -10
  163. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
  164. package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
  165. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
  166. package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
  167. package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
  168. package/dist/prosemirror/runFormattingProvenance.js +48 -0
  169. package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
  170. package/dist/prosemirror/runFormattingReconciliation.js +54 -0
  171. package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
  172. package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
  173. package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
  174. package/dist/prosemirror/runStyleFormatting.js +105 -0
  175. package/dist/prosemirror/schema/index.d.ts +3 -3
  176. package/dist/prosemirror/schema/marks.d.ts +82 -9
  177. package/dist/prosemirror/schema/marks.js +73 -1
  178. package/dist/prosemirror/schema/nodes.d.ts +18 -5
  179. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
  180. package/dist/prosemirror/styles/resolvedStyleAttrs.js +44 -23
  181. package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
  182. package/dist/prosemirror/styles/styleResolver.js +3 -3
  183. package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
  184. package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
  185. package/dist/prosemirror/tableGridMutation.d.ts +20 -0
  186. package/dist/prosemirror/tableGridMutation.js +30 -0
  187. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
  188. package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
  189. package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
  190. package/dist/prosemirror/validation.js +23 -4
  191. package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
  192. package/dist/render-dom/RenderedDomContext.js +17 -34
  193. package/dist/server.d.ts +4 -4
  194. package/dist/server.js +2 -2
  195. package/dist/style-engine/styleEngine.d.ts +1 -1
  196. package/dist/utils/clipboard.js +1 -1
  197. package/dist/utils/findReplace.js +38 -21
  198. package/dist/version-comparison.d.ts +38 -44
  199. package/dist/version-comparison.js +270 -401
  200. package/package.json +3 -2
@@ -1,21 +1,26 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { FolioContentBlock, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
2
3
  //#region src/ai-edits/types.d.ts
3
4
  type FolioAIBlockKind = "heading" | "listItem" | "paragraph";
4
- type FolioAIInlineFormatting = Partial<Record<"bold" | "italic" | "underline" | "strike", boolean>> & {
5
- fontFamily?: string | null;
6
- fontSizePt?: number | null;
7
- color?: string | null;
8
- };
9
- type FolioAIBlockPreviewRun = {
10
- text: string;
11
- bold?: boolean;
12
- italic?: boolean;
13
- underline?: boolean;
14
- strike?: boolean;
15
- fontFamily?: string;
16
- fontSizePt?: number;
17
- color?: string;
18
- directFormatting?: FolioAIInlineFormatting;
5
+ /** Boolean run properties supported by snapshots and range-formatting mutations. */
6
+ type FolioAIInlineBooleanProperty = FolioContentInlineBooleanProperty;
7
+ type FolioAIInlineFormatting = FolioContentInlineFormatting;
8
+ /**
9
+ * A run-formatting mutation: `false` authors an explicit off value, while
10
+ * `null` removes the direct property so its inherited value becomes effective.
11
+ */
12
+ type FolioAIInlineFormattingPatch = FolioContentInlineFormattingPatch;
13
+ type FolioAIBlockPreviewRun = FolioContentRun;
14
+ /**
15
+ * A zero-width inline structure at one clean-text boundary. `pageBreak`
16
+ * always represents an authored `<w:br w:type="page"/>`; an omitted `clear`
17
+ * remains distinct from any explicit value even though it does not affect
18
+ * layout for this break type.
19
+ */
20
+ type FolioAIBlockStructuralBoundary = {
21
+ type: "pageBreak";
22
+ offset: number;
23
+ clear?: document_d_exports.BreakContent["clear"];
19
24
  };
20
25
  /**
21
26
  * Where a block sits inside its innermost enclosing table. Every index is
@@ -25,45 +30,16 @@ type FolioAIBlockPreviewRun = {
25
30
  * not a grid column), and `paragraphIndex` orders the block among the cell's
26
31
  * own blocks. Absent on a block that is not inside a table.
27
32
  */
28
- type FolioAIBlockTableLocation = {
29
- /**
30
- * Document-order index of the OUTERMOST table the block sits in — the same
31
- * as `tableIndex` unless tables nest. A comparison aligns on this: a table
32
- * inside a cell is part of its parent, not a structure of its own that can
33
- * be paired against one somewhere else.
34
- */
35
- outerTableIndex: number;
36
- tableIndex: number;
37
- rowIndex: number;
38
- cellIndex: number;
39
- /** Grid column occupied by the cell's left edge, derived from `TableMap`. */
40
- gridColumnIndex: number;
41
- /** Number of grid columns occupied by this physical cell. */
42
- columnSpan: number;
43
- /** Number of grid rows occupied by this physical cell. */
44
- rowSpan: number;
45
- paragraphIndex: number;
46
- };
47
- type FolioAIBlock = {
48
- id: string;
49
- kind: FolioAIBlockKind;
50
- text: string;
51
- /** One-based heading depth when the block has outline semantics. */
52
- headingLevel?: number;
53
- displayLabel?: string;
54
- styleId?: string;
55
- /** Direct `w:pPr/w:jc`; absent when alignment comes only from a style. */
56
- directAlignment?: document_d_exports.ParagraphAlignment;
57
- /**
58
- * `w:numPr/w:ilvl`: the block's list indent level. Present only on a block
59
- * that carries numbering, and the only pPr property a redline can move
60
- * without touching a word — a demoted list item reads as unchanged text and
61
- * is not.
62
- */
63
- listLevel?: number;
64
- previewRuns?: FolioAIBlockPreviewRun[];
65
- table?: FolioAIBlockTableLocation;
33
+ type FolioAIBlockTableLocation = FolioContentTableLocation;
34
+ type FolioAIBlock = FolioContentBlock<FolioAIBlockKind> & {
35
+ structuralBoundaries?: readonly FolioAIBlockStructuralBoundary[];
66
36
  };
37
+ /**
38
+ * The complete modeled attribute set of one direct `w:pPr/w:spacing` child.
39
+ * Optional fields preserve the distinction between an absent attribute and
40
+ * an explicit zero or false value.
41
+ */
42
+ type FolioAIParagraphSpacing = FolioContentParagraphSpacing;
67
43
  /**
68
44
  * The paragraph properties an operation may set. A subset of `w:pPrChange`'s
69
45
  * scope: properties a comparison can see in a block projection and an agent
@@ -79,6 +55,8 @@ type FolioAIBlockParagraphProperties = {
79
55
  listLevel?: number | null;
80
56
  /** Direct `w:jc`. `null` clears the override and restores style inheritance. */
81
57
  alignment?: document_d_exports.ParagraphAlignment | null;
58
+ /** Direct `w:spacing` attributes. `null` removes the whole direct child. */
59
+ spacing?: FolioAIParagraphSpacing | null;
82
60
  };
83
61
  /**
84
62
  * Every paragraph of one story, blank ones included.
@@ -89,8 +67,7 @@ type FolioAIBlockParagraphProperties = {
89
67
  * document for a person or a model wants only the paragraphs that carry text,
90
68
  * and says so with `isFolioAIContentBlock`.
91
69
  */
92
- type FolioAIEditSnapshot = {
93
- blocks: FolioAIBlock[];
70
+ type FolioAIEditSnapshot = FolioContentSnapshot<FolioAIBlock> & {
94
71
  anchors: Record<string, FolioAIBlockAnchor>;
95
72
  };
96
73
  type FolioAIBlockAnchor = {
@@ -100,6 +77,8 @@ type FolioAIBlockAnchor = {
100
77
  text: string;
101
78
  normalizedText: string;
102
79
  textHash: string;
80
+ /** Hash of zero-width structural boundaries in the clean block view. */
81
+ structuralBoundaryHash: string;
103
82
  hashOccurrenceCount: number;
104
83
  };
105
84
  type FolioAIComment = {
@@ -218,7 +197,7 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
218
197
  id: string;
219
198
  type: "formatRange";
220
199
  range: FolioAITextRangeHandle;
221
- formatting: FolioAIInlineFormatting;
200
+ formatting: FolioAIInlineFormattingPatch;
222
201
  } | {
223
202
  id: string;
224
203
  type: "insertAfterBlock" | "insertBeforeBlock";
@@ -272,6 +251,11 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
272
251
  * from the anchor and lets the inserted paragraph's style decide.
273
252
  */
274
253
  alignment?: document_d_exports.ParagraphAlignment | null;
254
+ /**
255
+ * Direct `w:spacing` for the inserted block. `null` clears spacing
256
+ * copied from the anchor and lets the inserted paragraph's style decide.
257
+ */
258
+ spacing?: FolioAIParagraphSpacing | null;
275
259
  comment?: FolioAIComment;
276
260
  } | {
277
261
  id: string;
@@ -328,6 +312,16 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
328
312
  */
329
313
  separator?: string;
330
314
  blockId: string;
315
+ /**
316
+ * Paragraph properties for the first result. Omitted properties keep
317
+ * the source paragraph's value.
318
+ */
319
+ firstParagraphProperties?: FolioAIBlockParagraphProperties;
320
+ /**
321
+ * Paragraph properties for the second result. Omitted properties keep
322
+ * the source paragraph's value.
323
+ */
324
+ secondParagraphProperties?: FolioAIBlockParagraphProperties;
331
325
  } |
332
326
  /**
333
327
  * Add a whole table next to the anchor block, its rows marked inserted in
@@ -393,6 +387,11 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
393
387
  */
394
388
  separator?: string;
395
389
  blockId: string;
390
+ /**
391
+ * Paragraph properties for the joined result. Omitted properties keep
392
+ * the first paragraph's value.
393
+ */
394
+ mergedParagraphProperties?: FolioAIBlockParagraphProperties;
396
395
  } | {
397
396
  id: string;
398
397
  type: "commentOnBlock";
@@ -483,6 +482,10 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
483
482
  */
484
483
  type FolioAIEditApplyMode = "direct" | "tracked-changes" | "suggested";
485
484
  type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" | "missingFind" | "unsupportedBlock" | "unsupportedMode" | "atomicBatchRejected" | "preconditionFailed" | "staleRange" | "emptyOperation" |
485
+ /** The paragraph already owns the one `w:pPrChange` OOXML permits. */
486
+ "pendingParagraphPropertyChange" |
487
+ /** The affected run already owns the one `w:rPrChange` OOXML permits. */
488
+ "pendingRunPropertyChange" |
486
489
  /**
487
490
  * The operation would not change the document — find equals
488
491
  * replace, or replaceBlock's `text` matches the live block.
@@ -571,4 +574,4 @@ type FolioAIEditApplyResult = {
571
574
  normalizations?: FolioAIEditNormalization[];
572
575
  };
573
576
  //#endregion
574
- export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockParagraphProperties, FolioAIBlockPreviewRun, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAISignatureParty, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult };
577
+ export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockParagraphProperties, FolioAIBlockPreviewRun, FolioAIBlockStructuralBoundary, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineBooleanProperty, FolioAIInlineFormatting, FolioAIInlineFormattingPatch, FolioAIParagraphSpacing, FolioAISignatureParty, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult };
@@ -53,10 +53,7 @@ function resolveSuggestionAnchor(doc, suggestion) {
53
53
  to: point
54
54
  };
55
55
  }
56
- return {
57
- from: positional.pmPositionAt(originalTextStart),
58
- to: positional.pmPositionAt(originalTextEnd - 1) + 1
59
- };
56
+ return positional.pmRangeAt(originalTextStart, originalTextEnd);
60
57
  }
61
58
  return null;
62
59
  }
@@ -10,6 +10,14 @@ type PositionalText = {
10
10
  * preceding block.
11
11
  */
12
12
  pmPositionAt: (textIndex: number) => number;
13
+ /**
14
+ * Resolve a text span without absorbing an explicit zero-width structure.
15
+ * Returns `null` when the span crosses one.
16
+ */
17
+ pmRangeAt: (startTextIndex: number, endTextIndex: number) => {
18
+ from: number;
19
+ to: number;
20
+ } | null;
13
21
  };
14
22
  /**
15
23
  * Walk a slice of the document, returning its text plus a position
@@ -8,9 +8,18 @@ const BLOCK_SEPARATOR = "\n";
8
8
  function buildPositionalText(doc, from = 0, to = doc.content.size) {
9
9
  const chunks = [];
10
10
  const offsets = [];
11
+ const structuralBoundaries = [];
11
12
  let textLength = 0;
12
13
  let lastBlockEnd = null;
13
14
  doc.nodesBetween(from, to, (node, pos) => {
15
+ if (node.type.name === "pageBreakRun") {
16
+ structuralBoundaries.push({
17
+ textOffset: textLength,
18
+ from: pos,
19
+ to: pos + node.nodeSize
20
+ });
21
+ return false;
22
+ }
14
23
  if (node.isText) {
15
24
  const text = node.text ?? "";
16
25
  const startInNode = Math.max(from, pos);
@@ -35,12 +44,25 @@ function buildPositionalText(doc, from = 0, to = doc.content.size) {
35
44
  }
36
45
  return true;
37
46
  });
47
+ const text = chunks.join("");
48
+ const pmPositionAt = (textIndex) => {
49
+ if (textIndex < 0) return offsets[0] ?? from;
50
+ if (textIndex >= offsets.length) return (offsets.at(-1) ?? from) + 1;
51
+ return offsets[textIndex] ?? from;
52
+ };
38
53
  return {
39
- text: chunks.join(""),
40
- pmPositionAt: (textIndex) => {
41
- if (textIndex < 0) return offsets[0] ?? from;
42
- if (textIndex >= offsets.length) return (offsets.at(-1) ?? from) + 1;
43
- return offsets[textIndex] ?? from;
54
+ text,
55
+ pmPositionAt,
56
+ pmRangeAt: (startTextIndex, endTextIndex) => {
57
+ if (!Number.isInteger(startTextIndex) || !Number.isInteger(endTextIndex) || startTextIndex < 0 || endTextIndex <= startTextIndex || endTextIndex > text.length || structuralBoundaries.some(({ textOffset }) => textOffset > startTextIndex && textOffset < endTextIndex)) return null;
58
+ const rangeFrom = pmPositionAt(startTextIndex);
59
+ const finalCharacter = offsets[endTextIndex - 1];
60
+ if (finalCharacter === void 0) return null;
61
+ const rangeTo = finalCharacter + 1;
62
+ return rangeFrom <= rangeTo ? {
63
+ from: rangeFrom,
64
+ to: rangeTo
65
+ } : null;
44
66
  }
45
67
  };
46
68
  }
@@ -88,10 +88,9 @@ const EMPTY_PARAGRAPH = {
88
88
  text: ""
89
89
  };
90
90
  /**
91
- * A container may not end with a table: the format requires a paragraph after
92
- * one, and a body's section properties do not supply it. One rule for both
93
- * containers, because a fixture that is well formed in a cell and malformed in
94
- * the body would be measuring two different things.
91
+ * Most fixtures add a paragraph after a table so later paragraph insertions
92
+ * have an explicit anchor. Tests for a schema-valid terminal table remove that
93
+ * convenience paragraph deliberately.
95
94
  */
96
95
  const closedSequence = (items) => {
97
96
  const last = items.at(-1);
@@ -1,27 +1,27 @@
1
- import { FolioAIBlock, FolioAIBlockTableLocation } from "../ai-edits/types.js";
1
+ import { FolioContentBlock, FolioContentTableLocation } from "./content-types.js";
2
2
  //#region src/compare/column-alignment.d.ts
3
- type TableColumnAlignmentStep = {
3
+ type TableColumnAlignmentStep<Block extends FolioContentBlock = FolioContentBlock> = {
4
4
  type: "baseColumn";
5
- blocks: readonly FolioAIBlock[];
6
- location: FolioAIBlockTableLocation;
5
+ blocks: readonly Block[];
6
+ location: FolioContentTableLocation;
7
7
  columnIndex: number;
8
8
  } | {
9
- type: "targetColumn";
10
- blocks: readonly FolioAIBlock[];
11
- location: FolioAIBlockTableLocation;
9
+ type: "revisedColumn";
10
+ blocks: readonly Block[];
11
+ location: FolioContentTableLocation;
12
12
  columnIndex: number;
13
13
  anchor: {
14
14
  blockId: string;
15
15
  position: "after" | "before";
16
16
  };
17
17
  };
18
- type TableColumnAlignment = {
19
- steps: TableColumnAlignmentStep[];
20
- baseBlocks: FolioAIBlock[];
21
- targetBlocks: FolioAIBlock[];
18
+ type TableColumnAlignment<Block extends FolioContentBlock = FolioContentBlock> = {
19
+ steps: TableColumnAlignmentStep<Block>[];
20
+ baseBlocks: Block[];
21
+ revisedBlocks: Block[];
22
22
  baseColumnKeys: ReadonlyMap<number, number>;
23
- targetColumnKeys: ReadonlyMap<number, number>;
23
+ revisedColumnKeys: ReadonlyMap<number, number>;
24
24
  };
25
- declare const alignTableColumns: (baseBlocks: readonly FolioAIBlock[], targetBlocks: readonly FolioAIBlock[]) => TableColumnAlignment | null;
25
+ declare const alignTableColumns: <Block extends FolioContentBlock>(baseBlocks: readonly Block[], revisedBlocks: readonly Block[]) => TableColumnAlignment<Block> | null;
26
26
  //#endregion
27
27
  export { TableColumnAlignment, TableColumnAlignmentStep, alignTableColumns };
@@ -2,7 +2,7 @@ import { panic } from "better-result";
2
2
  //#region src/compare/column-alignment.ts
3
3
  const MAX_TABLE_GRID_COLUMNS = 63;
4
4
  const MAX_TABLE_GRID_AREA = 1e6;
5
- const tableGridColumns = (blocks) => {
5
+ const extractTableGrid = (blocks) => {
6
6
  const cellsByPhysicalLocation = /* @__PURE__ */ new Map();
7
7
  let width = 0;
8
8
  let height = 0;
@@ -17,7 +17,7 @@ const tableGridColumns = (blocks) => {
17
17
  }
18
18
  const right = location.gridColumnIndex + location.columnSpan;
19
19
  const bottom = location.rowIndex + location.rowSpan;
20
- if (location.gridColumnIndex < 0 || location.rowIndex < 0 || location.columnSpan < 1 || location.rowSpan < 1 || right > MAX_TABLE_GRID_COLUMNS) return null;
20
+ if (!Number.isSafeInteger(location.gridColumnIndex) || !Number.isSafeInteger(location.rowIndex) || !Number.isSafeInteger(location.columnSpan) || !Number.isSafeInteger(location.rowSpan) || !Number.isSafeInteger(right) || !Number.isSafeInteger(bottom) || location.gridColumnIndex < 0 || location.rowIndex < 0 || location.columnSpan < 1 || location.rowSpan < 1 || right > MAX_TABLE_GRID_COLUMNS) return null;
21
21
  cellsByPhysicalLocation.set(key, {
22
22
  blocks: [block],
23
23
  rowIndex: location.rowIndex,
@@ -29,30 +29,58 @@ const tableGridColumns = (blocks) => {
29
29
  height = Math.max(height, bottom);
30
30
  }
31
31
  if (width === 0 || height === 0 || width * height > MAX_TABLE_GRID_AREA) return null;
32
- const grid = Array.from({ length: height }, () => Array.from({ length: width }));
33
- const ownedCellsByColumn = Array.from({ length: width }, () => []);
34
- for (const cell of cellsByPhysicalLocation.values()) {
35
- ownedCellsByColumn[cell.gridColumnIndex]?.push(cell);
36
- for (let row = cell.rowIndex; row < cell.rowIndex + cell.rowSpan; row++) for (let column = cell.gridColumnIndex; column < cell.gridColumnIndex + cell.columnSpan; column++) {
37
- const gridRow = grid[row];
38
- if (!gridRow || gridRow[column] !== void 0) return null;
39
- gridRow[column] = cell;
32
+ const occupancyEvents = [...cellsByPhysicalLocation.values()].flatMap((cell) => {
33
+ const mask = (1n << BigInt(cell.columnSpan)) - 1n << BigInt(cell.gridColumnIndex);
34
+ return [{
35
+ rowIndex: cell.rowIndex,
36
+ type: "enter",
37
+ mask
38
+ }, {
39
+ rowIndex: cell.rowIndex + cell.rowSpan,
40
+ type: "leave",
41
+ mask
42
+ }];
43
+ });
44
+ occupancyEvents.sort((left, right) => {
45
+ const rowOrder = left.rowIndex - right.rowIndex;
46
+ if (rowOrder !== 0 || left.type === right.type) return rowOrder;
47
+ return left.type === "leave" ? -1 : 1;
48
+ });
49
+ let occupiedColumns = 0n;
50
+ for (const event of occupancyEvents) {
51
+ if (event.type === "leave") {
52
+ occupiedColumns &= ~event.mask;
53
+ continue;
40
54
  }
55
+ if ((occupiedColumns & event.mask) !== 0n) return null;
56
+ occupiedColumns |= event.mask;
41
57
  }
42
- return Array.from({ length: width }, (_unused, columnIndex) => {
43
- const structuralRows = grid.map((row) => {
44
- const cell = row[columnIndex];
45
- return cell ? [
46
- columnIndex - cell.gridColumnIndex,
47
- cell.columnSpan,
48
- cell.rowIndex,
49
- cell.rowSpan,
50
- cell.blocks.map(({ text }) => text)
51
- ] : null;
52
- });
58
+ return {
59
+ cells: [...cellsByPhysicalLocation.values()],
60
+ width,
61
+ height
62
+ };
63
+ };
64
+ const tableGridColumns = (grid, internText) => {
65
+ const coveringCellsByColumn = Array.from({ length: grid.width }, () => []);
66
+ const ownedCellsByColumn = Array.from({ length: grid.width }, () => []);
67
+ const textKeysByCell = /* @__PURE__ */ new Map();
68
+ for (const cell of grid.cells) {
69
+ textKeysByCell.set(cell, cell.blocks.map(({ text }) => internText(text)));
70
+ ownedCellsByColumn[cell.gridColumnIndex]?.push(cell);
71
+ for (let column = cell.gridColumnIndex; column < cell.gridColumnIndex + cell.columnSpan; column++) coveringCellsByColumn[column]?.push(cell);
72
+ }
73
+ return Array.from({ length: grid.width }, (_unused, columnIndex) => {
74
+ const structuralCells = [...coveringCellsByColumn[columnIndex] ?? []].toSorted((left, right) => left.rowIndex - right.rowIndex).map((cell) => [
75
+ columnIndex - cell.gridColumnIndex,
76
+ cell.columnSpan,
77
+ cell.rowIndex,
78
+ cell.rowSpan,
79
+ textKeysByCell.get(cell) ?? panic("A table cell has no interned text signature")
80
+ ]);
53
81
  return {
54
82
  index: columnIndex,
55
- signature: JSON.stringify(structuralRows),
83
+ signature: JSON.stringify([grid.height, structuralCells]),
56
84
  ownedCells: ownedCellsByColumn[columnIndex] ?? []
57
85
  };
58
86
  });
@@ -90,14 +118,25 @@ const columnOwnedBlocks = ({ ownedCells }) => {
90
118
  if (ownedCells.length === 0 || ownedCells.some(({ columnSpan }) => columnSpan !== 1)) return null;
91
119
  return ownedCells.flatMap(({ blocks }) => blocks);
92
120
  };
93
- const alignTableColumns = (baseBlocks, targetBlocks) => {
94
- const baseColumns = tableGridColumns(baseBlocks);
95
- const targetColumns = tableGridColumns(targetBlocks);
96
- if (!baseColumns || !targetColumns || baseColumns.length === targetColumns.length) return null;
97
- const targetIsWider = targetColumns.length > baseColumns.length;
98
- const mapping = uniqueColumnEmbedding(targetIsWider ? baseColumns : targetColumns, targetIsWider ? targetColumns : baseColumns);
121
+ const alignTableColumns = (baseBlocks, revisedBlocks) => {
122
+ const baseGrid = extractTableGrid(baseBlocks);
123
+ const revisedGrid = extractTableGrid(revisedBlocks);
124
+ if (!baseGrid || !revisedGrid || baseGrid.width === revisedGrid.width) return null;
125
+ const textKeys = /* @__PURE__ */ new Map();
126
+ let nextTextKey = 0;
127
+ const internText = (text) => {
128
+ const existing = textKeys.get(text);
129
+ if (existing !== void 0) return existing;
130
+ const key = nextTextKey++;
131
+ textKeys.set(text, key);
132
+ return key;
133
+ };
134
+ const baseColumns = tableGridColumns(baseGrid, internText);
135
+ const revisedColumns = tableGridColumns(revisedGrid, internText);
136
+ const revisedIsWider = revisedColumns.length > baseColumns.length;
137
+ const mapping = uniqueColumnEmbedding(revisedIsWider ? baseColumns : revisedColumns, revisedIsWider ? revisedColumns : baseColumns);
99
138
  if (!mapping) return null;
100
- const widerColumns = targetIsWider ? targetColumns : baseColumns;
139
+ const widerColumns = revisedIsWider ? revisedColumns : baseColumns;
101
140
  const mapped = new Set(mapping);
102
141
  const unmatchedColumns = widerColumns.filter((_column, index) => !mapped.has(index));
103
142
  const ownedColumns = [];
@@ -109,21 +148,21 @@ const alignTableColumns = (baseBlocks, targetBlocks) => {
109
148
  blocks
110
149
  });
111
150
  }
112
- if (ownedColumns.length === 0 || targetIsWider && unmatchedColumns.some(({ ownedCells }) => ownedCells.some(({ rowSpan }) => rowSpan !== 1))) return null;
151
+ if (ownedColumns.length === 0 || revisedIsWider && unmatchedColumns.some(({ ownedCells }) => ownedCells.some(({ rowSpan }) => rowSpan !== 1))) return null;
113
152
  const unmatchedIds = new Set(ownedColumns.flatMap(({ blocks }) => blocks.map(({ id }) => id)));
114
153
  const baseColumnKeys = /* @__PURE__ */ new Map();
115
- const targetColumnKeys = /* @__PURE__ */ new Map();
154
+ const revisedColumnKeys = /* @__PURE__ */ new Map();
116
155
  mapping.forEach((wideIndex, shortIndex) => {
117
- if (targetIsWider) {
156
+ if (revisedIsWider) {
118
157
  baseColumnKeys.set(shortIndex, shortIndex);
119
- targetColumnKeys.set(wideIndex, shortIndex);
158
+ revisedColumnKeys.set(wideIndex, shortIndex);
120
159
  } else {
121
160
  baseColumnKeys.set(wideIndex, shortIndex);
122
- targetColumnKeys.set(shortIndex, shortIndex);
161
+ revisedColumnKeys.set(shortIndex, shortIndex);
123
162
  }
124
163
  });
125
164
  const steps = [];
126
- if (!targetIsWider) for (const { column, blocks } of ownedColumns) {
165
+ if (!revisedIsWider) for (const { column, blocks } of ownedColumns) {
127
166
  const location = blocks.at(0)?.table;
128
167
  if (!location) return null;
129
168
  steps.push({
@@ -140,7 +179,7 @@ const alignTableColumns = (baseBlocks, targetBlocks) => {
140
179
  const anchorBlock = (rightBaseIndex >= 0 ? baseColumns[rightBaseIndex] : baseColumns[leftBaseIndex])?.ownedCells.at(0)?.blocks.at(0);
141
180
  if (!location || !anchorBlock) return null;
142
181
  steps.push({
143
- type: "targetColumn",
182
+ type: "revisedColumn",
144
183
  blocks,
145
184
  location,
146
185
  columnIndex: column.index,
@@ -152,10 +191,10 @@ const alignTableColumns = (baseBlocks, targetBlocks) => {
152
191
  }
153
192
  return {
154
193
  steps,
155
- baseBlocks: baseBlocks.filter(({ id }) => targetIsWider || !unmatchedIds.has(id)),
156
- targetBlocks: targetBlocks.filter(({ id }) => !targetIsWider || !unmatchedIds.has(id)),
194
+ baseBlocks: baseBlocks.filter(({ id }) => revisedIsWider || !unmatchedIds.has(id)),
195
+ revisedBlocks: revisedBlocks.filter(({ id }) => !revisedIsWider || !unmatchedIds.has(id)),
157
196
  baseColumnKeys,
158
- targetColumnKeys
197
+ revisedColumnKeys
159
198
  };
160
199
  };
161
200
  //#endregion
@@ -1,4 +1,4 @@
1
- import { FolioAIEditSnapshot } from "../ai-edits/types.js";
1
+ import { FolioAIEditSkipReason, FolioAIEditSnapshot } from "../ai-edits/types.js";
2
2
  import { WordDiffGranularity } from "../ai-edits/word-diff.js";
3
3
  import { FolioRevisionStamp } from "../ai-edits/apply.js";
4
4
  import { FolioDocumentStoryHandle, FolioDocxReviewer } from "../ai-edits/headless.js";
@@ -50,7 +50,8 @@ type PlannedStoryComparison = {
50
50
  * Stage 2: align every paired story and derive its operations. Pure — no
51
51
  * parsing, no serialization, no clock.
52
52
  */
53
- declare const planComparison: ({ pairs }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
53
+ declare const planComparison: ({ pairs, targetReviewer }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
54
+ declare const getCompareSkipDisposition: (reason: FolioAIEditSkipReason) => "fatal" | "unwritable";
54
55
  /** What stage 3 produced: the change list, whether it was proven, and whether it wrote anything. */
55
56
  type AppliedComparison = {
56
57
  changes: readonly CompareChange[];
@@ -111,4 +112,4 @@ declare const serializeComparison: ({ baseBuffer, baseCarriedRevisions, reviewer
111
112
  */
112
113
  declare const compareDocx: (base: ArrayBuffer, target: ArrayBuffer, options: CompareDocxOptions) => Promise<Result<CompareResult, CompareDocxError>>;
113
114
  //#endregion
114
- export { AppliedComparison, ComparedStoryPair, MAX_COMPARE_OPERATIONS, ParsedComparison, PlannedStoryComparison, applyComparison, compareDocx, parseComparison, planComparison, serializeComparison };
115
+ export { AppliedComparison, ComparedStoryPair, MAX_COMPARE_OPERATIONS, ParsedComparison, PlannedStoryComparison, applyComparison, compareDocx, getCompareSkipDisposition, parseComparison, planComparison, serializeComparison };
@@ -1,9 +1,11 @@
1
1
  import { FolioDocxReviewer } from "../ai-edits/headless.js";
2
2
  import { numberingReferenceKeysOf } from "../ai-edits/snapshot.js";
3
3
  import { projectTableGeometry } from "../ai-edits/table-geometry.js";
4
+ import { tableTemplateCanCrossPackageLosslessly } from "../ai-edits/table-template.js";
4
5
  import { createScopedWordDiffOptions } from "../ai-edits/word-diff.js";
5
6
  import "../document-operations.js";
6
7
  import { pairFolioDocumentStories } from "../document-stories.js";
8
+ import { createContentComparisonWorkSession } from "./content.js";
7
9
  import { planStoryCompare } from "./plan.js";
8
10
  import { withFixedPackageDates } from "./reproducible-package.js";
9
11
  import { CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "./types.js";
@@ -215,19 +217,49 @@ const parseComparison = async (base, target, options) => {
215
217
  unsupported
216
218
  });
217
219
  };
220
+ const planCopiesNonPortableWholeTable = (targetReviewer, pair, plan) => {
221
+ const targetTables = new Map(targetReviewer.storyTables({ story: pair.targetStory }).map(({ index, node }) => [index, node]));
222
+ return plan.tableTemplates.some(({ targetRowIndex, targetTableIndex }) => {
223
+ if (targetRowIndex !== void 0) return false;
224
+ const table = targetTables.get(targetTableIndex);
225
+ return table === void 0 || !tableTemplateCanCrossPackageLosslessly(table);
226
+ });
227
+ };
218
228
  /**
219
229
  * Stage 2: align every paired story and derive its operations. Pure — no
220
230
  * parsing, no serialization, no clock.
221
231
  */
222
- const planComparison = ({ pairs }) => {
232
+ const planComparison = ({ pairs, targetReviewer }) => {
223
233
  const planned = [];
234
+ const workSession = createContentComparisonWorkSession();
235
+ let remainingOperations = MAX_COMPARE_OPERATIONS;
224
236
  for (const pair of pairs) {
225
- const plan = planStoryCompare({
237
+ const remainingLcsCells = workSession.alignment.remainingLcsCells;
238
+ const remainingStructuralTokenLookups = workSession.alignment.remainingStructuralTokenLookups;
239
+ const remainingMoveComparisons = workSession.remainingMoveComparisons;
240
+ const remainingMoveTokenLookups = workSession.remainingMoveTokenLookups;
241
+ let plan = planStoryCompare({
226
242
  story: pair.baseStory,
227
243
  baseSnapshot: pair.baseSnapshot,
228
244
  targetSnapshot: pair.targetSnapshot,
229
- maxOperations: MAX_COMPARE_OPERATIONS
245
+ maxOperations: remainingOperations,
246
+ wholeTableReplacement: "allow",
247
+ workSession
230
248
  });
249
+ if (plan && planCopiesNonPortableWholeTable(targetReviewer, pair, plan)) {
250
+ workSession.alignment.remainingLcsCells = remainingLcsCells;
251
+ workSession.alignment.remainingStructuralTokenLookups = remainingStructuralTokenLookups;
252
+ workSession.remainingMoveComparisons = remainingMoveComparisons;
253
+ workSession.remainingMoveTokenLookups = remainingMoveTokenLookups;
254
+ plan = planStoryCompare({
255
+ story: pair.baseStory,
256
+ baseSnapshot: pair.baseSnapshot,
257
+ targetSnapshot: pair.targetSnapshot,
258
+ maxOperations: remainingOperations,
259
+ wholeTableReplacement: "avoid",
260
+ workSession
261
+ });
262
+ }
231
263
  if (plan === null) return Result.err(new CompareDocxOperationLimitError({
232
264
  message: "The comparison needs more operations than the engine generates.",
233
265
  limit: MAX_COMPARE_OPERATIONS
@@ -236,6 +268,7 @@ const planComparison = ({ pairs }) => {
236
268
  pair,
237
269
  plan
238
270
  });
271
+ remainingOperations -= plan.operations.length;
239
272
  }
240
273
  return Result.ok(planned);
241
274
  };
@@ -266,10 +299,13 @@ const COMPARE_SKIP_DISPOSITION = {
266
299
  preconditionFailed: "fatal",
267
300
  staleRange: "fatal",
268
301
  emptyOperation: "unwritable",
302
+ pendingParagraphPropertyChange: "unwritable",
303
+ pendingRunPropertyChange: "unwritable",
269
304
  noopOperation: "unwritable",
270
305
  documentVersionMismatch: "fatal",
271
306
  documentNotEditable: "fatal"
272
307
  };
308
+ const getCompareSkipDisposition = (reason) => COMPARE_SKIP_DISPOSITION[reason];
273
309
  /**
274
310
  * The table each `insertTable` / `insertTableRow` in the plan should place,
275
311
  * resolved against the target document the plan named it in.
@@ -354,7 +390,7 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
354
390
  if (nextRevisionId === void 0) panic("The applier did not report where it left the revision-id counter", { story: pair.baseStory });
355
391
  idSeed = nextRevisionId;
356
392
  documentChanged = true;
357
- const refused = skipped.filter(({ reason }) => COMPARE_SKIP_DISPOSITION[reason] === "fatal");
393
+ const refused = skipped.filter(({ reason }) => getCompareSkipDisposition(reason) === "fatal");
358
394
  if (refused.length > 0) return Result.err(new CompareDocxApplyError({
359
395
  message: "Some derived operations were refused, so the result would not match the target.",
360
396
  skipped: refused
@@ -510,4 +546,4 @@ const compareDocx = async (base, target, options) => {
510
546
  });
511
547
  };
512
548
  //#endregion
513
- export { MAX_COMPARE_OPERATIONS, applyComparison, compareDocx, parseComparison, planComparison, serializeComparison };
549
+ export { MAX_COMPARE_OPERATIONS, applyComparison, compareDocx, getCompareSkipDisposition, parseComparison, planComparison, serializeComparison };