@stll/folio-core 0.37.5 → 0.38.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 (121) hide show
  1. package/dist/ai-edits/apply.js +275 -88
  2. package/dist/ai-edits/clean-text.js +12 -1
  3. package/dist/ai-edits/headless.d.ts +110 -0
  4. package/dist/ai-edits/headless.js +569 -40
  5. package/dist/ai-edits/snapshot.d.ts +24 -1
  6. package/dist/ai-edits/snapshot.js +206 -9
  7. package/dist/ai-edits/table-geometry.d.ts +7 -7
  8. package/dist/ai-edits/table-geometry.js +43 -27
  9. package/dist/ai-edits/table-template.d.ts +2 -1
  10. package/dist/ai-edits/table-template.js +9 -3
  11. package/dist/ai-edits/types.d.ts +37 -15
  12. package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
  13. package/dist/compare/__fixtures__/body-sequence.js +5 -3
  14. package/dist/compare/compare.d.ts +26 -2
  15. package/dist/compare/compare.js +444 -16
  16. package/dist/compare/content-alignment.js +3 -2
  17. package/dist/compare/content-types.d.ts +34 -2
  18. package/dist/compare/content.d.ts +13 -10
  19. package/dist/compare/content.js +69 -23
  20. package/dist/compare/formatting.d.ts +4 -22
  21. package/dist/compare/formatting.js +8 -112
  22. package/dist/compare/inline-atom-resources.d.ts +6 -0
  23. package/dist/compare/inline-atom-resources.js +26 -0
  24. package/dist/compare/inline-atoms.d.ts +35 -0
  25. package/dist/compare/inline-atoms.js +383 -0
  26. package/dist/compare/inline-provenance.d.ts +36 -0
  27. package/dist/compare/inline-provenance.js +334 -0
  28. package/dist/compare/plan.d.ts +4 -1
  29. package/dist/compare/plan.js +32 -4
  30. package/dist/compare/scenario.d.ts +4 -6
  31. package/dist/compare/section-boundary-properties.d.ts +63 -0
  32. package/dist/compare/section-boundary-properties.js +235 -0
  33. package/dist/compare/style-resources.d.ts +30 -0
  34. package/dist/compare/style-resources.js +404 -0
  35. package/dist/compare/types.d.ts +50 -11
  36. package/dist/compare/types.js +3 -1
  37. package/dist/compare/verification.d.ts +3 -5
  38. package/dist/compare/verification.js +8 -39
  39. package/dist/compat/eigenpal.d.ts +4 -4
  40. package/dist/compat/eigenpal.js +3 -3
  41. package/dist/document-operations.d.ts +5 -3
  42. package/dist/document-operations.js +112 -5
  43. package/dist/docx/blockContentParser.js +14 -2
  44. package/dist/docx/drawingIdNormalization.js +43 -2
  45. package/dist/docx/drawingRelationships.d.ts +10 -0
  46. package/dist/docx/drawingRelationships.js +28 -0
  47. package/dist/docx/fieldParser.js +1 -1
  48. package/dist/docx/hyperlinkParser.js +3 -5
  49. package/dist/docx/imageRawXml.d.ts +8 -0
  50. package/dist/docx/imageRawXml.js +13 -0
  51. package/dist/docx/newImage.d.ts +2 -2
  52. package/dist/docx/newImage.js +3 -3
  53. package/dist/docx/numberingParser.js +30 -27
  54. package/dist/docx/paragraphParser.js +11 -4
  55. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  56. package/dist/docx/removeHeaderFooterParts.js +87 -0
  57. package/dist/docx/rezip.d.ts +3 -1
  58. package/dist/docx/rezip.js +143 -41
  59. package/dist/docx/runParser.js +16 -5
  60. package/dist/docx/sectionParser.js +3 -0
  61. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  62. package/dist/docx/sectionReferenceHistory.js +59 -0
  63. package/dist/docx/selectiveSave.js +11 -31
  64. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  65. package/dist/docx/selectiveXmlPatch.js +62 -2
  66. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  67. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  68. package/dist/docx/serializer/partNamespaces.js +4 -0
  69. package/dist/docx/serializer/runSerializer.js +2 -1
  70. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  71. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  72. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  73. package/dist/docx/tableParser.js +21 -11
  74. package/dist/docx/xmlParser.d.ts +10 -1
  75. package/dist/docx/xmlParser.js +23 -1
  76. package/dist/index.d.ts +4 -4
  77. package/dist/index.js +3 -3
  78. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  79. package/dist/internal/compare/inline-presentation.js +222 -0
  80. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  81. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  82. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  83. package/dist/internal/sectionReferenceResolution.js +72 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  85. package/dist/markdown/renderRuns.js +1 -0
  86. package/dist/markdown/renderTable.js +1 -0
  87. package/dist/prosemirror/attrs/index.js +20 -1
  88. package/dist/prosemirror/commands/comments.d.ts +11 -1
  89. package/dist/prosemirror/commands/comments.js +68 -6
  90. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  91. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  92. package/dist/prosemirror/conversion/fromProseDoc.js +16 -8
  93. package/dist/prosemirror/conversion/toProseDoc.js +16 -57
  94. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  95. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  96. package/dist/prosemirror/imageCommit.js +7 -9
  97. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  98. package/dist/prosemirror/paragraphIndentation.js +72 -0
  99. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  100. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  101. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  102. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  103. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  104. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  105. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  106. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  107. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  108. package/dist/prosemirror/runStyleFormatting.js +76 -2
  109. package/dist/prosemirror/schema/marks.d.ts +3 -1
  110. package/dist/prosemirror/schema/marks.js +2 -0
  111. package/dist/prosemirror/schema/nodes.d.ts +3 -0
  112. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  113. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  114. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  115. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  116. package/dist/redline.js +15 -2
  117. package/dist/server.d.ts +2 -2
  118. package/dist/server.js +2 -2
  119. package/dist/version-comparison.d.ts +2 -0
  120. package/dist/version-comparison.js +7 -2
  121. package/package.json +2 -2
