@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
@@ -0,0 +1,164 @@
1
+ import { validateFolioDocumentModel } from "../docx/modelValidation.js";
2
+ import { proseDocToBlocks } from "../prosemirror/conversion/fromProseDoc.js";
3
+ import { sectionPropertiesOf } from "../prosemirror/sectionCarrier.js";
4
+ import { anchoredCommentIdsInProseDoc } from "./comment-lifecycle.js";
5
+ import { Fragment } from "prosemirror-model";
6
+ //#region src/ai-edits/result-validation.ts
7
+ /**
8
+ * The check an operation batch's result passes before it is committed.
9
+ *
10
+ * An operation that reaches the document is a promise that the document can
11
+ * still be saved: the batch reports it as applied, issues a receipt and an
12
+ * undo handle, and the next thing the caller does may be `save()`. The model
13
+ * validator the save runs (`validateFolioDocumentModel`, the one
14
+ * `repackDocx` asserts) is therefore the judge here too, run on what the
15
+ * batch produced, while the batch can still be refused.
16
+ *
17
+ * Two constraints shape it:
18
+ *
19
+ * - Cost. Converting a whole story back to the model and validating it costs
20
+ * about as much as the apply itself on a long document. A batch only
21
+ * changes the top-level blocks it touched, and ProseMirror keeps every
22
+ * untouched child node by identity, so only the window between the first
23
+ * and the last changed child is converted and validated.
24
+ * - Blame. The apply layer holds the story, not the package: comments, notes
25
+ * and header/footer parts are the host's. A window can therefore report an
26
+ * error the batch did not cause (a comment the package defines, a range the
27
+ * window cuts). Only an error the same window did not already have before
28
+ * the batch counts; an error the document already carried is the
29
+ * document's, and refusing every later edit near it would help no one.
30
+ */
31
+ /**
32
+ * The top-level children that differ between two versions of one story,
33
+ * found by node identity from both ends. `null` when every child is shared.
34
+ */
35
+ const changedTopLevelWindow = (before, after) => {
36
+ if (before === after) return null;
37
+ const beforeCount = before.childCount;
38
+ const afterCount = after.childCount;
39
+ let from = 0;
40
+ while (from < beforeCount && from < afterCount && before.child(from) === after.child(from)) from += 1;
41
+ let beforeTo = beforeCount;
42
+ let afterTo = afterCount;
43
+ while (beforeTo > from && afterTo > from && before.child(beforeTo - 1) === after.child(afterTo - 1)) {
44
+ beforeTo -= 1;
45
+ afterTo -= 1;
46
+ }
47
+ if (from === beforeTo && from === afterTo) return null;
48
+ return {
49
+ from,
50
+ beforeTo,
51
+ afterTo
52
+ };
53
+ };
54
+ const TOP_LEVEL_PATH = /^package\.document\.content\[(\d+)\]/u;
55
+ /** Rebase a window-relative block path onto the story's own block index. */
56
+ const rebasePath = (path, offset) => path.replace(TOP_LEVEL_PATH, (_match, index) => `package.document.content[${String(Number(index) + offset)}]`);
57
+ const syntheticComment = (id, author) => ({
58
+ id,
59
+ author,
60
+ content: []
61
+ });
62
+ /** A story can identify its existing references even though the host owns the parts. */
63
+ const knownSectionParts = (doc) => {
64
+ const headers = /* @__PURE__ */ new Map();
65
+ const footers = /* @__PURE__ */ new Map();
66
+ doc.descendants((node) => {
67
+ if (node.type.name !== "paragraph") return true;
68
+ const section = sectionPropertiesOf(node);
69
+ for (const { rId, type } of section?.headerReferences ?? []) headers.set(rId, {
70
+ type: "header",
71
+ hdrFtrType: type,
72
+ content: []
73
+ });
74
+ for (const { rId, type } of section?.footerReferences ?? []) footers.set(rId, {
75
+ type: "footer",
76
+ hdrFtrType: type,
77
+ content: []
78
+ });
79
+ return false;
80
+ });
81
+ return {
82
+ headers,
83
+ footers
84
+ };
85
+ };
86
+ const windowErrors = ({ doc, from, to, context, knownParts, knownCommentIds }) => {
87
+ if (to <= from) return [];
88
+ const children = [];
89
+ for (let index = from; index < to; index += 1) children.push(doc.child(index));
90
+ let content;
91
+ try {
92
+ content = proseDocToBlocks(doc.copy(Fragment.fromArray(children)), []);
93
+ } catch (error) {
94
+ return [{
95
+ path: `package.document.content[${String(from)}]`,
96
+ message: `The content cannot be converted for saving: ${error instanceof Error ? error.message : String(error)}`,
97
+ severity: "error"
98
+ }];
99
+ }
100
+ return validateFolioDocumentModel({ package: {
101
+ document: {
102
+ content,
103
+ comments: [...[...knownCommentIds ?? []].map((id) => syntheticComment(id, "")), ...context.createdCommentIds.filter((id) => !knownCommentIds?.has(id)).map((id) => syntheticComment(id, context.commentAuthor ?? ""))]
104
+ },
105
+ ...context.numbering !== null && context.numbering !== void 0 && { numbering: context.numbering },
106
+ ...knownParts && {
107
+ headers: knownParts.headers,
108
+ footers: knownParts.footers
109
+ }
110
+ } }).issues.filter((issue) => issue.severity === "error" && !(context.numbering === void 0 && issue.path.endsWith(".numPr.numId"))).map(({ path, message, severity }) => ({
111
+ path: rebasePath(path, from),
112
+ message,
113
+ severity
114
+ }));
115
+ };
116
+ /**
117
+ * The save-validator errors the batch introduced: errors in the changed window
118
+ * of `after` that the same window of `before` did not already report. Empty
119
+ * when the result is as saveable as the story it started from.
120
+ */
121
+ const findIntroducedModelErrors = (before, after, context) => {
122
+ const window = changedTopLevelWindow(before, after);
123
+ if (window === null) return [];
124
+ let afterErrors = windowErrors({
125
+ doc: after,
126
+ from: window.from,
127
+ to: window.afterTo,
128
+ context
129
+ });
130
+ let parts;
131
+ if (afterErrors.some(({ message }) => message.startsWith("Section references missing "))) parts = knownSectionParts(before);
132
+ let knownCommentIds;
133
+ if (afterErrors.some(({ message }) => /^Comment \d+ is referenced but not present/u.test(message))) knownCommentIds = anchoredCommentIdsInProseDoc(before);
134
+ if (parts || knownCommentIds) afterErrors = windowErrors({
135
+ doc: after,
136
+ from: window.from,
137
+ to: window.afterTo,
138
+ context,
139
+ ...parts !== void 0 && { knownParts: parts },
140
+ ...knownCommentIds !== void 0 && { knownCommentIds }
141
+ });
142
+ if (afterErrors.length === 0) return afterErrors;
143
+ const known = /* @__PURE__ */ new Map();
144
+ for (const { message } of windowErrors({
145
+ doc: before,
146
+ from: window.from,
147
+ to: window.beforeTo,
148
+ context: {
149
+ ...context,
150
+ createdCommentIds: []
151
+ },
152
+ ...parts !== void 0 && { knownParts: parts },
153
+ ...knownCommentIds !== void 0 && { knownCommentIds }
154
+ })) known.set(message, (known.get(message) ?? 0) + 1);
155
+ return afterErrors.filter(({ message }) => {
156
+ const remaining = known.get(message) ?? 0;
157
+ if (remaining === 0) return true;
158
+ known.set(message, remaining - 1);
159
+ return false;
160
+ });
161
+ };
162
+ const describeModelError = ({ path, message }) => `The result would not save: ${path}: ${message}`;
163
+ //#endregion
164
+ export { describeModelError, findIntroducedModelErrors };
@@ -1,4 +1,5 @@
1
1
  import { FolioAIBlock, FolioAIBlockStructuralBoundary, FolioAIEditSnapshot, FolioAITextRangeHandle } from "./types.js";
