@stll/folio-core 0.52.0 → 0.53.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 (307) hide show
  1. package/README.md +23 -0
  2. package/dist/ai-edits/apply.d.ts +26 -1
  3. package/dist/ai-edits/apply.js +1519 -105
  4. package/dist/ai-edits/batch-claims.d.ts +130 -0
  5. package/dist/ai-edits/batch-claims.js +244 -0
  6. package/dist/ai-edits/blockRange.js +9 -3
  7. package/dist/ai-edits/character-boundaries.d.ts +43 -0
  8. package/dist/ai-edits/character-boundaries.js +95 -0
  9. package/dist/ai-edits/clean-text.d.ts +33 -3
  10. package/dist/ai-edits/clean-text.js +52 -6
  11. package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
  12. package/dist/ai-edits/comment-lifecycle.js +52 -0
  13. package/dist/ai-edits/headless.d.ts +71 -7
  14. package/dist/ai-edits/headless.js +381 -41
  15. package/dist/ai-edits/index.d.ts +2 -1
  16. package/dist/ai-edits/index.js +2 -1
  17. package/dist/ai-edits/minimal-replacement.d.ts +21 -6
  18. package/dist/ai-edits/minimal-replacement.js +134 -15
  19. package/dist/ai-edits/newListNumbering.d.ts +23 -0
  20. package/dist/ai-edits/newListNumbering.js +94 -0
  21. package/dist/ai-edits/note-references.d.ts +96 -0
  22. package/dist/ai-edits/note-references.js +139 -0
  23. package/dist/ai-edits/pending-suggestions.d.ts +79 -0
  24. package/dist/ai-edits/pending-suggestions.js +306 -0
  25. package/dist/ai-edits/read.d.ts +1 -1
  26. package/dist/ai-edits/read.js +56 -43
  27. package/dist/ai-edits/result-validation.d.ts +29 -0
  28. package/dist/ai-edits/result-validation.js +164 -0
  29. package/dist/ai-edits/snapshot.d.ts +36 -3
  30. package/dist/ai-edits/snapshot.js +177 -23
  31. package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
  32. package/dist/ai-edits/table-cell-mutations.js +4 -1
  33. package/dist/ai-edits/table-geometry.d.ts +1 -1
  34. package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
  35. package/dist/ai-edits/table-row-column-mutations.js +61 -26
  36. package/dist/ai-edits/types.d.ts +155 -16
  37. package/dist/compare/compare.d.ts +1 -1
  38. package/dist/compare/compare.js +65 -16
  39. package/dist/compare/content-alignment.js +13 -2
  40. package/dist/compare/content-types.d.ts +7 -0
  41. package/dist/compare/content.d.ts +4 -1
  42. package/dist/compare/content.js +6 -1
  43. package/dist/compare/formatting.js +2 -1
  44. package/dist/compare/inline-atoms.d.ts +21 -4
  45. package/dist/compare/inline-atoms.js +138 -16
  46. package/dist/compare/plan.js +10 -5
  47. package/dist/compare/scenario.js +14 -3
  48. package/dist/compare/section-boundary-properties.js +1 -1
  49. package/dist/compare/types.d.ts +4 -2
  50. package/dist/compare/types.js +2 -1
  51. package/dist/compare/verification.d.ts +1 -1
  52. package/dist/compare/verification.js +15 -3
  53. package/dist/controller/committedLayoutDiff.d.ts +25 -0
  54. package/dist/controller/committedLayoutDiff.js +71 -0
  55. package/dist/controller/contentControlWidgetController.d.ts +1 -1
  56. package/dist/controller/folioEditor.d.ts +1 -1
  57. package/dist/controller/fontReadiness.d.ts +70 -3
  58. package/dist/controller/fontReadiness.js +100 -10
  59. package/dist/controller/headerFooterEditorManager.js +6 -1
  60. package/dist/controller/layoutPipeline.d.ts +12 -10
  61. package/dist/controller/layoutPipeline.js +62 -9
  62. package/dist/controller/layoutRunOptions.d.ts +24 -0
  63. package/dist/controller/layoutRunOptions.js +17 -0
  64. package/dist/controller/layoutScheduler.d.ts +19 -14
  65. package/dist/controller/layoutScheduler.js +15 -12
  66. package/dist/controller/layoutSession.d.ts +26 -2
  67. package/dist/controller/layoutSession.js +1 -1
  68. package/dist/controller/noteEditorManager.js +8 -3
  69. package/dist/document-operations.d.ts +11 -2
  70. package/dist/document-operations.js +230 -21
  71. package/dist/docx/altChunk.d.ts +8 -0
  72. package/dist/docx/altChunk.js +17 -0
  73. package/dist/docx/attributeRemainder.d.ts +1 -1
  74. package/dist/docx/blockContentParser.d.ts +64 -1
  75. package/dist/docx/blockContentParser.js +17 -4
  76. package/dist/docx/blockCustomXmlShell.d.ts +7 -0
  77. package/dist/docx/blockCustomXmlShell.js +14 -0
  78. package/dist/docx/blockPlainText.js +7 -0
  79. package/dist/docx/commentAnchorIndex.js +1 -1
  80. package/dist/docx/commentRangeIntegrity.js +8 -3
  81. package/dist/docx/commentReferenceNormalization.js +1 -1
  82. package/dist/docx/commentReplyMarkers.js +20 -1
  83. package/dist/docx/compatibility.d.ts +1 -1
  84. package/dist/docx/compatibility.js +34 -13
  85. package/dist/docx/containerChildren.gen.d.ts +1 -1
  86. package/dist/docx/containerChildren.gen.js +1 -0
  87. package/dist/docx/documentParser.js +12 -12
  88. package/dist/docx/drawingIdNormalization.d.ts +3 -1
  89. package/dist/docx/drawingIdNormalization.js +6 -1
  90. package/dist/docx/ensureParaIds.js +119 -97
  91. package/dist/docx/footnoteParser.js +8 -4
  92. package/dist/docx/headerFooterParser.js +3 -3
  93. package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
  94. package/dist/docx/headerFooterReferenceNormalization.js +52 -27
  95. package/dist/docx/hyperlinkParser.d.ts +1 -1
  96. package/dist/docx/index.d.ts +1 -1
  97. package/dist/docx/listNumberingInstances.d.ts +59 -0
  98. package/dist/docx/listNumberingInstances.js +249 -0
  99. package/dist/docx/normalizeBaseDirection.js +2 -1
  100. package/dist/docx/opaqueCarrier.d.ts +7 -0
  101. package/dist/docx/opaqueCarrier.js +38 -0
  102. package/dist/docx/packageParts.js +4 -1
  103. package/dist/docx/paragraphParser.js +91 -24
  104. package/dist/docx/paragraphPropertySource.d.ts +4 -2
  105. package/dist/docx/paragraphPropertySource.js +7 -1
  106. package/dist/docx/paragraphTraversal.js +1 -0
  107. package/dist/docx/parseWarningMessage.js +4 -1
  108. package/dist/docx/parser.js +173 -20
  109. package/dist/docx/rezip.js +27 -5
  110. package/dist/docx/sdtProperties.js +1 -1
  111. package/dist/docx/sdtPropertiesPatch.js +3 -0
  112. package/dist/docx/selectiveSave.js +16 -9
  113. package/dist/docx/selectiveXmlPatch.js +106 -37
  114. package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
  115. package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
  116. package/dist/docx/serializer/documentSerializer.js +2 -0
  117. package/dist/docx/serializer/headerFooterSerializer.js +2 -0
  118. package/dist/docx/serializer/noteSerializer.js +3 -1
  119. package/dist/docx/serializer/paragraphSerializer.js +33 -9
  120. package/dist/docx/serializer/tableSerializer.js +25 -38
  121. package/dist/docx/server/createBilingualDocument.js +3 -3
  122. package/dist/docx/tableParser.d.ts +157 -1
  123. package/dist/docx/tableParser.js +89 -10
  124. package/dist/docx/textBoxParser.js +1 -1
  125. package/dist/docx/unzip.d.ts +2 -1
  126. package/dist/docx/unzip.js +1 -1
  127. package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
  128. package/dist/docx/wordprocessingPrefixes.js +286 -0
  129. package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
  130. package/dist/i18n/messages/catalogs.gen.js +153 -17
  131. package/dist/i18n/messages/messages.gen.d.ts +8 -0
  132. package/dist/internal/compare/inline-presentation.d.ts +2 -1
  133. package/dist/internal/compare/inline-presentation.js +11 -10
  134. package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
  135. package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
  136. package/dist/internal/indexedPositionMap.d.ts +11 -0
  137. package/dist/internal/indexedPositionMap.js +42 -0
  138. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  139. package/dist/internal/revisionResolutionEdits.d.ts +13 -0
  140. package/dist/internal/revisionResolutionEdits.js +19 -0
  141. package/dist/internal/revisionResolutionInline.d.ts +24 -0
  142. package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
  143. package/dist/internal/revisionResolutionStep.d.ts +34 -0
  144. package/dist/internal/revisionResolutionStep.js +282 -0
  145. package/dist/internal/revisionResolutionTracking.d.ts +11 -0
  146. package/dist/internal/revisionResolutionTracking.js +124 -0
  147. package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
  148. package/dist/internal/wholeStoryRevisionResolution.js +530 -0
  149. package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
  150. package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
  151. package/dist/layout-bridge/convert/tableConversion.js +1 -1
  152. package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
  153. package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
  154. package/dist/layout-engine/layoutInstrumentation.js +4 -1
  155. package/dist/layout-engine/measure/cache.d.ts +18 -1
  156. package/dist/layout-engine/measure/cache.js +31 -1
  157. package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
  158. package/dist/layout-engine/measure/measureContainer.js +7 -47
  159. package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
  160. package/dist/layout-engine/measure/measureHelpers.js +22 -2
  161. package/dist/layout-engine/measure/measureWorker.js +2 -3
  162. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
  163. package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
  164. package/dist/layout-engine/types.d.ts +6 -0
  165. package/dist/layout-painter/renderPage.js +1 -0
  166. package/dist/layout-painter/renderParagraph.js +1 -1
  167. package/dist/managers/ContextMenuManager.js +5 -2
  168. package/dist/managers/types.d.ts +3 -0
  169. package/dist/markdown/fromMarkdown.js +2 -1
  170. package/dist/markdown/internals.d.ts +11 -1
  171. package/dist/markdown/internals.js +24 -3
  172. package/dist/markdown/renderBlock.js +39 -9
  173. package/dist/markdown/renderParagraph.d.ts +13 -1
  174. package/dist/markdown/renderParagraph.js +85 -38
  175. package/dist/markdown/renderRuns.js +3 -10
  176. package/dist/markdown/renderTable.js +24 -12
  177. package/dist/markdown/trailers.js +2 -2
  178. package/dist/markdown/types.d.ts +29 -7
  179. package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
  180. package/dist/paged-layout/incrementalMeasure.js +1 -9
  181. package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
  182. package/dist/paged-layout/transactionDirtyRange.js +29 -41
  183. package/dist/panel-layout.d.ts +83 -0
  184. package/dist/panel-layout.js +96 -0
  185. package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
  186. package/dist/prosemirror/anchoredTextBoxes.js +156 -0
  187. package/dist/prosemirror/attrs/index.d.ts +6 -2
  188. package/dist/prosemirror/attrs/index.js +172 -1
  189. package/dist/prosemirror/commands/comments.d.ts +7 -8
  190. package/dist/prosemirror/commands/comments.js +241 -207
  191. package/dist/prosemirror/commands/contentControls.d.ts +1 -1
  192. package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
  193. package/dist/prosemirror/commands/formatting.d.ts +1 -1
  194. package/dist/prosemirror/commands/index.d.ts +2 -2
  195. package/dist/prosemirror/commands/index.js +2 -2
  196. package/dist/prosemirror/commands/paragraph.d.ts +7 -1
  197. package/dist/prosemirror/commands/paragraph.js +9 -1
  198. package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
  199. package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
  200. package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
  201. package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
  202. package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
  203. package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
  204. package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
  205. package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
  206. package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
  207. package/dist/prosemirror/commands/sectionBreak.js +2 -1
  208. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
  209. package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
  210. package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
  211. package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
  212. package/dist/prosemirror/conversion/toProseDoc.js +39 -4
  213. package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
  214. package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
  215. package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
  216. package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
  217. package/dist/prosemirror/extensions/StarterKit.js +3 -1
  218. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  219. package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
  220. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
  221. package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
  222. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
  223. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
  224. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
  225. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
  226. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
  227. package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
  228. package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
  229. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
  230. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
  231. package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
  232. package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
  233. package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
  234. package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
  235. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
  236. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
  237. package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
  238. package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
  239. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
  240. package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
  241. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
  242. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  243. package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
  244. package/dist/prosemirror/extensions/types.d.ts +4 -1
  245. package/dist/prosemirror/findReplaceSelection.js +1 -1
  246. package/dist/prosemirror/index.d.ts +3 -2
  247. package/dist/prosemirror/index.js +3 -2
  248. package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
  249. package/dist/prosemirror/indexedNodeLookup.js +103 -0
  250. package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
  251. package/dist/prosemirror/listAutoformatMarkers.js +98 -0
  252. package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
  253. package/dist/prosemirror/listInstanceReferences.js +72 -0
  254. package/dist/prosemirror/listLabels.d.ts +24 -0
  255. package/dist/prosemirror/listLabels.js +111 -0
  256. package/dist/prosemirror/listNumbering.d.ts +110 -0
  257. package/dist/prosemirror/listNumbering.js +345 -0
  258. package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
  259. package/dist/prosemirror/listRenderingAttrs.js +8 -1
  260. package/dist/prosemirror/markupViewProjection.d.ts +95 -0
  261. package/dist/prosemirror/markupViewProjection.js +83 -0
  262. package/dist/prosemirror/numberingAttr.d.ts +9 -1
  263. package/dist/prosemirror/numberingAttr.js +9 -1
  264. package/dist/prosemirror/outlineLevelAttr.js +1 -1
  265. package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
  266. package/dist/prosemirror/paragraphIndentation.js +29 -7
  267. package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
  268. package/dist/prosemirror/paragraphMarkJoin.js +24 -0
  269. package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
  270. package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
  271. package/dist/prosemirror/plugins/documentNumbering.js +97 -11
  272. package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
  273. package/dist/prosemirror/plugins/documentStyles.js +14 -1
  274. package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
  275. package/dist/prosemirror/plugins/suggestionMode.js +116 -52
  276. package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
  277. package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
  278. package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
  279. package/dist/prosemirror/positionSweep.d.ts +24 -0
  280. package/dist/prosemirror/positionSweep.js +222 -0
  281. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
  282. package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
  283. package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
  284. package/dist/prosemirror/replacedAnnotations.js +170 -1
  285. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
  286. package/dist/prosemirror/schema/index.d.ts +2 -2
  287. package/dist/prosemirror/schema/index.js +3 -0
  288. package/dist/prosemirror/schema/nodes.d.ts +42 -11
  289. package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
  290. package/dist/prosemirror/sectionMarkEdits.js +59 -0
  291. package/dist/prosemirror/storyListNumbering.d.ts +17 -0
  292. package/dist/prosemirror/storyListNumbering.js +102 -0
  293. package/dist/prosemirror/tableGridMutation.d.ts +48 -2
  294. package/dist/prosemirror/tableGridMutation.js +106 -1
  295. package/dist/prosemirror/validation.js +5 -1
  296. package/dist/redline.js +93 -9
  297. package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
  298. package/dist/render-dom/BodySelectionOverlay.js +3 -2
  299. package/dist/utils/findReplace.js +2 -2
  300. package/dist/utils/mergeDocumentContent.js +1 -1
  301. package/dist/utils/noteReferenceLabels.d.ts +36 -0
  302. package/dist/utils/noteReferenceLabels.js +32 -0
  303. package/dist/utils/replaceText.js +1 -1
  304. package/dist/version-comparison.d.ts +1 -0
  305. package/dist/version-comparison.js +1 -0
  306. package/package.json +3 -2
  307. package/dist/internal/headlessRevisionResolution.d.ts +0 -23