@@ -9,6 +9,29 @@ declare const numberingReferenceKeysOf: (snapshot: FolioAIEditSnapshot) => reado
9
9
  declare const storyTablesOf: (snapshot: FolioAIEditSnapshot) => readonly FolioStoryTable[];
10
10
  /** @internal The immutable ProseMirror document that produced this snapshot. */
11
11
  declare const sourceDocumentOf: (snapshot: FolioAIEditSnapshot) => Node;
12
+ /** @internal Style context that produced the snapshot's authored run properties. */
13
+ declare const styleResolverOf: (snapshot: FolioAIEditSnapshot) => RunStyleResolver | null;
14
+ /**
15
+ * Derive a comparison-only view with concrete numbering references rebound.
16
+ * Rebuild from the remapped source document so blocks, anchors, table nodes,
17
+ * and operation templates retain one canonical set of numbering references.
18
+ */
19
+ declare const remapFolioAIEditSnapshotNumberingReferences: (snapshot: FolioAIEditSnapshot, numIdMap: ReadonlyMap<number, number>) => FolioAIEditSnapshot;
20
+ /**
21
+ * Detach external hyperlink relationship ids before copying a story into a
22
+ * different package. The serializer allocates relationship ids for the
23
+ * receiving part; bookmark targets remain package-local and are refused.
24
+ */
25
+ declare const detachFolioAIEditSnapshotExternalHyperlinks: (snapshot: FolioAIEditSnapshot) => FolioAIEditSnapshot | null;
26
+ type RemapFolioAIEditSnapshotStyleReferencesOptions = {
27
+ snapshot: FolioAIEditSnapshot;
28
+ styleIdMap: ReadonlyMap<string, string>;
29
+ defaultParagraphStyleId: string | undefined;
30
+ importedStyleResolver: RunStyleResolver | null;
31
+ reconcileAuthoredFormatting?: boolean;
32
+ };
33
+ /** Rebind imported style identifiers while retaining the source formatting context. */
34
+ declare const remapFolioAIEditSnapshotStyleReferences: ({ snapshot, styleIdMap, defaultParagraphStyleId, importedStyleResolver, reconcileAuthoredFormatting }: RemapFolioAIEditSnapshotStyleReferencesOptions) => FolioAIEditSnapshot;
12
35
  declare const normalizeFolioAIBlockText: (text: string) => string;
13
36
  /**
14
37
  * Whether a block carries text a reader would see.
@@ -82,4 +105,4 @@ declare const createFolioAIEditSnapshot: (doc: Node) => FolioAIEditSnapshot;
82
105
  /** @internal Use for an EditorState that owns the document's style resolver. */
83
106
  declare const createFolioAIEditSnapshotWithStyleResolver: (doc: Node, styleResolver: RunStyleResolver | null) => FolioAIEditSnapshot;
84
107
  //#endregion