2
+ import { NoteReferenceLabels } from "./note-references.js";
2
3
  import { CleanBlockText } from "./clean-text.js";
3
4
  import { RunStyleResolver } from "../prosemirror/runStyleFormatting.js";
4
5
  import { Node } from "prosemirror-model";
@@ -67,10 +68,12 @@ declare const isFolioAIContentBlock: ({ text }: Pick<FolioAIBlock, "text">) => b
67
68
  declare const trailingBodyBlockId: ({ blocks }: FolioAIEditSnapshot) => string | null;
68
69
  declare const hashFolioAIBlockText: (text: string) => string;
69
70
  /**
70
- * Canonical public projection of the clean view's zero-width structure.
71
+ * Canonical public projection of the clean view's inline structure.
71
72
  *
72
73
  * A field boundary stays internal: it marks text the reader already sees, so it
73
- * belongs to range resolution rather than to a block's published structure.
74
+ * belongs to range resolution rather than to a block's published structure. A
75
+ * note reference is published: its marker reads like text, and a reader has to
76
+ * be able to tell it is not.
74
77
  */
75
78
  declare const projectFolioAIBlockStructuralBoundaries: ({ structuralBoundaries }: Pick<CleanBlockText, "structuralBoundaries">) => readonly FolioAIBlockStructuralBoundary[];
76
79
  /** Stable precondition fingerprint for a block's zero-width structure. */
@@ -90,6 +93,36 @@ declare const createFolioAITextRangeHandle: ({ blockId, text, startOffset, endOf
90
93
  * whole subtree, so a table nested inside a hidden row is hidden with it.
91
94
  */
92
95
  declare const isHiddenTableRow: (node: Node) => boolean;
96
+ /**
97
+ * The marker every footnote/endnote reference of a story reads as: numbered in
98
+ * reading order over the blocks {@link createFolioAIEditSnapshot} reads (a
99
+ * hidden row's references are as absent as its text), so the applier, the
100
+ * range resolvers and the snapshot all read the same text.
101
+ *
102
+ * The story is numbered on the first label asked for, not here: a reader
103
+ * whose walk meets no reference (most documents, and every revision-stats
104
+ * read) never pays for a second walk of the story.
105
+ */
106
+ declare const collectNoteReferenceLabels: (doc: Node) => NoteReferenceLabels;
107
+ /**
108
+ * @internal The markers a comparison reads a revised story's references as:
109
+ * each note the base story references keeps the base's marker, and a note
110
+ * only the revised story references is numbered after them.
111
+ *
112
+ * Each story's own reading-order numbering would renumber every later marker
113
+ * of the revised story as soon as a reference is added or removed before it,
114
+ * and a text comparison would then read a reference both stories keep as an
115
+ * edit of it. Numbering the revised story on from the base keeps a kept
116
+ * reference's marker identical, and makes a reference the comparison brings
117
+ * write the marker the base story (which the edits resolve against) gives it.
118
+ */
119
+ declare const alignedNoteReferenceLabels: (base: Node, revised: Node) => NoteReferenceLabels;
120
+ /**
121
+ * @internal The same snapshot with every note reference read through `labels`
122
+ * rather than the story's own numbering. Only the text and structural
123
+ * boundaries can change: block ids, anchors and the source document do not.
124
+ */
125
+ declare const relabelFolioAIEditSnapshotNoteReferences: (snapshot: FolioAIEditSnapshot, labels: NoteReferenceLabels) => FolioAIEditSnapshot;
93
126
  /** One table of a story, numbered the way {@link createFolioAIEditSnapshot} numbers it. */
94
127
  type FolioStoryTable = {
95
128
  /** Document-order index over every table, nested ones included. */
@@ -110,4 +143,4 @@ declare const createFolioAIEditSnapshot: (doc: Node) => FolioAIEditSnapshot;
110
143
  /** @internal Use for an EditorState that owns the document's style resolver. */
111
144
  declare const createFolioAIEditSnapshotWithStyleResolver: (doc: Node, styleResolver: RunStyleResolver | null) => FolioAIEditSnapshot;
112
145
  //#endregion
113
- export { FolioStoryTable, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf, styleResolverOf, trailingBodyBlockId };
146
+ export { FolioStoryTable, alignedNoteReferenceLabels, collectNoteReferenceLabels, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, relabelFolioAIEditSnapshotNoteReferences, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf, styleResolverOf, trailingBodyBlockId };
@@ -1,8 +1,11 @@
1
+ import { isAltChunkMarkup } from "../docx/altChunk.js";
1
2
  import { EMPTY_BUILT_IN_STYLE_INDEX, resolveHeadingLevel } from "../docx/builtInStyles.js";
2
3
  import { resolveParagraphNumbering } from "../docx/numberingReference.js";
4
+ import { OPAQUE_REVISION_CARRIER_READER_DIAGNOSTIC, isOpaqueNestedRowMarkup, opaqueRevisionCarrierName } from "../docx/opaqueCarrier.js";
3
5
  import { recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
4
6
  import { expectCharacterStyleMarkAttrs, expectHyperlinkMarkAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
5
7
  import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
8
+ import { createListLabelCounter } from "../prosemirror/listLabels.js";
6
9
  import { paragraphNumberingAttr, readParagraphNumberingAttr } from "../prosemirror/numberingAttr.js";
7
10
  import { readOutlineLevelAttr } from "../prosemirror/outlineLevelAttr.js";
8
11
  import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
@@ -11,9 +14,11 @@ import { directParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
11
14
  import { runFormattingInlineAtomCleanText } from "../prosemirror/runFormattingInlineCarriers.js";
12
15
  import { authoredRunFormattingFromAttrs } from "../prosemirror/runFormattingProvenance.js";
13
16
  import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
14
- import { paragraphRunStyleContext } from "../prosemirror/runStyleFormatting.js";
17
+ import { paragraphRunStyleContext, resolveParagraphBodyRunFormatting } from "../prosemirror/runStyleFormatting.js";
15
18
  import { deriveBlankBlockId, deriveBlockId } from "../types/block-id.js";
19
+ import { splitsSurrogatePair } from "./character-boundaries.js";
16
20
  import { buildCleanBlockText } from "./clean-text.js";
21
+ import { createNoteReferenceLabeler } from "./note-references.js";
17
22
  import { panic } from "better-result";
18
23
  import { Fragment } from "prosemirror-model";
19
24
  import { TableMap } from "prosemirror-tables";
@@ -234,14 +239,25 @@ const hashFolioAIBlockText = (text) => {
234
239
  const EMPTY_FOLIO_AI_BLOCK_STRUCTURAL_BOUNDARIES = Object.freeze([]);
235
240
  const EMPTY_FOLIO_AI_BLOCK_STRUCTURAL_BOUNDARY_HASH = hashFolioAIBlockText(JSON.stringify(EMPTY_FOLIO_AI_BLOCK_STRUCTURAL_BOUNDARIES));
236
241
  /**
237
- * Canonical public projection of the clean view's zero-width structure.
242
+ * Canonical public projection of the clean view's inline structure.
238
243
  *
239
244
  * A field boundary stays internal: it marks text the reader already sees, so it
240
- * belongs to range resolution rather than to a block's published structure.
245
+ * belongs to range resolution rather than to a block's published structure. A
246
+ * note reference is published: its marker reads like text, and a reader has to
247
+ * be able to tell it is not.
241
248
  */
242
249
  const projectFolioAIBlockStructuralBoundaries = ({ structuralBoundaries }) => {
243
250
  let projected;
244
251
  for (const boundary of structuralBoundaries) {
252
+ if (boundary.type === "noteReference") {
253
+ (projected ??= []).push({
254
+ type: "noteReference",
255
+ noteType: boundary.noteType,
256
+ offset: boundary.offset,
257
+ length: boundary.length
258
+ });
259
+ continue;
260
+ }
245
261
  if (boundary.type !== "pageBreakRun" || !boundary.presentInCleanView) continue;
246
262
  (projected ??= []).push({
247
263
  type: "pageBreak",
@@ -255,7 +271,7 @@ const hashFolioAIBlockStructuralBoundaryProjection = (structuralBoundaries) => s
255
271
  /** Stable precondition fingerprint for a block's zero-width structure. */
256
272
  const hashFolioAIBlockStructuralBoundaries = (cleanBlock) => hashFolioAIBlockStructuralBoundaryProjection(projectFolioAIBlockStructuralBoundaries(cleanBlock));
257
273
  const createFolioAITextRangeHandle = ({ blockId, text, startOffset, endOffset }) => {
258
- if (blockId.length === 0 || !Number.isInteger(startOffset) || !Number.isInteger(endOffset) || startOffset < 0 || endOffset <= startOffset || endOffset > text.length) return null;
274
+ if (blockId.length === 0 || !Number.isInteger(startOffset) || !Number.isInteger(endOffset) || startOffset < 0 || endOffset <= startOffset || endOffset > text.length || splitsSurrogatePair(text, startOffset) || splitsSurrogatePair(text, endOffset)) return null;
259
275
  return {
260
276
  type: "textRange",
261
277
  story: "main",
@@ -276,6 +292,64 @@ const TABLE_ROLE_ROW = "row";
276
292
  * whole subtree, so a table nested inside a hidden row is hidden with it.
277
293
  */
278
294
  const isHiddenTableRow = (node) => node.type.name === TABLE_ROW_NODE_NAME && node.attrs["hidden"] === true;
295
+ const noteReferenceLabelsByStory = /* @__PURE__ */ new WeakMap();
296
+ /**
297
+ * The marker every footnote/endnote reference of a story reads as: numbered in
298
+ * reading order over the blocks {@link createFolioAIEditSnapshot} reads (a
299
+ * hidden row's references are as absent as its text), so the applier, the
300
+ * range resolvers and the snapshot all read the same text.
301
+ *
302
+ * The story is numbered on the first label asked for, not here: a reader
303
+ * whose walk meets no reference (most documents, and every revision-stats
304
+ * read) never pays for a second walk of the story.
305
+ */
306
+ const collectNoteReferenceLabels = (doc) => {
307
+ const cached = noteReferenceLabelsByStory.get(doc);
308
+ if (cached !== void 0) return cached;
309
+ const labels = numberNoteReferencesLazily([doc]);
310
+ noteReferenceLabelsByStory.set(doc, labels);
311
+ return labels;
312
+ };
313
+ /** Number the stories' references in reading order, one story after another, on first use. */
314
+ const numberNoteReferencesLazily = (stories) => {
315
+ let numbered;
316
+ const numberStories = () => {
317
+ const labeler = createNoteReferenceLabeler();
318
+ for (const doc of stories) doc.descendants((node) => {
319
+ if (isHiddenTableRow(node)) return false;
320
+ if (node.isTextblock) {
321
+ labeler.numberBlock(node);
322
+ return false;
323
+ }
324
+ return true;
325
+ });
326
+ return labeler;
327
+ };
328
+ return { labelOf: (reference) => (numbered ??= numberStories()).labelOf(reference) };
329
+ };
330
+ /**
331
+ * @internal The markers a comparison reads a revised story's references as:
332
+ * each note the base story references keeps the base's marker, and a note
333
+ * only the revised story references is numbered after them.
334
+ *
335
+ * Each story's own reading-order numbering would renumber every later marker
336
+ * of the revised story as soon as a reference is added or removed before it,
337
+ * and a text comparison would then read a reference both stories keep as an
338
+ * edit of it. Numbering the revised story on from the base keeps a kept
339
+ * reference's marker identical, and makes a reference the comparison brings
340
+ * write the marker the base story (which the edits resolve against) gives it.
341
+ */
342
+ const alignedNoteReferenceLabels = (base, revised) => numberNoteReferencesLazily([base, revised]);
343
+ /**
344
+ * @internal The same snapshot with every note reference read through `labels`
345
+ * rather than the story's own numbering. Only the text and structural
346
+ * boundaries can change: block ids, anchors and the source document do not.
347
+ */
348
+ const relabelFolioAIEditSnapshotNoteReferences = (snapshot, labels) => {
349
+ const metadata = metadataOf(snapshot);
350
+ if (!snapshot.blocks.some((block) => block.structuralBoundaries?.some(({ type }) => type === "noteReference"))) return snapshot;
351
+ return createFolioAIEditSnapshotInternal(metadata.sourceDocument, metadata.styleResolver, labels);
352
+ };
279
353
  const STORY_TABLE_CONTAINER = "container";
280
354
  const STORY_TABLE_HIDDEN_SUBTREE = "hidden-subtree";
281
355
  const STORY_TABLE = "table";
@@ -336,12 +410,17 @@ const getTableLocation = ({ path, blockIndex, tableIndexByStart }) => {
336
410
  };
337
411
  }
338
412
  };
339
- const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
413
+ const createFolioAIEditSnapshotInternal = (doc, styleResolver, noteReferenceLabels) => {
340
414
  const builtInStyles = styleResolver?.builtInStyles ?? EMPTY_BUILT_IN_STYLE_INDEX;
415
+ const noteReferences = noteReferenceLabels ? {
416
+ labelOf: noteReferenceLabels.labelOf,
417
+ numberBlock: () => {}
418
+ } : createNoteReferenceLabeler();
341
419
  const draftBlocks = [];
342
420
  const hashCounts = /* @__PURE__ */ new Map();
343
421
  const usedBlockIds = /* @__PURE__ */ new Set();
344
422
  const numberingReferenceKeys = /* @__PURE__ */ new Set();
423
+ const nextListLabel = createListLabelCounter();
345
424
  const tables = [];
346
425
  const tableIndexByStart = /* @__PURE__ */ new Map();
347
426
  const path = [];
@@ -352,6 +431,44 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
352
431
  if (!node.isTextblock) {
353
432
  const disposition = classifyNonTextblockStoryTableNode(node);
354
433
  if (disposition === STORY_TABLE_HIDDEN_SUBTREE) return false;
434
+ const preserved = node.attrs["_preserved"];
435
+ const preservedChildren = preserved !== null && typeof preserved === "object" && "children" in preserved ? preserved.children : void 0;
436
+ if (Array.isArray(preservedChildren)) for (const [carrierIndex, child] of preservedChildren.entries()) {
437
+ if (child === null || typeof child !== "object" || !("xml" in child)) continue;
438
+ const xml = child.xml;
439
+ if (typeof xml !== "string") continue;
440
+ const diagnostic = getOpaqueCarrierDiagnostic(xml);
441
+ if (diagnostic === void 0) continue;
442
+ draftBlocks.push({ block: {
443
+ id: `opaque-${String(pos)}-${String(carrierIndex)}`,
444
+ kind: "diagnostic",
445
+ text: diagnostic.text,
446
+ diagnostic: {
447
+ type: "opaqueCarrier",
448
+ carrier: diagnostic.carrier
449
+ }
450
+ } });
451
+ }
452
+ if (node.type.name === "preservedBlock") {
453
+ const xml = node.attrs["xml"];
454
+ if (typeof xml === "string") {
455
+ const readerText = node.attrs["readerText"];
456
+ const diagnostic = getOpaqueCarrierDiagnostic(xml, typeof readerText === "string" ? readerText : void 0);
457
+ if (diagnostic !== void 0) {
458
+ const id = `opaque-${String(pos)}`;
459
+ draftBlocks.push({ block: {
460
+ id,
461
+ kind: "diagnostic",
462
+ text: diagnostic.text,
463
+ diagnostic: {
464
+ type: "opaqueCarrier",
465
+ carrier: diagnostic.carrier
466
+ }
467
+ } });
468
+ }
469
+ }
470
+ return false;
471
+ }
355
472
  if (disposition === STORY_TABLE) {
356
473
  const tableIndex = tables.length;
357
474
  tables.push({
@@ -369,7 +486,11 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
369
486
  });
370
487
  return true;
371
488
  }
372
- const cleanBlock = buildCleanBlockText(node, pos);
489
+ noteReferences.numberBlock(node);
490
+ const cleanBlock = buildCleanBlockText(node, pos, {
491
+ fieldResults: "text",
492
+ noteReferences
493
+ });
373
494
  const { text } = cleanBlock;
374
495
  const structuralBoundaries = projectFolioAIBlockStructuralBoundaries(cleanBlock);
375
496
  const structuralBoundaryHash = hashFolioAIBlockStructuralBoundaryProjection(structuralBoundaries);
@@ -398,9 +519,11 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
398
519
  }
399
520
  usedBlockIds.add(id);
400
521
  const headingLevel = getHeadingLevel(node, builtInStyles);
401
- const kind = getBlockKind(node, headingLevel);
402
- const displayLabel = getDisplayLabel(node, kind === "heading");
522
+ const listLabel = node.type.name === "paragraph" ? nextListLabel(expectParagraphAttrs(node)) : void 0;
523
+ const kind = getBlockKind(headingLevel, listLabel);
524
+ const displayLabel = getDisplayLabel(node, listLabel, kind === "heading");
403
525
  const styleId = getStyleId(node);
526
+ const directOutlineLevel = node.type.name === "paragraph" ? expectParagraphAttrs(node)._originalFormatting?.outlineLevel : void 0;
404
527
  const listLevel = getListLevel(node);
405
528
  const listReference = getListReference(node);
406
529
  const numberingReferenceKey = getNumberingReferenceKey(node);
@@ -428,6 +551,7 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
428
551
  ...headingLevel !== void 0 && { headingLevel },
429
552
  ...displayLabel !== void 0 && { displayLabel },
430
553
  ...styleId !== void 0 && { styleId },
554
+ ...directOutlineLevel !== void 0 && { directOutlineLevel },
431
555
  ...listLevel !== void 0 && { listLevel },
432
556
  ...listReference !== void 0 && { listReference },
433
557
  ...directAlignment !== void 0 && { directAlignment },
@@ -453,6 +577,7 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
453
577
  const anchors = {};
454
578
  for (const draft of draftBlocks) {
455
579
  blocks.push(draft.block);
580
+ if (draft.anchor === void 0) continue;
456
581
  anchors[draft.block.id] = {
457
582
  ...draft.anchor,
458
583
  hashOccurrenceCount: hashCounts.get(draft.anchor.textHash) ?? 0
@@ -470,15 +595,37 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
470
595
  });
471
596
  return snapshot;
472
597
  };
598
+ const getOpaqueCarrierDiagnostic = (xml, readerText) => {
599
+ if (isAltChunkMarkup(xml)) return {
600
+ carrier: "w:altChunk",
601
+ text: readerText === void 0 ? "[Unsupported w:altChunk content]" : `[Unsupported w:altChunk content] ${readerText}`
602
+ };
603
+ const elementName = opaqueRevisionCarrierName(xml);
604
+ if (elementName !== void 0) return {
605
+ carrier: elementName,
606
+ text: OPAQUE_REVISION_CARRIER_READER_DIAGNOSTIC
607
+ };
608
+ return isOpaqueNestedRowMarkup(xml) ? {
609
+ carrier: "w:tr",
610
+ text: "[Unsupported nested w:tr content]"
611
+ } : void 0;
612
+ };
473
613
  const createFolioAIEditSnapshot = (doc) => createFolioAIEditSnapshotInternal(doc, null);
474
614
  /** @internal Use for an EditorState that owns the document's style resolver. */
475
615
  const createFolioAIEditSnapshotWithStyleResolver = (doc, styleResolver) => createFolioAIEditSnapshotInternal(doc, styleResolver);
476
- const getBlockKind = (node, headingLevel) => {
477
- const listMarker = node.attrs["listMarker"];
478
- const numPr = node.attrs["numPr"];
479
- if (typeof listMarker === "string" && listMarker.trim().length > 0 || numPr !== void 0 && numPr !== null) return "listItem";
616
+ /**
617
+ * What a reader sees the block as, heading first: a numbered heading (`1.
618
+ * Scope`, numbered through its style or its own `w:numPr`) is a heading that
619
+ * shows a number, with the number in `displayLabel` and its level in
620
+ * `listLevel`. A paragraph is a list item when it shows a marker, which is
621
+ * when it has a label. One whose numbering shows none (a `w:vanish` level, a
622
+ * level its list does not define, or `w:numId="0"` cancelling its style's
623
+ * numbering) reads as prose and is a paragraph, keeping its `listLevel` and
624
+ * `listReference`.
625
+ */
626
+ const getBlockKind = (headingLevel, listLabel) => {
480
627
  if (headingLevel !== void 0) return "heading";
481
- return "paragraph";
628
+ return listLabel === void 0 ? "paragraph" : "listItem";
482
629
  };
483
630
  /** The block's 1-based heading level, as {@link resolveHeadingLevel} classifies it. */
484
631
  const getHeadingLevel = (node, styles) => {
@@ -489,9 +636,8 @@ const getHeadingLevel = (node, styles) => {
489
636
  }, styles);
490
637
  return level === void 0 ? void 0 : level + 1;
491
638
  };
492
- const getDisplayLabel = (node, isHeading) => {
493
- const listMarker = node.attrs["listMarker"];
494
- if (typeof listMarker === "string" && listMarker.trim().length > 0) return listMarker.trim();
639
+ const getDisplayLabel = (node, listLabel, isHeading) => {
640
+ if (listLabel !== void 0) return listLabel;
495
641
  const styleId = node.attrs["styleId"];
496
642
  if (isHeading && typeof styleId === "string") return styleId;
497
643
  };
@@ -541,16 +687,19 @@ const RUN_FORMATTING_OVERRIDE_MARK = "runFormattingOverride";
541
687
  const CHARACTER_STYLE_MARK = "characterStyle";
542
688
  const getPreviewRuns = ({ node, nodeFrom, cleanBlock, styleResolver }) => {
543
689
  const runs = [];
544
- const defaultStyle = getDefaultPreviewRunStyle(node);
690
+ const defaultStyle = getDefaultPreviewRunStyle(node, styleResolver);
545
691
  let paragraphStyleContext;
546
692
  let cleanOffset = 0;
547
693
  node.descendants((child, relativePosition) => {
548
- const text = child.isText ? child.text : runFormattingInlineAtomCleanText(child);
694
+ const start = nodeFrom + 1 + relativePosition;
695
+ const noteReference = cleanBlock.structuralBoundaries.find((boundary) => boundary.type === "noteReference" && boundary.from === start);
696
+ let text;
697
+ if (noteReference !== void 0) text = cleanBlock.text.slice(noteReference.offset, noteReference.offset + noteReference.length);
698
+ else text = child.isText ? child.text : runFormattingInlineAtomCleanText(child);
549
699
  if (text === void 0 || text === null || text.length === 0) return true;
550
700
  if (child.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK)) return false;
551
- const start = nodeFrom + 1 + relativePosition;
552
701
  while ((cleanBlock.offsets[cleanOffset] ?? Number.POSITIVE_INFINITY) < start) cleanOffset++;
553
- const lastCharacterPosition = child.isText ? start + text.length - 1 : start;
702
+ const lastCharacterPosition = child.isText && noteReference === void 0 ? start + text.length - 1 : start;
554
703
  if (cleanBlock.offsets[cleanOffset] !== start || cleanBlock.offsets[cleanOffset + text.length - 1] !== lastCharacterPosition) return false;
555
704
  cleanOffset += text.length;
556
705
  const style = getPreviewRunStyle(child.marks, defaultStyle);
@@ -583,8 +732,13 @@ const getPreviewRuns = ({ node, nodeFrom, cleanBlock, styleResolver }) => {
583
732
  if (runs.every(isUnstyledPreviewRun)) return;
584
733
  return runs;
585
734
  };
586
- const getDefaultPreviewRunStyle = (node) => {
587
- const formatting = node.attrs["defaultTextFormatting"];
735
+ const getDefaultPreviewRunStyle = (node, styleResolver) => {
736
+ const attrs = expectParagraphAttrs(node);
737
+ const formatting = attrs.defaultTextFormatting ?? resolveParagraphBodyRunFormatting({
738
+ styleId: attrs.styleId,
739
+ tableRunFormatting: attrs._tableRunFormatting,
740
+ styleResolver
741
+ }).defaultFormatting;
588
742
  if (typeof formatting !== "object" || formatting === null) return {};
589
743
  return {
590
744
  ...getBooleanTextFormatting(formatting),
@@ -717,4 +871,4 @@ const isEmptyPreviewRunStyle = ({ bold, italic, underline, strike, fontFamily, f
717
871
  const sameDirectFormatting = (left, right) => left === void 0 && isEmptyPreviewRunStyle(right) || left !== void 0 && left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strike === right.strike && left.fontFamily === right.fontFamily && left.fontSizePt === right.fontSizePt && left.color === right.color;
718
872
  const isUnstyledPreviewRun = ({ directFormatting, ...style }) => isEmptyPreviewRunStyle(style) && (directFormatting === void 0 || isEmptyPreviewRunStyle(directFormatting));
719
873
  //#endregion
720
- export { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf, styleResolverOf, trailingBodyBlockId };
874
+ export { alignedNoteReferenceLabels, collectNoteReferenceLabels, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, relabelFolioAIEditSnapshotNoteReferences, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf, styleResolverOf, trailingBodyBlockId };
@@ -1,6 +1,6 @@
1
1
  import { TableRectangle } from "./table-mutation-plan.js";
2
- import { Transaction } from "prosemirror-state";
3
2
  import { Node } from "prosemirror-model";
3
+ import { Transaction } from "prosemirror-state";
4
4
  //#region src/ai-edits/table-cell-mutations.d.ts
5
5
  type TableCellRectangleMutationOptions = {
6
6
  tr: Transaction;
@@ -3,6 +3,7 @@ import { expectTableCellAttrs } from "../prosemirror/attrs/index.js";
3
3
  import { standaloneTableCellFromProseMirror } from "../prosemirror/conversion/fromProseDoc.js";
4
4
  import { standaloneTableCellToProseMirror } from "../prosemirror/conversion/toProseDoc.js";
5
5
  import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
6
+ import { removeRowsWithoutCells } from "../prosemirror/tableGridMutation.js";
6
7
  import { tableRectanglesEqual } from "./table-mutation-plan.js";
7
8
  import { tableRectangleCutsMergedCell } from "./table-targets.js";
8
9
  import { Result } from "better-result";
@@ -62,6 +63,7 @@ const mergeTableRectangle = ({ tr, tablePosition, table, rectangle }) => {
62
63
  const contentStart = isEmptyTableCell(merged.cell) ? absoluteMergedPosition + 1 : contentEnd;
63
64
  tr = tr.replaceWith(contentStart, contentEnd, appendedContent);
64
65
  }
66
+ removeRowsWithoutCells(tr, tablePosition);
65
67
  return tr;
66
68
  };
67
69
  const mergeTopRowColwidths = (cells, mergedColspan) => {
@@ -121,7 +123,8 @@ const mergeTrackedVerticalTableCells = ({ tr, tablePosition, table, rectangle, r
121
123
  });
122
124
  }
123
125
  const origin = cells.at(0);
124
- if (!origin || cells.length !== rectangle.bottom - rectangle.top || cells.some(({ cell }) => cell.type !== origin.cell.type) || cells.slice(1).some(({ cell }) => !isEmptyTableCell(cell))) return null;
126
+ const leavesRowWithoutCells = cells.slice(1).some(({ position }) => table.child(map.findCell(position).top).childCount === 1);
127
+ if (!origin || leavesRowWithoutCells || cells.length !== rectangle.bottom - rectangle.top || cells.some(({ cell }) => cell.type !== origin.cell.type) || cells.slice(1).some(({ cell }) => !isEmptyTableCell(cell))) return null;
125
128
  const continuationCells = [];
126
129
  for (const { cell } of cells.slice(1)) {
127
130
  const continuation = standaloneTableCellFromProseMirror(cell);
@@ -1,6 +1,6 @@
1
1
  import { FolioStoryTable } from "./snapshot.js";
2
- import { Transaction } from "prosemirror-state";
3
2
  import { Node } from "prosemirror-model";
3
+ import { Transaction } from "prosemirror-state";
4
4
  //#region src/ai-edits/table-geometry.d.ts
5
5
  /** Where a cell sits: which table, which row of it, which cell of that row. */
6
6
  type TableCellCoordinate = {