@@ -1,16 +1,19 @@
1
1
  import { FolioDocxReviewer, getFolioDocxComparisonAccess } from "../ai-edits/headless.js";
2
- import { detachFolioAIEditSnapshotExternalHyperlinks, numberingReferenceKeysOf, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf } from "../ai-edits/snapshot.js";
2
+ import { withComparisonNoteReferenceEdits } from "../ai-edits/note-references.js";
3
+ import { alignedNoteReferenceLabels, detachFolioAIEditSnapshotExternalHyperlinks, numberingReferenceKeysOf, relabelFolioAIEditSnapshotNoteReferences, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf } from "../ai-edits/snapshot.js";
3
4
  import { projectTableGeometry } from "../ai-edits/table-geometry.js";
4
5
  import { tableTemplateCanCrossPackageLosslessly } from "../ai-edits/table-template.js";
5
6
  import { createScopedWordDiffOptions } from "../ai-edits/word-diff.js";
6
7
  import "../document-operations.js";
7
8
  import { pairFolioDocumentStories } from "../document-stories.js";
9
+ import { isAltChunkMarkup } from "../docx/altChunk.js";
10
+ import { isOpaqueNestedRowMarkup, opaqueRevisionCarrierName } from "../docx/opaqueCarrier.js";
8
11
  import { sectionReferenceHistory } from "../docx/sectionReferenceHistory.js";
9
12
  import { sameCanonicalInlinePresentation } from "../internal/compare/inline-presentation.js";
10
13
  import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
11
14
  import { canonicalJson } from "../utils/canonicalJson.js";
12
15
  import { createContentComparisonWorkSession } from "./content.js";
13
- import { sameInlineAtoms } from "./inline-atoms.js";
16
+ import { MISSING_NOTE_DETAIL, sameInlineAtoms } from "./inline-atoms.js";
14
17
  import { sameAuthoredInlineProvenance } from "./inline-provenance.js";
15
18
  import { planStoryCompare } from "./plan.js";
16
19
  import { withFixedPackageDates } from "./reproducible-package.js";
@@ -67,6 +70,26 @@ const parseSide = async (buffer, side, author) => await Result.tryPromise({
67
70
  cause
68
71
  })
69
72
  });
73
+ const snapshotContainsUnsupportedCarrier = (snapshot) => {
74
+ let found = false;
75
+ sourceDocumentOf(snapshot).descendants((node) => {
76
+ if (node.type.name === "preservedBlock") {
77
+ const xml = node.attrs["xml"];
78
+ if (typeof xml === "string" && (isAltChunkMarkup(xml) || opaqueRevisionCarrierName(xml) !== void 0 || isOpaqueNestedRowMarkup(xml))) found = true;
79
+ }
80
+ const preserved = node.attrs["_preserved"];
81
+ if (preserved !== null && typeof preserved === "object" && "children" in preserved) {
82
+ const children = preserved.children;
83
+ if (!Array.isArray(children)) return;
84
+ for (const child of children) {
85
+ if (child === null || typeof child !== "object" || !("xml" in child)) continue;
86
+ const xml = child.xml;
87
+ if (typeof xml === "string" && (opaqueRevisionCarrierName(xml) !== void 0 || isOpaqueNestedRowMarkup(xml))) found = true;
88
+ }
89
+ }
90
+ });
91
+ return found;
92
+ };
70
93
  /** Verify formatting only where the plan claims a text-equal formatting change. */
71
94
  const formattingRoundTripFailure = ({ invariant, story, changes, actualBlocks, expectedBlocks, expectedBlockId }) => {
72
95
  const expectedIndexById = new Map(expectedBlocks.map(({ id }, index) => [id, index]));
@@ -286,6 +309,14 @@ const parseComparison = async (base, target, options) => {
286
309
  });
287
310
  continue;
288
311
  }
312
+ if (snapshotContainsUnsupportedCarrier(baseSnapshot) || snapshotContainsUnsupportedCarrier(targetSnapshot)) {
313
+ unsupported.push({
314
+ reason: "unsupported-content",
315
+ baseStory,
316
+ targetStory
317
+ });
318
+ continue;
319
+ }
289
320
  const rawBaseSnapshot = baseSnapshot;
290
321
  if (revisionFormat === "folio-exact" && baseStory.type === "main" && getFolioDocxComparisonAccess(reviewer).stageTerminalTableReviewCarrier(sourceDocumentOf(targetSnapshot))) {
291
322
  const staged = reviewer.snapshotStory(baseStory);
@@ -368,6 +399,8 @@ const parseComparison = async (base, target, options) => {
368
399
  unsupported
369
400
  });
370
401
  };
402
+ /** The note-reference markers a story's plan is written in: the base's, then the target's new notes. */
403
+ const noteReferenceLabelsOf = (pair) => alignedNoteReferenceLabels(sourceDocumentOf(pair.baseSnapshot), sourceDocumentOf(pair.targetSnapshot));
371
404
  const planCopiesNonPortableWholeTable = (pair, plan) => {
372
405
  const targetTables = new Map(storyTablesOf(pair.targetSnapshot).map(({ index, node }) => [index, node]));
373
406
  return plan.tableTemplates.some(({ targetRowIndex, targetTableIndex }) => {
@@ -389,10 +422,11 @@ const planComparison = ({ pairs }) => {
389
422
  const remainingStructuralTokenLookups = workSession.alignment.remainingStructuralTokenLookups;
390
423
  const remainingMoveComparisons = workSession.remainingMoveComparisons;
391
424
  const remainingMoveTokenLookups = workSession.remainingMoveTokenLookups;
425
+ const planningTarget = relabelFolioAIEditSnapshotNoteReferences(pair.targetSnapshot, noteReferenceLabelsOf(pair));
392
426
  let plan = planStoryCompare({
393
427
  story: pair.baseStory,
394
428
  baseSnapshot: pair.baseSnapshot,
395
- targetSnapshot: pair.targetSnapshot,
429
+ targetSnapshot: planningTarget,
396
430
  maxOperations: remainingOperations,
397
431
  wholeTableReplacement: "allow",
398
432
  workSession
@@ -405,7 +439,7 @@ const planComparison = ({ pairs }) => {
405
439
  plan = planStoryCompare({
406
440
  story: pair.baseStory,
407
441
  baseSnapshot: pair.baseSnapshot,
408
- targetSnapshot: pair.targetSnapshot,
442
+ targetSnapshot: planningTarget,
409
443
  maxOperations: remainingOperations,
410
444
  wholeTableReplacement: "avoid",
411
445
  workSession
@@ -454,7 +488,15 @@ const COMPARE_SKIP_DISPOSITION = {
454
488
  pendingRunPropertyChange: "unwritable",
455
489
  noopOperation: "unwritable",
456
490
  documentVersionMismatch: "fatal",
457
- documentNotEditable: "fatal"
491
+ documentNotEditable: "fatal",
492
+ payloadDoesNotFit: "unwritable",
493
+ overlappingOperation: "fatal",
494
+ splitsCharacter: "unwritable",
495
+ protectedReference: "unwritable",
496
+ missingStyle: "fatal",
497
+ missingNumbering: "fatal",
498
+ invalidResult: "fatal",
499
+ pendingDeletion: "unwritable"
458
500
  };
459
501
  const getCompareSkipDisposition = (reason) => COMPARE_SKIP_DISPOSITION[reason];
460
502
  /**
@@ -494,8 +536,10 @@ const resolveTableTemplates = (targetTables, requests) => {
494
536
  * what you could not represent" and "give me nothing unless you can prove it"
495
537
  * are both legitimate asks and only the caller knows which one it is making.
496
538
  */
497
- const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat, terminalTableCarrierStaged, numberingChanges, targetNumbering, targetNumberingReferences, targetNumberingReferenceMap, finalSectionComparison, styleImportFailure, pairs }, planned) => {
539
+ const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat, terminalTableCarrierStaged, numberingChanges, targetNumbering, targetNumberingReferences, targetNumberingReferenceMap, finalSectionComparison, styleImportFailure, pairs, unsupported }, planned) => {
498
540
  const relationshipIds = sectionRelationshipMap(pairs);
541
+ const baseNotes = /* @__PURE__ */ new Set();
542
+ for (const { baseStory } of [...pairs, ...unsupported]) if (baseStory?.type === "footnote" || baseStory?.type === "endnote") baseNotes.add(`${baseStory.type}:${String(baseStory.noteId)}`);
499
543
  const plannedOperationCount = planned.reduce((count, { plan }) => count + plan.operations.length, 0);
500
544
  const finalSectionOperationCount = finalSectionComparison.status === "stage" ? 1 : 0;
501
545
  if (plannedOperationCount + finalSectionOperationCount > 1e4) return Result.err(new CompareDocxOperationLimitError({
@@ -560,13 +604,14 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat,
560
604
  documentChanged ||= geometryChanged;
561
605
  idSeed = afterGeometry;
562
606
  if (plan.operations.length > 0) {
563
- const { skipped, nextRevisionId } = reviewer.applyDocumentOperationsToStory({
607
+ const operationRevisionStamp = {
608
+ date: revisionStamp.date,
609
+ idSeed
610
+ };
611
+ const { skipped, nextRevisionId } = withComparisonNoteReferenceEdits(() => reviewer.applyDocumentOperationsToStory({
564
612
  story: pair.baseStory,
565
613
  snapshot: pair.baseSnapshot,
566
- revisionStamp: {
567
- date: revisionStamp.date,
568
- idSeed
569
- },
614
+ revisionStamp: operationRevisionStamp,
570
615
  wordDiff,
571
616
  batch: {
572
617
  version: 1,
@@ -574,8 +619,9 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat,
574
619
  operations: plan.operations
575
620
  },
576
621
  tableTemplates: resolveTableTemplates(targetTables, plan.tableTemplates),
577
- replacementBackground: "keep"
578
- });
622
+ replacementBackground: "keep",
623
+ undefinedStyles: "keep"
624
+ }));
579
625
  if (nextRevisionId === void 0) panic("The applier did not report where it left the revision-id counter", { story: pair.baseStory });
580
626
  idSeed = nextRevisionId;
581
627
  documentChanged = true;
@@ -614,6 +660,8 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat,
614
660
  const atoms = comparisonAccess.matchInlineAtoms({
615
661
  story: pair.baseStory,
616
662
  targetSnapshot: pair.targetSnapshot,
663
+ noteReferenceLabels: noteReferenceLabelsOf(pair),
664
+ baseNotes,
617
665
  revisionStamp: {
618
666
  date: revisionStamp.date,
619
667
  idSeed
@@ -646,7 +694,7 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat,
646
694
  invariant: "accept-reproduces-target",
647
695
  cause: "inline-structure",
648
696
  story: pair.baseStory,
649
- detail: "supported inline atoms could not be aligned to the revised content"
697
+ detail: atoms.detail ?? "supported inline atoms could not be aligned to the revised content"
650
698
  });
651
699
  break;
652
700
  case "budget-exceeded": return Result.err(new CompareDocxOperationLimitError({
@@ -927,8 +975,9 @@ const compareDocx = async (base, target, options) => {
927
975
  const applied = applyComparison(parsed.value, planned.value);
928
976
  if (applied.isErr()) return Result.err(applied.error);
929
977
  const { changes, verification } = applied.value;
930
- if (verification.status === "unverified" && (options.onUnverified ?? "refuse") === "refuse") {
931
- const [firstFailure] = verification.failures;
978
+ const missingNote = verification.status === "unverified" ? verification.failures.find(({ detail }) => detail === MISSING_NOTE_DETAIL) : void 0;
979
+ if (verification.status === "unverified" && (missingNote !== void 0 || (options.onUnverified ?? "refuse") === "refuse")) {
980
+ const firstFailure = missingNote ?? verification.failures[0];
932
981
  if (firstFailure === void 0) panic("An unverified comparison reported no failing invariant");
933
982
  return Result.err(new CompareDocxRoundTripError({
934
983
  message: `The generated tracked changes do not satisfy ${firstFailure.invariant}: ${firstFailure.detail}`,
@@ -1331,7 +1331,7 @@ const pairTableRows = ({ baseRows, revisedRows, workSession, idStability }) => {
1331
1331
  item: row,
1332
1332
  profile: rowStructureProfile(row, idStability)
1333
1333
  });
1334
- return alignProfiledContentSequence({
1334
+ const aligned = alignProfiledContentSequence({
1335
1335
  base: baseRows.map(profile),
1336
1336
  revised: revisedRows.map(profile),
1337
1337
  workSession,
@@ -1339,7 +1339,18 @@ const pairTableRows = ({ baseRows, revisedRows, workSession, idStability }) => {
1339
1339
  soleShiftedPairResiduePolicy: "row-count-evidence",
1340
1340
  primaryEvidence: "exact",
1341
1341
  similarityFactor: (base, revised) => base.profile.physicalCellCount === revised.profile.physicalCellCount ? 1 : .5
1342
- }).map((alignment) => {
1342
+ });
1343
+ const baseRow = baseRows.at(0);
1344
+ const revisedRow = revisedRows.at(0);
1345
+ if (baseRows.length === 1 && baseRow !== void 0 && revisedRow !== void 0 && !aligned.some(({ type }) => type === "pair")) return [{
1346
+ type: "pair",
1347
+ baseRow,
1348
+ revisedRow
1349
+ }, ...revisedRows.slice(1).map((row) => ({
1350
+ type: "revisedOnly",
1351
+ row
1352
+ }))];
1353
+ return aligned.map((alignment) => {
1343
1354
  switch (alignment.type) {
1344
1355
  case "pair": return {
1345
1356
  type: "pair",
@@ -116,8 +116,15 @@ type FolioContentBlock<Kind extends string = string> = {
116
116
  idStability?: FolioContentIdStability;
117
117
  /** One-based heading depth when the block has outline semantics. */
118
118
  headingLevel?: number;
119
+ /**
120
+ * The number or bullet the block shows beside its text (`2.1.`, `a)`, `•`),
121
+ * a numbered heading's included; an unnumbered heading's style id. Absent
122
+ * when the block shows neither, a marker its level hides included.
123
+ */
119
124
  displayLabel?: string;
120
125
  styleId?: string;
126
+ /** Authored `w:outlineLvl`; absent when heading depth comes only from a style. */
127
+ directOutlineLevel?: document_d_exports.OutlineLevel;
121
128
  /** Direct paragraph alignment; absent when alignment comes only from a style. */
122
129
  directAlignment?: FolioContentParagraphAlignment;
123
130
  /** Direct paragraph spacing; absent when every spacing value is inherited. */
@@ -55,12 +55,15 @@ type FolioContentTextSegment = {
55
55
  /** Target-side paragraph properties that differ from the base block. */
56
56
  type FolioContentParagraphFormattingPatch = {
57
57
  styleId?: string | null;
58
+ outlineLevel?: FolioContentBlock["directOutlineLevel"] | null;
58
59
  listLevel?: number | null;
59
60
  listReference?: FolioContentListReference | null;
60
61
  alignment?: FolioContentBlock["directAlignment"] | null;
61
62
  spacing?: FolioContentParagraphSpacing | null;
62
63
  indentation?: FolioContentParagraphIndentation | null;
63
64
  };
65
+ /** Authored outline identity, including the distinction between heading and body text. */
66
+ declare const sameFolioContentOutlineLevel: (left: FolioContentBlock["directOutlineLevel"], right: FolioContentBlock["directOutlineLevel"]) => boolean;
64
67
  /** Presentation differences for one text-aligned block pair. */
65
68
  type FolioContentFormattingChange = {
66
69
  paragraph?: FolioContentParagraphFormattingPatch;
@@ -210,4 +213,4 @@ declare const compareAlignedFolioContent: <Block extends FolioContentBlock>({ ba
210
213
  /** Compare two representation-neutral ordered content snapshots. */
211
214
  declare const compareContent: <Block extends FolioContentBlock = FolioContentBlock>(options: CompareContentOptions<Block>) => Result<FolioContentComparison<Block>, FolioContentComparisonError>;
212
215
  //#endregion
213
- export { CompareContentOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentBlockProperty, FolioContentComparison, FolioContentComparisonError, FolioContentComparisonEvent, FolioContentComparisonLimit, FolioContentComparisonLimitError, FolioContentComparisonWorkSession, type FolioContentFormatRange, FolioContentFormattingChange, FolioContentInlinePresentationProjectionError, FolioContentParagraphFormattingPatch, FolioContentStructuralChange, FolioContentTextSegment, InvalidFolioContentComparisonError, changedFolioContentParagraphFormatting, compareAlignedFolioContent, compareContent, createContentComparisonWorkSession, detectFolioContentMoves, detectFolioContentParagraphMarkPlans };
216
+ export { CompareContentOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentBlockProperty, FolioContentComparison, FolioContentComparisonError, FolioContentComparisonEvent, FolioContentComparisonLimit, FolioContentComparisonLimitError, FolioContentComparisonWorkSession, type FolioContentFormatRange, FolioContentFormattingChange, FolioContentInlinePresentationProjectionError, FolioContentParagraphFormattingPatch, FolioContentStructuralChange, FolioContentTextSegment, InvalidFolioContentComparisonError, changedFolioContentParagraphFormatting, compareAlignedFolioContent, compareContent, createContentComparisonWorkSession, detectFolioContentMoves, detectFolioContentParagraphMarkPlans, sameFolioContentOutlineLevel };
@@ -1,4 +1,5 @@
1
1
  import { WORD_DIFF_GRANULARITIES, createWordDiffSession } from "../ai-edits/word-diff.js";
2
+ import { outlineLevelFromAttrValue } from "../prosemirror/outlineLevelAttr.js";
2
3
  import { paragraphIndentationEqual } from "../prosemirror/paragraphIndentation.js";
3
4
  import { PARAGRAPH_ALIGNMENT_VALUES } from "../types/documentEnumValues.js";
4
5
  import { alignFolioContentStructure, contentBlocksShareContainer, createFolioContentAlignmentWorkSession } from "./content-alignment.js";
@@ -43,6 +44,8 @@ const MAX_MOVE_PROFILE_CODE_UNITS = 1048576;
43
44
  var InvalidFolioContentComparisonError = class extends TaggedError("InvalidFolioContentComparisonError") {};
44
45
  var FolioContentComparisonLimitError = class extends TaggedError("FolioContentComparisonLimitError") {};
45
46
  var FolioContentInlinePresentationProjectionError = class extends TaggedError("FolioContentInlinePresentationProjectionError") {};
47
+ /** Authored outline identity, including the distinction between heading and body text. */
48
+ const sameFolioContentOutlineLevel = (left, right) => left?.kind === right?.kind && (left?.kind !== "heading" || right?.kind === "heading" && left.level === right.level);
46
49
  const createContentComparisonWorkSession = (granularity) => ({
47
50
  alignment: createFolioContentAlignmentWorkSession(),
48
51
  remainingMoveComparisons: MAX_MOVE_SIMILARITY_COMPARISONS,
@@ -99,6 +102,7 @@ const validateParagraphFormatting = (block, side, blockIndex) => {
99
102
  if (block.headingLevel !== void 0 && (!isFiniteInteger(block.headingLevel) || block.headingLevel < 1)) return invalidInput(side, `blocks[${String(blockIndex)}].headingLevel`, "Heading levels must be positive integers.", blockIndex);
100
103
  if (block.listLevel !== void 0 && (!isFiniteInteger(block.listLevel) || block.listLevel < 0)) return invalidInput(side, `blocks[${String(blockIndex)}].listLevel`, "List levels must be non-negative integers.", blockIndex);
101
104
  if (block.directAlignment !== void 0 && !PARAGRAPH_ALIGNMENTS.has(block.directAlignment)) return invalidInput(side, `blocks[${String(blockIndex)}].directAlignment`, "Paragraph alignment is not recognized.", blockIndex);
105
+ if (block.directOutlineLevel !== void 0 && outlineLevelFromAttrValue(block.directOutlineLevel) === null) return invalidInput(side, `blocks[${String(blockIndex)}].directOutlineLevel`, "Direct outline level is not recognized.", blockIndex);
102
106
  const spacing = block.directSpacing;
103
107
  if (spacing === void 0) return null;
104
108
  if (!isRecord(spacing)) return invalidInput(side, `blocks[${String(blockIndex)}].directSpacing`, "Direct paragraph spacing must be an object.", blockIndex);
@@ -409,6 +413,7 @@ const paragraphSpacingEqual = (base, revised) => base?.spaceBefore === revised?.
409
413
  const changedFolioContentParagraphFormatting = (base, revised) => {
410
414
  const patch = {};
411
415
  if ((base.styleId ?? null) !== (revised.styleId ?? null)) patch.styleId = revised.styleId ?? null;
416
+ if (!sameFolioContentOutlineLevel(base.directOutlineLevel, revised.directOutlineLevel)) patch.outlineLevel = revised.directOutlineLevel ?? null;
412
417
  const listReferenceChanged = base.listReference?.numId !== revised.listReference?.numId;
413
418
  const targetOmitsLevelOnChangedReference = listReferenceChanged && revised.listReference !== void 0 && revised.listLevel === void 0;
414
419
  if (base.listLevel !== revised.listLevel || targetOmitsLevelOnChangedReference) patch.listLevel = revised.listLevel ?? null;
@@ -950,4 +955,4 @@ const compareContent = (options) => {
950
955
  });
951
956
  };
952
957
  //#endregion
953
- export { FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentComparisonLimitError, FolioContentInlinePresentationProjectionError, InvalidFolioContentComparisonError, changedFolioContentParagraphFormatting, compareAlignedFolioContent, compareContent, createContentComparisonWorkSession, detectFolioContentMoves, detectFolioContentParagraphMarkPlans };
958
+ export { FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentComparisonLimitError, FolioContentInlinePresentationProjectionError, InvalidFolioContentComparisonError, changedFolioContentParagraphFormatting, compareAlignedFolioContent, compareContent, createContentComparisonWorkSession, detectFolioContentMoves, detectFolioContentParagraphMarkPlans, sameFolioContentOutlineLevel };
@@ -5,7 +5,8 @@ import { canonicalInlinePresentationSegments } from "../internal/compare/inline-
5
5
  const inlineFormattingSegments = ({ baseBlock, targetBlock, maxSegments }) => canonicalInlinePresentationSegments({
6
6
  baseBlock,
7
7
  revisedBlock: targetBlock,
8
- maxSegments
8
+ maxSegments,
9
+ presentationBasis: baseBlock.styleId === targetBlock.styleId ? "full" : "authored"
9
10
  });
10
11
  //#endregion
11
12
  export { inlineFormattingSegments };
@@ -1,7 +1,8 @@
1
1
  import { FolioAIEditSnapshot } from "../ai-edits/types.js";
2
2
  import { FolioRevisionStamp } from "../ai-edits/apply.js";
3
- import { EditorState, Transaction } from "prosemirror-state";
3
+ import { NoteReferenceLabels } from "../ai-edits/note-references.js";
4
4
  import { Node } from "prosemirror-model";
5
+ import { EditorState, Transaction } from "prosemirror-state";
5
6
  //#region src/compare/inline-atoms.d.ts
6
7
  type MatchInlineAtomsOptions = {
7
8
  state: EditorState;
@@ -11,6 +12,17 @@ type MatchInlineAtomsOptions = {
11
12
  originalRevisionIdSeed: number;
12
13
  author: string;
13
14
  maxRanges: number;
15
+ /**
16
+ * The markers the comparison's operations wrote note references as.
17
+ * Defaults to the target story's own numbering.
18
+ */
19
+ noteReferenceLabels?: NoteReferenceLabels;
20
+ /**
21
+ * The notes the base package defines, as `footnote:<id>` / `endnote:<id>`.
22
+ * A reference the comparison writes may only name one of them. Defaults to
23
+ * the notes the story already references.
24
+ */
25
+ baseNotes?: ReadonlySet<string>;
14
26
  };
15
27
  type MatchInlineAtomsResult = {
16
28
  status: "matched";
@@ -20,16 +32,21 @@ type MatchInlineAtomsResult = {
20
32
  rangeCount: number;
21
33
  } | {
22
34
  status: "unalignable";
35
+ /** Why, when the cause is more specific than an alignment failure. */
36
+ detail?: string;
23
37
  } | {
24
38
  status: "budget-exceeded";
25
39
  };
40
+ /** A revised reference names a note the redline's package does not have. */
41
+ declare const MISSING_NOTE_DETAIL = "a note reference names a footnote or endnote the base document does not have";
26
42
  /**
27
- * Restore field, image, and page-break atoms omitted by text-only comparison operations.
43
+ * Restore field, image, and page-break atoms omitted by text-only comparison
44
+ * operations, and the note references whose markers they wrote as text.
28
45
  * Positions come from the legacy review resolver's mapping, never a guessed
29
46
  * paragraph correspondence: paragraph-mark deletions can merge paragraphs.
30
47
  */
31
- declare const matchInlineAtoms: ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges }: MatchInlineAtomsOptions) => MatchInlineAtomsResult;
48
+ declare const matchInlineAtoms: ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges, noteReferenceLabels, baseNotes }: MatchInlineAtomsOptions) => MatchInlineAtomsResult;
32
49
  /** Compare supported inline atom identity after accept or reject projection. */
33
50
  declare const sameInlineAtoms: (leftDocument: Node, rightDocument: Node) => boolean;
34
51
  //#endregion
35
- export { MatchInlineAtomsOptions, MatchInlineAtomsResult, matchInlineAtoms, sameInlineAtoms };
52
+ export { MISSING_NOTE_DETAIL, MatchInlineAtomsOptions, MatchInlineAtomsResult, matchInlineAtoms, sameInlineAtoms };
@@ -1,10 +1,14 @@
1
1
  import { buildCleanBlockText } from "../ai-edits/clean-text.js";
2
- import { sourceDocumentOf } from "../ai-edits/snapshot.js";
2
+ import { noteReferenceOf } from "../ai-edits/note-references.js";
3
+ import { collectNoteReferenceLabels, sourceDocumentOf } from "../ai-edits/snapshot.js";
4
+ import { drawingXmlWithoutIdentity } from "../docx/drawingIdNormalization.js";
3
5
  import { resolveAllChangesInHeadlessStateWithMapping } from "../prosemirror/commands/comments.js";
4
6
  import { runFormattingInlineAtomResultText } from "../prosemirror/runFormattingInlineCarriers.js";
5
7
  import { canonicalJson } from "../utils/canonicalJson.js";
6
8
  import { prepareTargetInlineAtom } from "./inline-atom-resources.js";
7
9
  //#region src/compare/inline-atoms.ts
10
+ /** A revised reference names a note the redline's package does not have. */
11
+ const MISSING_NOTE_DETAIL = "a note reference names a footnote or endnote the base document does not have";
8
12
  const actionRangeCount = (action) => action.kind === "replace-text" && action.textDisposition === "retained" || action.kind === "replace-atom" && action.atomDisposition === "retained" ? 2 : 1;
9
13
  const MAX_INLINE_ATOM_LCS_CELLS = 16384;
10
14
  const isSupportedAtom = (node) => node.isInline && node.isAtom && (node.type.name === "field" || node.type.name === "image" || node.type.name === "pageBreakRun");
@@ -65,6 +69,11 @@ const offsetAt = ({ offsets, position }) => {
65
69
  */
66
70
  const CONVERSION_LOCAL_ATTRS = { textBoxAnchor: ["anchorId"] };
67
71
  const documentFactAttrs = (node) => {
72
+ if (node.type.name === "field" || node.type.name === "structuredField") {
73
+ const equivalentFieldForms = node.type.name === "field" && (node.attrs["fieldKind"] === "simple" || node.attrs["fieldKind"] === "complex") && node.attrs["fieldResultIsFallback"] == null;
74
+ return Object.fromEntries(Object.entries(node.attrs).filter(([name]) => name !== "_docxFieldCode" && (!equivalentFieldForms || name !== "fieldKind")));
75
+ }
76
+ if (node.type.name === "image") return Object.fromEntries(Object.entries(node.attrs).filter(([name]) => name !== "docPrId").map(([name, value]) => [name, name === "_docxRawXml" && typeof value === "string" ? drawingXmlWithoutIdentity(value) : value]));
68
77
  const local = CONVERSION_LOCAL_ATTRS[node.type.name];
69
78
  if (!local) return node.attrs;
70
79
  return Object.fromEntries(Object.entries(node.attrs).filter(([name]) => !local.includes(name)));
@@ -74,8 +83,22 @@ const atomKey = (node) => canonicalJson({
74
83
  attrs: documentFactAttrs(node),
75
84
  content: node.content.toJSON()
76
85
  });
77
- const atomBlockOf = ({ node, from }, fieldResults) => {
78
- const clean = buildCleanBlockText(node, from, { fieldResults });
86
+ const atomBlockOf = ({ node, from }, fieldResults, noteReferences) => {
87
+ const clean = buildCleanBlockText(node, from, {
88
+ fieldResults,
89
+ noteReferences
90
+ });
91
+ const references = [];
92
+ for (const boundary of clean.structuralBoundaries) {
93
+ if (boundary.type !== "noteReference") continue;
94
+ references.push({
95
+ noteType: boundary.noteType,
96
+ noteId: boundary.noteId,
97
+ offset: boundary.offset,
98
+ length: boundary.length,
99
+ from: boundary.from
100
+ });
101
+ }
79
102
  const supported = [];
80
103
  const unsupportedTopology = [];
81
104
  node.descendants((child, relativePosition) => {
@@ -116,7 +139,8 @@ const atomBlockOf = ({ node, from }, fieldResults) => {
116
139
  cleanText: clean.text,
117
140
  offsets: clean.offsets,
118
141
  supported,
119
- unsupportedTopology
142
+ unsupportedTopology,
143
+ noteReferences: references
120
144
  };
121
145
  };
122
146
  const targetBlockIdLookup = (snapshot) => {
@@ -138,7 +162,44 @@ const targetBlockIdLookup = (snapshot) => {
138
162
  return owner?.id;
139
163
  };
140
164
  };
141
- const atomBlocksOf = (doc, fieldResults) => textBlocksOf(doc).map((block) => atomBlockOf(block, fieldResults));
165
+ const atomBlocksOf = (doc, fieldResults, noteReferences = "sourceText") => textBlocksOf(doc).map((block) => atomBlockOf(block, fieldResults, noteReferences));
166
+ const noteReferenceKey = ({ noteType, noteId }) => `${noteType}:${noteId}`;
167
+ /** Every note a story references, tracked-deleted references included. */
168
+ const referencedNotesOf = (doc) => {
169
+ const notes = /* @__PURE__ */ new Set();
170
+ doc.descendants((node) => {
171
+ const reference = noteReferenceOf(node);
172
+ if (reference) notes.add(noteReferenceKey(reference));
173
+ return !node.isText;
174
+ });
175
+ return notes;
176
+ };
177
+ const hasNoteReference = (doc) => {
178
+ let found = false;
179
+ doc.descendants((node) => {
180
+ if (found) return false;
181
+ if (noteReferenceOf(node)) found = true;
182
+ return !node.isText;
183
+ });
184
+ return found;
185
+ };
186
+ /**
187
+ * Whether this comparison inserted text that may stand for a note reference.
188
+ * Operations carry clean text, where a reference reads as its marker, so an
189
+ * operation that brings a reference writes the marker as text.
190
+ */
191
+ const wroteNoteReferenceMarker = (doc, originalRevisionIdSeed) => {
192
+ let found = false;
193
+ doc.descendants((node) => {
194
+ if (found) return false;
195
+ if (!node.isText) return true;
196
+ if (!node.text?.includes("[^") || noteReferenceOf(node)) return false;
197
+ const revisionId = node.marks.find(({ type }) => type.name === "insertion")?.attrs["revisionId"];
198
+ if (typeof revisionId === "number" && revisionId >= originalRevisionIdSeed) found = true;
199
+ return false;
200
+ });
201
+ return found;
202
+ };
142
203
  const sameBlockTopology = (left, right) => {
143
204
  if (left.node.type !== right.node.type || left.cleanText !== right.cleanText) return false;
144
205
  const supportedOffsets = /* @__PURE__ */ new Set([...left.supported.map(({ offset }) => offset), ...right.supported.map(({ offset }) => offset)]);
@@ -279,12 +340,15 @@ const sourceTextBlockIds = (source) => {
279
340
  }
280
341
  return blocks;
281
342
  };
282
- const sameParagraphSourcePosition = ({ sourceBlocks, reviewed, offset, fieldResults }) => {
343
+ const sameParagraphSourcePosition = ({ sourceBlocks, reviewed, offset, fieldResults, noteReferences }) => {
283
344
  const paraId = reviewed.node.attrs["paraId"];
284
345
  if (typeof paraId !== "string" || paraId.length === 0) return null;
285
346
  const source = sourceBlocks.get(paraId);
286
347
  if (!source || source.node.type !== reviewed.node.type) return null;
287
- const clean = buildCleanBlockText(source.node, source.from, { fieldResults });
348
+ const clean = buildCleanBlockText(source.node, source.from, {
349
+ fieldResults,
350
+ noteReferences
351
+ });
288
352
  return clean.text === reviewed.cleanText ? clean.offsets[offset] ?? null : null;
289
353
  };
290
354
  const textRangeDisposition = ({ doc, from, to, expectedText, originalRevisionIdSeed }) => {
@@ -332,14 +396,17 @@ const atomDisposition = ({ atom, originalRevisionIdSeed }) => {
332
396
  return typeof revisionId === "number" && revisionId >= originalRevisionIdSeed ? "inserted" : null;
333
397
  };
334
398
  /**
335
- * Restore field, image, and page-break atoms omitted by text-only comparison operations.
399
+ * Restore field, image, and page-break atoms omitted by text-only comparison
400
+ * operations, and the note references whose markers they wrote as text.
336
401
  * Positions come from the legacy review resolver's mapping, never a guessed
337
402
  * paragraph correspondence: paragraph-mark deletions can merge paragraphs.
338
403
  */
339
- const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges }) => {
404
+ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges, noteReferenceLabels, baseNotes }) => {
340
405
  if (!Number.isSafeInteger(maxRanges) || maxRanges < 0) return { status: "budget-exceeded" };
341
406
  const targetDocument = sourceDocumentOf(targetSnapshot);
342
- if (!hasSupportedAtom(state.doc) && !hasSupportedAtom(targetDocument)) return {
407
+ const restoresNoteReferences = wroteNoteReferenceMarker(state.doc, originalRevisionIdSeed) && hasNoteReference(targetDocument);
408
+ const noteReferences = restoresNoteReferences ? noteReferenceLabels ?? collectNoteReferenceLabels(targetDocument) : "sourceText";
409
+ if (!restoresNoteReferences && !hasSupportedAtom(state.doc) && !hasSupportedAtom(targetDocument)) return {
343
410
  status: "matched",
344
411
  transaction: state.tr,
345
412
  nextRevisionId: revisionStamp.idSeed,
@@ -349,8 +416,8 @@ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisi
349
416
  const reviewed = resolveAllChangesInHeadlessStateWithMapping(state, "accept");
350
417
  const targetBlockIdAt = targetBlockIdLookup(targetSnapshot);
351
418
  let sourceBlocks;
352
- const liveBlocks = atomBlocksOf(reviewed.state.doc, "text");
353
- const targetBlocks = atomBlocksOf(targetDocument, "text");
419
+ const liveBlocks = atomBlocksOf(reviewed.state.doc, "text", noteReferences);
420
+ const targetBlocks = atomBlocksOf(targetDocument, "text", noteReferences);
354
421
  if (liveBlocks.length !== targetBlocks.length) return { status: "unalignable" };
355
422
  const actions = [];
356
423
  let rangeCount = 0;
@@ -361,19 +428,63 @@ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisi
361
428
  actions.push(action);
362
429
  return true;
363
430
  };
431
+ let referencedNotes;
364
432
  let omittedLiveBlocks;
365
433
  let omittedTargetBlocks;
366
434
  for (const [index, fullLive] of liveBlocks.entries()) {
367
435
  const fullTarget = targetBlocks[index];
368
436
  if (!fullTarget) return { status: "unalignable" };
437
+ if ((fullLive.noteReferences.length > 0 || fullTarget.noteReferences.length > 0) && fullLive.cleanText === fullTarget.cleanText) {
438
+ const kept = new Map(fullLive.noteReferences.map((reference) => [reference.offset, reference]));
439
+ for (const reference of fullTarget.noteReferences) {
440
+ const live = kept.get(reference.offset);
441
+ if (live) {
442
+ kept.delete(reference.offset);
443
+ if (noteReferenceKey(live) !== noteReferenceKey(reference) || live.length !== reference.length) return { status: "unalignable" };
444
+ continue;
445
+ }
446
+ referencedNotes ??= baseNotes ?? referencedNotesOf(state.doc);
447
+ if (!referencedNotes.has(noteReferenceKey(reference))) return {
448
+ status: "unalignable",
449
+ detail: MISSING_NOTE_DETAIL
450
+ };
451
+ const node = targetDocument.nodeAt(reference.from);
452
+ const reviewedFrom = fullLive.offsets[reference.offset];
453
+ const reviewedLast = fullLive.offsets[reference.offset + reference.length - 1];
454
+ if (!node || !noteReferenceOf(node) || reviewedFrom === void 0 || reviewedLast === void 0) return { status: "unalignable" };
455
+ const from = mappedSourcePosition({
456
+ mapping: reviewed.mapping,
457
+ position: reviewedFrom
458
+ });
459
+ const to = mappedSourcePosition({
460
+ mapping: reviewed.mapping,
461
+ position: reviewedLast + 1
462
+ });
463
+ if (from === null || to === null || textRangeDisposition({
464
+ doc: state.doc,
465
+ from,
466
+ to,
467
+ expectedText: fullTarget.cleanText.slice(reference.offset, reference.offset + reference.length),
468
+ originalRevisionIdSeed
469
+ }) !== "inserted") return { status: "unalignable" };
470
+ if (!planAction({
471
+ kind: "note-reference",
472
+ from,
473
+ to,
474
+ node,
475
+ targetBlockId: targetBlockIdAt(fullTarget.from)
476
+ })) return { status: "budget-exceeded" };
477
+ }
478
+ if (kept.size > 0) return { status: "unalignable" };
479
+ }
369
480
  if (fullLive.supported.length === 0 && fullTarget.supported.length === 0) continue;
370
481
  if (sameSupportedAtoms(fullLive, fullTarget)) continue;
371
482
  let live = fullLive;
372
483
  let target = fullTarget;
373
484
  let fieldResults = "text";
374
485
  if (!sameBlockTopology(live, target)) {
375
- omittedLiveBlocks ??= atomBlocksOf(reviewed.state.doc, "omitted");
376
- omittedTargetBlocks ??= atomBlocksOf(targetDocument, "omitted");
486
+ omittedLiveBlocks ??= atomBlocksOf(reviewed.state.doc, "omitted", noteReferences);
487
+ omittedTargetBlocks ??= atomBlocksOf(targetDocument, "omitted", noteReferences);
377
488
  const omittedLive = omittedLiveBlocks[index];
378
489
  const omittedTarget = omittedTargetBlocks[index];
379
490
  if (!omittedLive || !omittedTarget || !sameBlockTopology(omittedLive, omittedTarget)) return { status: "unalignable" };
@@ -441,7 +552,8 @@ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisi
441
552
  sourceBlocks: sourceBlocks ??= sourceTextBlockIds(state.doc),
442
553
  reviewed: live,
443
554
  offset: atom.offset,
444
- fieldResults
555
+ fieldResults,
556
+ noteReferences
445
557
  });
446
558
  if (from === null) return { status: "unalignable" };
447
559
  const resultText = runFormattingInlineAtomResultText(atom.node);
@@ -520,6 +632,16 @@ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisi
520
632
  if (action.targetBlockId) changedTargetBlockIds.add(action.targetBlockId);
521
633
  continue;
522
634
  }
635
+ if (action.kind === "note-reference") {
636
+ const from = transaction.mapping.map(action.from, 1);
637
+ const to = transaction.mapping.map(action.to, -1);
638
+ const written = transaction.doc.nodeAt(from);
639
+ const noteMark = action.node.marks.find(({ type }) => type.name === "footnoteRef");
640
+ if (from >= to || !written?.isText || !noteMark) return { status: "unalignable" };
641
+ transaction.replaceWith(from, to, state.schema.text(action.node.text ?? "", noteMark.addToSet(written.marks)));
642
+ if (action.targetBlockId) changedTargetBlockIds.add(action.targetBlockId);
643
+ continue;
644
+ }
523
645
  if (action.kind === "replace-atom") {
524
646
  const from = transaction.mapping.map(action.from, 1);
525
647
  const to = transaction.mapping.map(action.to, -1);
@@ -581,4 +703,4 @@ const sameInlineAtoms = (leftDocument, rightDocument) => {
581
703
  });
582
704
  };
583
705
  //#endregion
584
- export { matchInlineAtoms, sameInlineAtoms };
706
+ export { MISSING_NOTE_DETAIL, matchInlineAtoms, sameInlineAtoms };
@@ -223,6 +223,7 @@ const shareAContainer = (left, right) => {
223
223
  /** Narrow the neutral patch to the operation contract without explicit undefined fields. */
224
224
  const toFolioAIBlockParagraphProperties = (properties) => ({
225
225
  ...properties.styleId !== void 0 && { styleId: properties.styleId },
226
+ ...properties.outlineLevel !== void 0 && { outlineLevel: properties.outlineLevel },
226
227
  ...properties.listLevel !== void 0 && { listLevel: properties.listLevel },
227
228
  ...properties.listReference !== void 0 && { numbering: properties.listReference },
228
229
  ...properties.alignment !== void 0 && { alignment: properties.alignment },
@@ -594,18 +595,22 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations,
594
595
  const moveSourceId = moveSourceByTargetBlockId.get(block.id);
595
596
  const structuralBoundary = block.structuralBoundaries?.at(0);
596
597
  const hardPageBreak = block.text.length === 0 && block.structuralBoundaries?.length === 1 && structuralBoundary?.type === "pageBreak" && structuralBoundary.offset === 0 ? { ...structuralBoundary.clear !== void 0 && { clear: structuralBoundary.clear } } : void 0;
597
- const shared = {
598
- text: block.text,
599
- ...(block.text.includes(" ") || block.text.includes("\n")) && { lineBreakMode: "inline" },
600
- ...moveSourceId !== void 0 && { moveId: moveIdOf(moveSourceId) },
601
- ...hardPageBreak !== void 0 && { hardPageBreak },
598
+ const paragraphProperties = {
602
599
  styleId: block.styleId ?? null,
600
+ outlineLevel: block.directOutlineLevel ?? null,
603
601
  listLevel: block.listLevel ?? null,
604
602
  numbering: block.listReference ?? null,
605
603
  alignment: block.directAlignment ?? null,
606
604
  spacing: block.directSpacing ?? null,
607
605
  indentation: block.directIndentation ?? null
608
606
  };
607
+ const shared = {
608
+ text: block.text,
609
+ ...(block.text.includes(" ") || block.text.includes("\n")) && { lineBreakMode: "inline" },
610
+ ...moveSourceId !== void 0 && { moveId: moveIdOf(moveSourceId) },
611
+ ...hardPageBreak !== void 0 && { hardPageBreak },
612
+ ...paragraphProperties
613
+ };
609
614
  if (anchorId !== null) {
610
615
  operations.push({
611
616
  id: nextOperationId(),