85
- export { FolioStoryTable, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, sourceDocumentOf, storyTablesOf, trailingBodyBlockId };
108
+ export { FolioStoryTable, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf, styleResolverOf, trailingBodyBlockId };
@@ -1,12 +1,17 @@
1
- import { expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
1
+ import { recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
2
+ import { expectCharacterStyleMarkAttrs, expectHyperlinkMarkAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
2
3
  import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
3
4
  import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
5
+ import { directParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
4
6
  import { directParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
7
+ import { runFormattingInlineControlCharacter } from "../prosemirror/runFormattingInlineCarriers.js";
5
8
  import { authoredRunFormattingFromAttrs } from "../prosemirror/runFormattingProvenance.js";
9
+ import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
6
10
  import { paragraphRunStyleContext } from "../prosemirror/runStyleFormatting.js";
7
11
  import { deriveBlankBlockId, deriveBlockId } from "../types/block-id.js";
8
12
  import { buildCleanBlockText } from "./clean-text.js";
9
13
  import { panic } from "better-result";
14
+ import { Fragment } from "prosemirror-model";
10
15
  import { TableMap } from "prosemirror-tables";
11
16
  //#region src/ai-edits/snapshot.ts
12
17
  const metadataBySnapshot = /* @__PURE__ */ new WeakMap();
@@ -17,6 +22,169 @@ const numberingReferenceKeysOf = (snapshot) => metadataOf(snapshot).numberingRef
17
22
  const storyTablesOf = (snapshot) => metadataOf(snapshot).storyTables;
18
23
  /** @internal The immutable ProseMirror document that produced this snapshot. */
19
24
  const sourceDocumentOf = (snapshot) => metadataOf(snapshot).sourceDocument;
25
+ /** @internal Style context that produced the snapshot's authored run properties. */
26
+ const styleResolverOf = (snapshot) => metadataOf(snapshot).styleResolver;
27
+ /**
28
+ * Derive a comparison-only view with concrete numbering references rebound.
29
+ * Rebuild from the remapped source document so blocks, anchors, table nodes,
30
+ * and operation templates retain one canonical set of numbering references.
31
+ */
32
+ const remapFolioAIEditSnapshotNumberingReferences = (snapshot, numIdMap) => {
33
+ if (numIdMap.size === 0) return snapshot;
34
+ const metadata = metadataOf(snapshot);
35
+ const remapNode = (node) => {
36
+ const numPr = node.attrs["numPr"];
37
+ if (typeof numPr !== "object" || numPr === null || !("numId" in numPr)) return node;
38
+ const numId = numPr.numId;
39
+ if (typeof numId !== "number") return node;
40
+ const remappedNumId = numIdMap.get(numId);
41
+ if (remappedNumId === void 0) return node;
42
+ return recreateProseNodeWithParagraphPropertySource(node, { attrs: {
43
+ ...node.attrs,
44
+ numPr: {
45
+ ...numPr,
46
+ numId: remappedNumId
47
+ }
48
+ } });
49
+ };
50
+ const remapped = remapDocument(metadata.sourceDocument, remapNode);
51
+ if (remapped === metadata.sourceDocument) return snapshot;
52
+ return createFolioAIEditSnapshotInternal(remapped, metadata.styleResolver);
53
+ };
54
+ const remapDocument = (doc, remapNode) => {
55
+ const rewrite = (node) => {
56
+ if (node.isText) return remapNode(node);
57
+ const children = [];
58
+ let changed = false;
59
+ node.forEach((child) => {
60
+ const next = rewrite(child);
61
+ if (next !== child) changed = true;
62
+ children.push(next);
63
+ });
64
+ return remapNode(changed ? recreateProseNodeWithParagraphPropertySource(node, { content: Fragment.fromArray(children) }) : node);
65
+ };
66
+ return rewrite(doc);
67
+ };
68
+ const externalHrefCanCrossPackage = (href) => href.length > 0 && !href.startsWith("#");
69
+ const hyperlinkCanCrossPackage = ({ href, rId }) => externalHrefCanCrossPackage(href) || href.length === 0 && !(typeof rId === "string" && rId.length > 0);
70
+ /**
71
+ * Detach external hyperlink relationship ids before copying a story into a
72
+ * different package. The serializer allocates relationship ids for the
73
+ * receiving part; bookmark targets remain package-local and are refused.
74
+ */
75
+ const detachFolioAIEditSnapshotExternalHyperlinks = (snapshot) => {
76
+ const metadata = metadataOf(snapshot);
77
+ let portable = true;
78
+ metadata.sourceDocument.descendants((node) => {
79
+ const emptyHyperlinks = node.attrs["_emptyHyperlinks"];
80
+ if (Array.isArray(emptyHyperlinks)) {
81
+ for (const hyperlink of emptyHyperlinks) if (typeof hyperlink !== "object" || hyperlink === null || "anchor" in hyperlink || "rId" in hyperlink && (typeof hyperlink.href !== "string" || !externalHrefCanCrossPackage(hyperlink.href))) {
82
+ portable = false;
83
+ return false;
84
+ }
85
+ }
86
+ for (const mark of node.marks) {
87
+ if (mark.type.name !== "hyperlink") continue;
88
+ if (!hyperlinkCanCrossPackage(expectHyperlinkMarkAttrs(mark))) {
89
+ portable = false;
90
+ return false;
91
+ }
92
+ }
93
+ return true;
94
+ });
95
+ if (!portable) return null;
96
+ const remapNode = (node) => {
97
+ const emptyHyperlinks = node.attrs["_emptyHyperlinks"];
98
+ const attrs = Array.isArray(emptyHyperlinks) ? {
99
+ ...node.attrs,
100
+ _emptyHyperlinks: emptyHyperlinks.map(({ rId: _rId, ...hyperlink }) => hyperlink)
101
+ } : node.attrs;
102
+ let marks = node.marks;
103
+ for (const mark of node.marks) {
104
+ if (mark.type.name !== "hyperlink") continue;
105
+ const { rId: _rId, ...hyperlink } = expectHyperlinkMarkAttrs(mark);
106
+ marks = marks.map((candidate) => candidate === mark ? mark.type.create(hyperlink) : candidate);
107
+ }
108
+ if (attrs === node.attrs && marks === node.marks) return node;
109
+ if (node.isText) return node.mark(marks);
110
+ return recreateProseNodeWithParagraphPropertySource(node, {
111
+ attrs,
112
+ marks
113
+ });
114
+ };
115
+ const remapped = remapDocument(metadata.sourceDocument, remapNode);
116
+ return remapped === metadata.sourceDocument ? snapshot : createFolioAIEditSnapshotInternal(remapped, metadata.styleResolver);
117
+ };
118
+ /** Rebind imported style identifiers while retaining the source formatting context. */
119
+ const remapFolioAIEditSnapshotStyleReferences = ({ snapshot, styleIdMap, defaultParagraphStyleId, importedStyleResolver, reconcileAuthoredFormatting = false }) => {
120
+ if (defaultParagraphStyleId === void 0 && (styleIdMap.size === 0 || [...styleIdMap].every(([source, target]) => source === target))) return snapshot;
121
+ const metadata = metadataOf(snapshot);
122
+ const remapNode = (node) => {
123
+ const styleId = node.attrs["styleId"];
124
+ const remappedStyleId = typeof styleId === "string" ? styleIdMap.get(styleId) : void 0;
125
+ if (remappedStyleId !== void 0) return recreateProseNodeWithParagraphPropertySource(node, { attrs: {
126
+ ...node.attrs,
127
+ styleId: remappedStyleId
128
+ } });
129
+ if (node.isTextblock && typeof styleId !== "string" && defaultParagraphStyleId !== void 0) return recreateProseNodeWithParagraphPropertySource(node, { attrs: {
130
+ ...node.attrs,
131
+ styleId: defaultParagraphStyleId
132
+ } });
133
+ if (!node.isText) return node;
134
+ let marks = node.marks;
135
+ for (const mark of node.marks) {
136
+ if (mark.type.name !== "characterStyle") continue;
137
+ const attrs = expectCharacterStyleMarkAttrs(mark);
138
+ const remapped = styleIdMap.get(attrs.styleId);
139
+ if (remapped === void 0) continue;
140
+ marks = marks.map((candidate) => candidate === mark ? mark.type.create({
141
+ ...attrs,
142
+ styleId: remapped
143
+ }) : candidate);
144
+ }
145
+ return marks === node.marks ? node : node.mark(marks);
146
+ };
147
+ const remapped = remapDocument(metadata.sourceDocument, remapNode);
148
+ if (remapped === metadata.sourceDocument) return snapshot;
149
+ if (!reconcileAuthoredFormatting) return createFolioAIEditSnapshotInternal(remapped, importedStyleResolver);
150
+ const rebindAuthoredInlineFormatting = (source, candidate) => {
151
+ if (source.childCount !== candidate.childCount) return panic("Style remapping changed the document structure");
152
+ if (source.isInline) {
153
+ if (!source.marks.some(({ type }) => type.name === "runFormattingOverride" || type.name === "characterStyle")) return candidate;
154
+ return candidate;
155
+ }
156
+ const sourceContext = source.type.name === "paragraph" ? paragraphRunStyleContext(source, metadata.styleResolver) : void 0;
157
+ const candidateContext = candidate.type.name === "paragraph" ? paragraphRunStyleContext(candidate, importedStyleResolver) : void 0;
158
+ const children = [];
159
+ let changed = false;
160
+ source.forEach((sourceChild, _offset, index) => {
161
+ const candidateChild = candidate.child(index);
162
+ let next = rebindAuthoredInlineFormatting(sourceChild, candidateChild);
163
+ if (sourceContext && candidateContext && sourceChild.isInline) {
164
+ if (sourceChild.marks.some(({ type }) => type.name === "runFormattingOverride" || type.name === "characterStyle")) {
165
+ const authoredFormatting = readAuthoredRunFormatting({
166
+ context: sourceContext,
167
+ marks: sourceChild.marks,
168
+ styleResolver: metadata.styleResolver
169
+ });
170
+ if (authoredFormatting.styleId !== void 0) authoredFormatting.styleId = styleIdMap.get(authoredFormatting.styleId) ?? authoredFormatting.styleId;
171
+ const marks = reconcileRunFormattingMarks({
172
+ authoredFormatting,
173
+ context: candidateContext,
174
+ node: candidateChild,
175
+ styleResolver: importedStyleResolver
176
+ });
177
+ next = candidateChild.isText ? candidateChild.mark(marks) : recreateProseNodeWithParagraphPropertySource(candidateChild, { marks });
178
+ }
179
+ }
180
+ if (next !== candidateChild) changed = true;
181
+ children.push(next);
182
+ });
183
+ return changed ? recreateProseNodeWithParagraphPropertySource(candidate, { content: Fragment.fromArray(children) }) : candidate;
184
+ };
185
+ const rebound = rebindAuthoredInlineFormatting(metadata.sourceDocument, remapped);
186
+ return createFolioAIEditSnapshotInternal(rebound, importedStyleResolver);
187
+ };
20
188
  const normalizeFolioAIBlockText = (text) => text.replace(/\s+/gu, " ").trim();
21
189
  /**
22
190
  * Whether a block carries text a reader would see.
@@ -226,11 +394,18 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
226
394
  const displayLabel = getDisplayLabel(node);
227
395
  const styleId = getStyleId(node);
228
396
  const listLevel = getListLevel(node);
397
+ const listReference = getListReference(node);
229
398
  const numberingReferenceKey = getNumberingReferenceKey(node);
230
399
  if (numberingReferenceKey) numberingReferenceKeys.add(numberingReferenceKey);
231
400
  const directAlignment = getDirectAlignment(node);
232
401
  const directSpacing = getDirectSpacing(node);
233
- const previewRuns = getPreviewRuns(node, styleResolver);
402
+ const directIndentation = getDirectIndentation(node);
403
+ const previewRuns = getPreviewRuns({
404
+ node,
405
+ nodeFrom: pos,
406
+ cleanBlock,
407
+ styleResolver
408
+ });
234
409
  const table = getTableLocation({
235
410
  path,
236
411
  blockIndex: index,
@@ -246,8 +421,10 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
246
421
  ...displayLabel !== void 0 && { displayLabel },
247
422
  ...styleId !== void 0 && { styleId },
248
423
  ...listLevel !== void 0 && { listLevel },
424
+ ...listReference !== void 0 && { listReference },
249
425
  ...directAlignment !== void 0 && { directAlignment },
250
426
  ...directSpacing !== void 0 && { directSpacing },
427
+ ...directIndentation !== void 0 && { directIndentation },
251
428
  ...previewRuns !== void 0 && { previewRuns },
252
429
  ...structuralBoundaries.length > 0 && { structuralBoundaries },
253
430
  ...table !== void 0 && { table }
@@ -280,6 +457,7 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
280
457
  metadataBySnapshot.set(snapshot, {
281
458
  numberingReferenceKeys: [...numberingReferenceKeys],
282
459
  sourceDocument: doc,
460
+ styleResolver,
283
461
  storyTables: tables
284
462
  });
285
463
  return snapshot;
@@ -314,6 +492,16 @@ const getListLevel = (node) => {
314
492
  const { ilvl } = numPr;
315
493
  return typeof ilvl === "number" && Number.isInteger(ilvl) && ilvl >= 0 ? ilvl : void 0;
316
494
  };
495
+ const getListReference = (node) => {
496
+ const numPr = node.attrs["numPr"];
497
+ if (typeof numPr !== "object" || numPr === null || !("numId" in numPr)) return void 0;
498
+ const { numId } = numPr;
499
+ const level = "ilvl" in numPr ? numPr.ilvl : 0;
500
+ return typeof numId === "number" && Number.isInteger(numId) && numId > 0 && typeof level === "number" && Number.isInteger(level) && level >= 0 ? {
501
+ numId,
502
+ level
503
+ } : void 0;
504
+ };
317
505
  const getNumberingReferenceKey = (node) => {
318
506
  const numPr = node.attrs["numPr"];
319
507
  if (typeof numPr !== "object" || numPr === null || !("numId" in numPr)) return null;
@@ -331,17 +519,26 @@ const getStyleId = (node) => {
331
519
  const getDirectAlignment = (node) => directParagraphAlignment(expectParagraphAttrs(node));
332
520
  /** Read only authored `w:spacing`, never effective spacing resolved from a style. */
333
521
  const getDirectSpacing = (node) => directParagraphSpacing(expectParagraphAttrs(node));
522
+ /** Read only authored `w:ind`, never effective indentation resolved from a style. */
523
+ const getDirectIndentation = (node) => directParagraphIndentation(expectParagraphAttrs(node));
334
524
  const DELETION_MARK = "deletion";
335
525
  const HIDDEN_MARK = "hidden";
336
526
  const RUN_FORMATTING_OVERRIDE_MARK = "runFormattingOverride";
337
527
  const CHARACTER_STYLE_MARK = "characterStyle";
338
- const getPreviewRuns = (node, styleResolver) => {
528
+ const getPreviewRuns = ({ node, nodeFrom, cleanBlock, styleResolver }) => {
339
529
  const runs = [];
340
530
  const defaultStyle = getDefaultPreviewRunStyle(node);
341
531
  let paragraphStyleContext;
342
- node.descendants((child) => {
343
- if (!child.isText || child.text === void 0) return true;
532
+ let cleanOffset = 0;
533
+ node.descendants((child, relativePosition) => {
534
+ const text = child.isText ? child.text : runFormattingInlineControlCharacter(child);
535
+ if (text === void 0 || text === null) return true;
344
536
  if (child.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK)) return false;
537
+ const start = nodeFrom + 1 + relativePosition;
538
+ while ((cleanBlock.offsets[cleanOffset] ?? Number.POSITIVE_INFINITY) < start) cleanOffset++;
539
+ const endOffset = cleanOffset + text.length - 1;
540
+ if (cleanBlock.offsets[cleanOffset] !== start || cleanBlock.offsets[endOffset] !== start + text.length - 1) return false;
541
+ cleanOffset += text.length;
345
542
  const style = getPreviewRunStyle(child.marks, defaultStyle);
346
543
  const hasAuthorshipCarrier = child.marks.some(({ type }) => type.name === RUN_FORMATTING_OVERRIDE_MARK || type.name === CHARACTER_STYLE_MARK);
347
544
  let directFormatting;
@@ -359,11 +556,11 @@ const getPreviewRuns = (node, styleResolver) => {
359
556
  }
360
557
  const previous = runs.at(-1);
361
558
  if (previous && samePreviewRunStyle(previous, style) && sameDirectFormatting(previous.directFormatting, directFormatting)) {
362
- previous.text += child.text;
559
+ previous.text += text;
363
560
  return false;
364
561
  }
365
562
  runs.push({
366
- text: child.text,
563
+ text,
367
564
  ...style,
368
565
  ...!isEmptyPreviewRunStyle(directFormatting) && { directFormatting }
369
566
  });
@@ -504,6 +701,6 @@ const getColorFromAttrs = (attrs) => {
504
701
  const samePreviewRunStyle = (run, style) => run.bold === style.bold && run.italic === style.italic && run.underline === style.underline && run.strike === style.strike && run.fontFamily === style.fontFamily && run.fontSizePt === style.fontSizePt && run.color === style.color;
505
702
  const isEmptyPreviewRunStyle = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
506
703
  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;
507
- const isUnstyledPreviewRun = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
704
+ const isUnstyledPreviewRun = ({ directFormatting, ...style }) => isEmptyPreviewRunStyle(style) && (directFormatting === void 0 || isEmptyPreviewRunStyle(directFormatting));
508
705
  //#endregion
509
- export { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, sourceDocumentOf, storyTablesOf, trailingBodyBlockId };
706
+ export { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, numberingReferenceKeysOf, projectFolioAIBlockStructuralBoundaries, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf, styleResolverOf, trailingBodyBlockId };
@@ -2,13 +2,6 @@ import { FolioStoryTable } from "./snapshot.js";
2
2
  import { Transaction } from "prosemirror-state";
3
3
  import { Node } from "prosemirror-model";
4
4
  //#region src/ai-edits/table-geometry.d.ts
5
- /**
6
- * One line per table, in the document order tables are numbered in: the
7
- * table's own properties, then each row's and each cell's. Nested tables get
8
- * their own lines rather than being folded into their parent's, so a
9
- * difference names the table it is in.
10
- */
11
- declare const projectTableGeometry: (tables: readonly FolioStoryTable[]) => string[];
12
5
  /** Where a cell sits: which table, which row of it, which cell of that row. */
13
6
  type TableCellCoordinate = {
14
7
  tableIndex: number;
@@ -39,6 +32,13 @@ type MatchTableGeometryResult = {
39
32
  /** Tables, rows and cells whose properties the match moved. */
40
33
  matched: number;
41
34
  };
35
+ /**
36
+ * One line per table, in the document order tables are numbered in: the
37
+ * table's own properties, then each row's and each cell's. Nested tables get
38
+ * their own lines rather than being folded into their parent's, so a
39
+ * difference names the table it is in.
40
+ */
41
+ declare const projectTableGeometry: (tables: readonly FolioStoryTable[]) => string[];
42
42
  /**
43
43
  * Copy a paired table's, row's and cell's properties from the document it was
44
44
  * compared against, recording the previous set so a reject restores it.
@@ -36,31 +36,6 @@ const scopedAttrs = (attrs, keys) => {
36
36
  }
37
37
  return scoped;
38
38
  };
39
- const cellProjection = (cell) => canonicalJson({
40
- colspan: cell.attrs["colspan"],
41
- rowspan: cell.attrs["rowspan"],
42
- ...scopedAttrs(cell.attrs, CELL_SCOPED_ATTRS)
43
- });
44
- const rowProjection = (row) => {
45
- const cells = [];
46
- row.forEach((cell) => {
47
- cells.push(cellProjection(cell));
48
- });
49
- return `${canonicalJson(scopedAttrs(row.attrs, ROW_SCOPED_ATTRS))}|${cells.join("|")}`;
50
- };
51
- /**
52
- * One line per table, in the document order tables are numbered in: the
53
- * table's own properties, then each row's and each cell's. Nested tables get
54
- * their own lines rather than being folded into their parent's, so a
55
- * difference names the table it is in.
56
- */
57
- const projectTableGeometry = (tables) => tables.map(({ node }) => {
58
- const rows = [];
59
- node.forEach((row) => {
60
- rows.push(rowProjection(row));
61
- });
62
- return `${canonicalJson(scopedAttrs(node.attrs, TABLE_SCOPED_ATTRS))}#${rows.join("#")}`;
63
- });
64
39
  const childPositions = (node, start) => {
65
40
  const positions = [];
66
41
  let offset = start + 1;
@@ -99,6 +74,7 @@ const effectiveAttrs = (node) => ({
99
74
  });
100
75
  const TABLE_SCOPE = {
101
76
  keys: TABLE_SCOPED_ATTRS,
77
+ signatureOf: (node) => formattingProjection(tableAttrsToFormatting(effectiveAttrs(node))),
102
78
  formattingOf: (node) => tableAttrsToFormatting(effectiveAttrs(node)),
103
79
  rejectPatch: (previousFormatting) => tableRejectAttrPatch(previousFormatting),
104
80
  changeAttr: "tblPrChange",
@@ -106,6 +82,7 @@ const TABLE_SCOPE = {
106
82
  };
107
83
  const ROW_SCOPE = {
108
84
  keys: ROW_SCOPED_ATTRS,
85
+ signatureOf: (node) => scopedValues(node.attrs, ROW_SCOPED_ATTRS),
109
86
  formattingOf: (node) => tableRowAttrsToFormatting(node.attrs),
110
87
  rejectPatch: (previousFormatting) => tableRowRejectAttrPatch(previousFormatting),
111
88
  changeAttr: "trPrChange",
@@ -113,6 +90,7 @@ const ROW_SCOPE = {
113
90
  };
114
91
  const CELL_SCOPE = {
115
92
  keys: CELL_SCOPED_ATTRS,
93
+ signatureOf: (node) => scopedValues(node.attrs, CELL_SCOPED_ATTRS),
116
94
  formattingOf: (node) => tableCellAttrsToFormatting(cellAttrsOf(node)),
117
95
  rejectPatch: (previousFormatting, liveFormatting) => tableCellRejectAttrPatch(previousFormatting, liveFormatting),
118
96
  changeAttr: "tcPrChange",
@@ -120,6 +98,43 @@ const CELL_SCOPE = {
120
98
  };
121
99
  const scopedValues = (attrs, keys) => canonicalJson(scopedAttrs(attrs, keys));
122
100
  /**
101
+ * `sourceXml` and `gridSourceXml` record byte-exact captures; they are
102
+ * deliberately excluded from a tracked formatting decision. Every other
103
+ * parsed property is serializer-supported and belongs in the complete old
104
+ * property set Word stores in a property-change element.
105
+ */
106
+ const semanticFormatting = (formatting) => {
107
+ if (!formatting) return {};
108
+ const { sourceXml: _sourceXml, gridSourceXml: _gridSourceXml, ...properties } = formatting;
109
+ return properties;
110
+ };
111
+ const formattingProjection = (formatting) => canonicalJson(semanticFormatting(formatting));
112
+ const cellProjection = (cell) => canonicalJson({
113
+ colspan: cell.attrs["colspan"],
114
+ rowspan: cell.attrs["rowspan"],
115
+ ...scopedAttrs(cell.attrs, CELL_SCOPED_ATTRS)
116
+ });
117
+ const rowProjection = (row) => {
118
+ const cells = [];
119
+ row.forEach((cell) => {
120
+ cells.push(cellProjection(cell));
121
+ });
122
+ return `${canonicalJson(scopedAttrs(row.attrs, ROW_SCOPED_ATTRS))}|${cells.join("|")}`;
123
+ };
124
+ /**
125
+ * One line per table, in the document order tables are numbered in: the
126
+ * table's own properties, then each row's and each cell's. Nested tables get
127
+ * their own lines rather than being folded into their parent's, so a
128
+ * difference names the table it is in.
129
+ */
130
+ const projectTableGeometry = (tables) => tables.map(({ node }) => {
131
+ const rows = [];
132
+ node.forEach((row) => {
133
+ rows.push(rowProjection(row));
134
+ });
135
+ return `${formattingProjection(TABLE_SCOPE.formattingOf(node))}#${rows.join("#")}`;
136
+ });
137
+ /**
123
138
  * One node's properties, matched to the node it was paired with, or `null`
124
139
  * when the difference cannot be recorded.
125
140
  *
@@ -136,14 +151,15 @@ const scopedValues = (attrs, keys) => canonicalJson(scopedAttrs(attrs, keys));
136
151
  */
137
152
  const propertyChangeFor = (base, target, position, scope) => {
138
153
  const live = scopedValues(base.attrs, scope.keys);
139
- if (live === scopedValues(target.attrs, scope.keys)) return null;
140
154
  const previousFormatting = scope.formattingOf(base);
155
+ const targetFormatting = scope.formattingOf(target);
156
+ if (scope.signatureOf(base) === scope.signatureOf(target)) return null;
141
157
  if (scopedValues(scope.rejectPatch(previousFormatting, previousFormatting), scope.keys) !== live) return null;
142
158
  return {
143
159
  position,
144
160
  attrs: {
145
161
  ...scopedAttrs(target.attrs, scope.keys),
146
- [ORIGINAL_FORMATTING]: scope.formattingOf(target) ?? null
162
+ [ORIGINAL_FORMATTING]: targetFormatting ?? null
147
163
  },
148
164
  changeAttr: scope.changeAttr,
149
165
  changeType: scope.changeType,
@@ -3,6 +3,7 @@ import { Node, Schema } from "prosemirror-model";
3
3
  //#region src/ai-edits/table-template.d.ts
4
4
  /** The node an operation should place, by the id of the operation placing it. */
5
5
  type FolioTableTemplates = ReadonlyMap<string, Node>;
6
+ type HyperlinkPortability = "strict" | "rebind-external";
6
7
  /**
7
8
  * Whether a target table can cross into the base package without semantic
8
9
  * content being stripped from its template.
@@ -12,7 +13,7 @@ type FolioTableTemplates = ReadonlyMap<string, Node>;
12
13
  * bookmarks and unresolved revision metadata are not portable and therefore
13
14
  * make a replacement unsafe. Callers can retain their granular plan instead.
14
15
  */
15
- declare const tableTemplateCanCrossPackageLosslessly: (template: Node) => boolean;
16
+ declare const tableTemplateCanCrossPackageLosslessly: (template: Node, hyperlinkPortability?: HyperlinkPortability) => boolean;
16
17
  /**
17
18
  * A structural tracked change on a table row.
18
19
  *
@@ -1,5 +1,5 @@
1
1
  import { cloneTableCellsWithParagraphPropertyCaptures, decodeTableCellParagraphSourcePayload, recreateProseNodeWithDetachedParagraphPropertySource } from "../docx/paragraphPropertySource.js";
2
- import { expectTableCellAttrs } from "../prosemirror/attrs/index.js";
2
+ import { expectHyperlinkMarkAttrs, expectTableCellAttrs } from "../prosemirror/attrs/index.js";
3
3
  import { stripBlockIdentityAttrs } from "./block-identity.js";
4
4
  import { Fragment } from "prosemirror-model";
5
5
  //#region src/ai-edits/table-template.ts
@@ -50,6 +50,12 @@ const PACKAGE_BOUND_MARK_NAMES = /* @__PURE__ */ new Set([
50
50
  "deletion",
51
51
  "runPropertyChange"
52
52
  ]);
53
+ const canRebindExternalHyperlink = (mark) => {
54
+ if (mark.type.name !== "hyperlink") return false;
55
+ const { href, rId } = expectHyperlinkMarkAttrs(mark);
56
+ const hasRelationshipId = typeof rId === "string" && rId.length > 0;
57
+ return href.length > 0 && !href.startsWith("#") || href.length === 0 && !hasRelationshipId;
58
+ };
53
59
  /**
54
60
  * Paragraph attrs cleared on a copied paragraph: its identity, the bookmarks
55
61
  * and empty hyperlinks that name package-scoped ids, the section it ended, and
@@ -98,10 +104,10 @@ const losesAuthoredAttrs = (node) => clearedAttrsOf(node).some((name) => hasAuth
98
104
  * bookmarks and unresolved revision metadata are not portable and therefore
99
105
  * make a replacement unsafe. Callers can retain their granular plan instead.
100
106
  */
101
- const tableTemplateCanCrossPackageLosslessly = (template) => {
107
+ const tableTemplateCanCrossPackageLosslessly = (template, hyperlinkPortability = "strict") => {
102
108
  let portable = true;
103
109
  const inspect = (node) => {
104
- if (PACKAGE_BOUND_NODE_NAMES.has(node.type.name) || node.marks.some(({ type }) => PACKAGE_BOUND_MARK_NAMES.has(type.name)) || losesAuthoredAttrs(node)) {
110
+ if (PACKAGE_BOUND_NODE_NAMES.has(node.type.name) || node.marks.some((mark) => PACKAGE_BOUND_MARK_NAMES.has(mark.type.name) && !(hyperlinkPortability === "rebind-external" && canRebindExternalHyperlink(mark))) || losesAuthoredAttrs(node)) {
105
111
  portable = false;
106
112
  return false;
107
113
  }
@@ -1,5 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { FolioContentBlock, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentParagraphKind, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
2
+ import { FolioContentBlock, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentListReference, FolioContentParagraphIndentation, FolioContentParagraphKind, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
3
3
  //#region src/ai-edits/types.d.ts
4
4
  type FolioAIBlockKind = FolioContentParagraphKind;
5
5
  /** Boolean run properties supported by snapshots and range-formatting mutations. */
@@ -40,6 +40,9 @@ type FolioAIBlock = FolioContentBlock<FolioAIBlockKind> & {
40
40
  * an explicit zero or false value.
41
41
  */
42
42
  type FolioAIParagraphSpacing = FolioContentParagraphSpacing;
43
+ /** The complete modeled attribute set of one direct `w:pPr/w:ind` child. */
44
+ type FolioAIParagraphIndentation = FolioContentParagraphIndentation;
45
+ type FolioAIListReference = FolioContentListReference;
43
46
  /**
44
47
  * The paragraph properties an operation may set. A subset of `w:pPrChange`'s
45
48
  * scope: properties a comparison can see in a block projection and an agent
@@ -49,14 +52,18 @@ type FolioAIBlockParagraphProperties = {
49
52
  /** `w:pStyle`. `null` clears the style back to the default. */
50
53
  styleId?: string | null;
51
54
  /**
52
- * `w:numPr/w:ilvl`, zero-based. `null` removes `w:numPr` altogether: the
53
- * paragraph stops being a list item rather than moving to another level.
55
+ * `w:numPr/w:ilvl`, zero-based. `null` removes numbering unless `numbering`
56
+ * supplies a concrete instance; together they retain that instance without
57
+ * an authored level.
54
58
  */
55
59
  listLevel?: number | null;
60
+ numbering?: FolioAIListReference | null;
56
61
  /** Direct `w:jc`. `null` clears the override and restores style inheritance. */
57
62
  alignment?: document_d_exports.ParagraphAlignment | null;
58
63
  /** Direct `w:spacing` attributes. `null` removes the whole direct child. */
59
64
  spacing?: FolioAIParagraphSpacing | null;
65
+ /** Direct `w:ind` attributes. `null` removes the whole direct child. */
66
+ indentation?: FolioAIParagraphIndentation | null;
60
67
  };
61
68
  /**
62
69
  * Every paragraph of one story, blank ones included.
@@ -203,12 +210,13 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
203
210
  type: "insertAfterBlock" | "insertBeforeBlock";
204
211
  blockId: string;
205
212
  /**
206
- * The paragraph text to insert. A line break splits `text` into
207
- * consecutive paragraphs at the same anchor instead of becoming
208
- * literal newlines inside one paragraph: only the first paragraph
209
- * gets `styleId` / `alignment` / `inheritFormatting`, later ones use body
210
- * formatting. Blank lines are dropped. Reported as a
211
- * `splitMultilineText` normalization when it happens.
213
+ * The paragraph text to insert. With the default `lineBreakMode:
214
+ * "paragraph"`, a line break splits `text` into consecutive
215
+ * paragraphs at the same anchor: only the first paragraph gets
216
+ * `styleId` / `alignment` / `inheritFormatting`, later ones use
217
+ * body formatting. Blank lines are dropped and reported as a
218
+ * `splitMultilineText` normalization. `"inline"` retains the
219
+ * control inside one paragraph.
212
220
  *
213
221
  * `""` inserts a BLANK paragraph, and is a real edit: adding an empty
214
222
  * line is a change a reader sees, and a document that has one where
@@ -216,6 +224,11 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
216
224
  * with a tracked paragraph mark, so rejecting closes it away.
217
225
  */
218
226
  text: string;
227
+ /**
228
+ * `"paragraph"` (the default) splits newlines into consecutive blocks.
229
+ * `"inline"` retains tabs and hard breaks inside this inserted block.
230
+ */
231
+ lineBreakMode?: "paragraph" | "inline";
219
232
  inheritFormatting?: boolean;
220
233
  /**
221
234
  * Links this insertion to the deletion that carries the same
@@ -229,6 +242,13 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
229
242
  * new page. Use for explicit page-break inserts.
230
243
  */
231
244
  pageBreakBefore?: boolean;
245
+ /**
246
+ * Insert one standalone authored `<w:br w:type="page"/>` carrier.
247
+ * This differs from `pageBreakBefore`, which is a paragraph property.
248
+ */
249
+ hardPageBreak?: {
250
+ clear?: document_d_exports.BreakContent["clear"];
251
+ };
232
252
  /**
233
253
  * Override the paragraph `styleId` attr of the inserted
234
254
  * block (e.g. `ClauseHeading1`). When omitted the inserted
@@ -242,10 +262,11 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
242
262
  * anchor's `w:numId`. Without it the inserted paragraph takes the
243
263
  * anchor's level, which is the wrong one whenever the new item sits
244
264
  * beside a list item at a different depth. `null` gives it no
245
- * numbering at all an ordinary paragraph next to a list item, which
246
- * inheritance alone cannot say.
265
+ * numbering unless `numbering` supplies a concrete instance; together
266
+ * they retain that instance without an authored level.
247
267
  */
248
268
  listLevel?: number | null;
269
+ numbering?: FolioAIListReference | null;
249
270
  /**
250
271
  * Direct `w:jc` for the inserted block. `null` clears alignment copied
251
272
  * from the anchor and lets the inserted paragraph's style decide.
@@ -256,6 +277,8 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
256
277
  * copied from the anchor and lets the inserted paragraph's style decide.
257
278
  */
258
279
  spacing?: FolioAIParagraphSpacing | null;
280
+ /** Direct `w:ind` for the inserted block. `null` clears copied indentation. */
281
+ indentation?: FolioAIParagraphIndentation | null;
259
282
  comment?: FolioAIComment;
260
283
  } | {
261
284
  id: string;
@@ -545,9 +568,8 @@ type FolioAIEditSkippedOperation = {
545
568
  */
546
569
  type FolioAIEditNormalization =
547
570
  /**
548
- * A line-break in `insertAfterBlock` / `insertBeforeBlock`'s `text` cannot
549
- * become one paragraph with an embedded break (Word paragraphs are single
550
- * lines); the applier split it into one paragraph per non-blank line.
571
+ * A line-break in paragraph-mode `insertAfterBlock` /
572
+ * `insertBeforeBlock` text was split into one paragraph per non-blank line.
551
573
  */
552
574
  {
553
575
  id: string;
@@ -574,4 +596,4 @@ type FolioAIEditApplyResult = {
574
596
  normalizations?: FolioAIEditNormalization[];
575
597
  };
576
598
  //#endregion
577
- export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockParagraphProperties, FolioAIBlockPreviewRun, FolioAIBlockStructuralBoundary, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineBooleanProperty, FolioAIInlineFormatting, FolioAIInlineFormattingPatch, FolioAIParagraphSpacing, FolioAISignatureParty, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult };
599
+ export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockParagraphProperties, FolioAIBlockPreviewRun, FolioAIBlockStructuralBoundary, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineBooleanProperty, FolioAIInlineFormatting, FolioAIInlineFormattingPatch, FolioAIListReference, FolioAIParagraphIndentation, FolioAIParagraphSpacing, FolioAISignatureParty, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult };