@stll/folio-core 0.37.5 → 0.39.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 (144) 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/controller/layoutPipeline.js +4 -0
  42. package/dist/display-list/build/buildContext.d.ts +3 -1
  43. package/dist/display-list/build/buildDisplayList.d.ts +4 -1
  44. package/dist/display-list/build/buildDisplayList.js +38 -4
  45. package/dist/display-list/build/commentAnnotations.d.ts +10 -0
  46. package/dist/display-list/build/commentAnnotations.js +17 -0
  47. package/dist/display-list/build/paragraphPrimitives.js +39 -1
  48. package/dist/display-list/primitives.d.ts +1 -1
  49. package/dist/display-list/types.d.ts +16 -1
  50. package/dist/document-operations.d.ts +5 -3
  51. package/dist/document-operations.js +112 -5
  52. package/dist/docx/blockContentParser.js +14 -2
  53. package/dist/docx/diagramPreview.d.ts +7 -0
  54. package/dist/docx/diagramPreview.js +191 -0
  55. package/dist/docx/drawingIdNormalization.js +43 -2
  56. package/dist/docx/drawingRelationships.d.ts +10 -0
  57. package/dist/docx/drawingRelationships.js +28 -0
  58. package/dist/docx/fieldParser.js +1 -1
  59. package/dist/docx/hyperlinkParser.js +3 -5
  60. package/dist/docx/imageRawXml.d.ts +8 -0
  61. package/dist/docx/imageRawXml.js +13 -0
  62. package/dist/docx/newImage.d.ts +2 -2
  63. package/dist/docx/newImage.js +3 -3
  64. package/dist/docx/numberingParser.js +30 -27
  65. package/dist/docx/paragraphParser.js +11 -4
  66. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  67. package/dist/docx/parser.js +13 -0
  68. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  69. package/dist/docx/removeHeaderFooterParts.js +87 -0
  70. package/dist/docx/rezip.d.ts +3 -1
  71. package/dist/docx/rezip.js +143 -41
  72. package/dist/docx/runParser.js +24 -5
  73. package/dist/docx/sectionParser.js +3 -0
  74. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  75. package/dist/docx/sectionReferenceHistory.js +59 -0
  76. package/dist/docx/selectiveSave.js +11 -31
  77. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  78. package/dist/docx/selectiveXmlPatch.js +62 -2
  79. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  80. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  81. package/dist/docx/serializer/partNamespaces.js +4 -0
  82. package/dist/docx/serializer/runSerializer.js +5 -2
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  84. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  85. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  86. package/dist/docx/tableParser.js +21 -11
  87. package/dist/docx/textBoxParser.js +25 -5
  88. package/dist/docx/xmlParser.d.ts +10 -1
  89. package/dist/docx/xmlParser.js +23 -1
  90. package/dist/export-pdf.js +2 -0
  91. package/dist/headless-layout.js +1 -0
  92. package/dist/index.d.ts +4 -4
  93. package/dist/index.js +3 -3
  94. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  95. package/dist/internal/compare/inline-presentation.js +222 -0
  96. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  97. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  98. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  99. package/dist/internal/sectionReferenceResolution.js +72 -0
  100. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  101. package/dist/layout-engine/index.js +5 -5
  102. package/dist/layout-engine/sectionVerticalAlignment.d.ts +7 -0
  103. package/dist/layout-engine/sectionVerticalAlignment.js +52 -0
  104. package/dist/layout-engine/types.d.ts +3 -1
  105. package/dist/markdown/renderRuns.js +1 -0
  106. package/dist/markdown/renderTable.js +1 -0
  107. package/dist/pdf/writePdf.js +37 -2
  108. package/dist/prosemirror/attrs/index.js +55 -1
  109. package/dist/prosemirror/commands/comments.d.ts +11 -1
  110. package/dist/prosemirror/commands/comments.js +68 -6
  111. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  112. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  113. package/dist/prosemirror/conversion/fromProseDoc.js +17 -8
  114. package/dist/prosemirror/conversion/toProseDoc.js +18 -57
  115. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  116. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  117. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  118. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +39 -1
  119. package/dist/prosemirror/imageCommit.js +7 -9
  120. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  121. package/dist/prosemirror/paragraphIndentation.js +72 -0
  122. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  123. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  124. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  125. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  126. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  127. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  128. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  129. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  130. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  131. package/dist/prosemirror/runStyleFormatting.js +76 -2
  132. package/dist/prosemirror/schema/marks.d.ts +3 -1
  133. package/dist/prosemirror/schema/marks.js +2 -0
  134. package/dist/prosemirror/schema/nodes.d.ts +5 -0
  135. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  136. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  137. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  138. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  139. package/dist/redline.js +15 -2
  140. package/dist/server.d.ts +2 -2
  141. package/dist/server.js +2 -2
  142. package/dist/version-comparison.d.ts +2 -0
  143. package/dist/version-comparison.js +7 -2
  144. package/package.json +2 -2
@@ -0,0 +1,383 @@
1
+ import { buildCleanBlockText } from "../ai-edits/clean-text.js";
2
+ import { sourceDocumentOf } from "../ai-edits/snapshot.js";
3
+ import { resolveAllChangesInHeadlessStateWithMapping } from "../prosemirror/commands/comments.js";
4
+ import { canonicalJson } from "../utils/canonicalJson.js";
5
+ import { prepareTargetInlineAtom } from "./inline-atom-resources.js";
6
+ //#region src/compare/inline-atoms.ts
7
+ const MAX_INLINE_ATOM_LCS_CELLS = 16384;
8
+ const isSupportedAtom = (node) => node.isInline && node.isAtom && (node.type.name === "field" || node.type.name === "image" || node.type.name === "pageBreakRun");
9
+ const hasSupportedAtom = (doc) => {
10
+ let found = false;
11
+ doc.descendants((node) => {
12
+ if (isSupportedAtom(node)) {
13
+ found = true;
14
+ return false;
15
+ }
16
+ return !found;
17
+ });
18
+ return found;
19
+ };
20
+ const textBlocksOf = (doc) => {
21
+ const blocks = [];
22
+ doc.descendants((node, from) => {
23
+ if (!node.isTextblock) return true;
24
+ blocks.push({
25
+ node,
26
+ from
27
+ });
28
+ return false;
29
+ });
30
+ return blocks;
31
+ };
32
+ const offsetAt = ({ offsets, position }) => {
33
+ let low = 0;
34
+ let high = offsets.length;
35
+ while (low < high) {
36
+ const middle = Math.floor((low + high) / 2);
37
+ const candidate = offsets[middle];
38
+ if (candidate === void 0 || candidate < position) low = middle + 1;
39
+ else high = middle;
40
+ }
41
+ return Math.min(low, offsets.length - 1);
42
+ };
43
+ const atomKey = (node) => canonicalJson({
44
+ type: node.type.name,
45
+ attrs: node.attrs,
46
+ content: node.content.toJSON()
47
+ });
48
+ const atomBlockOf = ({ node, from }) => {
49
+ const clean = buildCleanBlockText(node, from);
50
+ const supported = [];
51
+ const unsupportedTopology = [];
52
+ let unalignable = false;
53
+ node.descendants((child, relativePosition) => {
54
+ if (child.isText) return false;
55
+ if (!child.isInline || !child.isAtom) return true;
56
+ const position = from + 1 + relativePosition;
57
+ const offset = offsetAt({
58
+ offsets: clean.offsets,
59
+ position
60
+ });
61
+ if (offset === null) {
62
+ unalignable = true;
63
+ return false;
64
+ }
65
+ if (!isSupportedAtom(child)) {
66
+ if (child.type.name === "renderedPageBreak") return false;
67
+ unsupportedTopology.push(`${offset}:${child.type.name}:${canonicalJson(child.attrs)}`);
68
+ return false;
69
+ }
70
+ const prepared = prepareTargetInlineAtom(child);
71
+ if (!prepared) {
72
+ unalignable = true;
73
+ return false;
74
+ }
75
+ supported.push({
76
+ node: prepared,
77
+ from: position,
78
+ offset,
79
+ key: atomKey(prepared)
80
+ });
81
+ return false;
82
+ });
83
+ return unalignable ? null : {
84
+ node,
85
+ from,
86
+ cleanText: clean.text,
87
+ offsets: clean.offsets,
88
+ supported,
89
+ unsupportedTopology
90
+ };
91
+ };
92
+ const targetBlockIdLookup = (snapshot) => {
93
+ const anchors = Object.values(snapshot.anchors).toSorted((left, right) => left.from - right.from || right.to - left.to);
94
+ return (position) => {
95
+ let low = 0;
96
+ let high = anchors.length;
97
+ while (low < high) {
98
+ const middle = Math.floor((low + high) / 2);
99
+ if ((anchors[middle]?.from ?? Number.POSITIVE_INFINITY) <= position) low = middle + 1;
100
+ else high = middle;
101
+ }
102
+ let owner;
103
+ for (let index = low - 1; index >= 0; index--) {
104
+ const anchor = anchors[index];
105
+ if (!anchor || anchor.to < position) continue;
106
+ if (!owner || anchor.to - anchor.from < owner.to - owner.from) owner = anchor;
107
+ }
108
+ return owner?.id;
109
+ };
110
+ };
111
+ const atomBlocksOf = (doc) => {
112
+ const blocks = [];
113
+ for (const block of textBlocksOf(doc)) {
114
+ const atoms = atomBlockOf(block);
115
+ if (!atoms) return null;
116
+ blocks.push(atoms);
117
+ }
118
+ return blocks;
119
+ };
120
+ const sameBlockTopology = (left, right) => left.node.type === right.node.type && left.cleanText === right.cleanText && canonicalJson(left.unsupportedTopology) === canonicalJson(right.unsupportedTopology);
121
+ const matchingAtomGroup = ({ live, target, liveStart, targetStart }) => {
122
+ if (live.length === target.length && live.every((atom, index) => atom.key === target[index]?.key)) return live.map((_, index) => [liveStart + index, targetStart + index]);
123
+ if (live.length === 0 || target.length === 0) return [];
124
+ if (live.length <= Math.floor(MAX_INLINE_ATOM_LCS_CELLS / target.length)) {
125
+ const width = target.length + 1;
126
+ const table = new Uint16Array((live.length + 1) * width);
127
+ for (let liveIndex = live.length - 1; liveIndex >= 0; liveIndex--) for (let targetIndex = target.length - 1; targetIndex >= 0; targetIndex--) {
128
+ const index = liveIndex * width + targetIndex;
129
+ if (live[liveIndex]?.key === target[targetIndex]?.key) {
130
+ table[index] = (table[(liveIndex + 1) * width + targetIndex + 1] ?? 0) + 1;
131
+ continue;
132
+ }
133
+ table[index] = Math.max(table[(liveIndex + 1) * width + targetIndex] ?? 0, table[liveIndex * width + targetIndex + 1] ?? 0);
134
+ }
135
+ const matches = [];
136
+ let liveIndex = 0;
137
+ let targetIndex = 0;
138
+ while (liveIndex < live.length && targetIndex < target.length) {
139
+ if (live[liveIndex]?.key === target[targetIndex]?.key) {
140
+ matches.push([liveStart + liveIndex, targetStart + targetIndex]);
141
+ liveIndex++;
142
+ targetIndex++;
143
+ continue;
144
+ }
145
+ if ((table[(liveIndex + 1) * width + targetIndex] ?? 0) >= (table[liveIndex * width + targetIndex + 1] ?? 0)) liveIndex++;
146
+ else targetIndex++;
147
+ }
148
+ return matches;
149
+ }
150
+ const targetIndexes = /* @__PURE__ */ new Map();
151
+ for (const [index, atom] of target.entries()) targetIndexes.set(atom.key, targetIndexes.has(atom.key) ? null : index);
152
+ const liveKeyCounts = /* @__PURE__ */ new Map();
153
+ for (const atom of live) liveKeyCounts.set(atom.key, (liveKeyCounts.get(atom.key) ?? 0) + 1);
154
+ const matches = [];
155
+ let previousTargetIndex = -1;
156
+ for (const [liveIndex, atom] of live.entries()) {
157
+ const targetIndex = targetIndexes.get(atom.key);
158
+ if (targetIndex === void 0 || targetIndex === null || targetIndex <= previousTargetIndex || liveKeyCounts.get(atom.key) !== 1) continue;
159
+ matches.push([liveStart + liveIndex, targetStart + targetIndex]);
160
+ previousTargetIndex = targetIndex;
161
+ }
162
+ return matches;
163
+ };
164
+ const matchingAtoms = ({ live, target }) => {
165
+ const matched = [];
166
+ let left = 0;
167
+ let right = 0;
168
+ while (left < live.length || right < target.length) {
169
+ const liveOffset = live[left]?.offset;
170
+ const targetOffset = target[right]?.offset;
171
+ const offset = Math.min(liveOffset ?? Number.POSITIVE_INFINITY, targetOffset ?? Number.POSITIVE_INFINITY);
172
+ if (!Number.isFinite(offset)) break;
173
+ const liveStart = left;
174
+ const targetStart = right;
175
+ while (live[left]?.offset === offset) left++;
176
+ while (target[right]?.offset === offset) right++;
177
+ const liveGroup = live.slice(liveStart, left);
178
+ const targetGroup = target.slice(targetStart, right);
179
+ matched.push(...matchingAtomGroup({
180
+ live: liveGroup,
181
+ target: targetGroup,
182
+ liveStart,
183
+ targetStart
184
+ }));
185
+ }
186
+ return matched;
187
+ };
188
+ const insertionAnchors = ({ live, target, matches }) => {
189
+ const liveIndexByTargetIndex = /* @__PURE__ */ new Map();
190
+ for (const [liveIndex, targetIndex] of matches) liveIndexByTargetIndex.set(targetIndex, liveIndex);
191
+ const anchors = /* @__PURE__ */ new Map();
192
+ let groupStart = 0;
193
+ while (groupStart < target.length) {
194
+ const offset = target[groupStart]?.offset;
195
+ let groupEnd = groupStart + 1;
196
+ while (target[groupEnd]?.offset === offset) groupEnd++;
197
+ let nextMatchedLiveIndex;
198
+ for (let index = groupEnd - 1; index >= groupStart; index--) {
199
+ const matchedLiveIndex = liveIndexByTargetIndex.get(index);
200
+ if (matchedLiveIndex !== void 0) nextMatchedLiveIndex = matchedLiveIndex;
201
+ else if (nextMatchedLiveIndex !== void 0) anchors.set(index, live[nextMatchedLiveIndex].from);
202
+ }
203
+ let previousMatchedLiveIndex;
204
+ for (let index = groupStart; index < groupEnd; index++) {
205
+ const matchedLiveIndex = liveIndexByTargetIndex.get(index);
206
+ if (matchedLiveIndex !== void 0) {
207
+ previousMatchedLiveIndex = matchedLiveIndex;
208
+ continue;
209
+ }
210
+ if (!anchors.has(index) && previousMatchedLiveIndex !== void 0) {
211
+ const previous = live[previousMatchedLiveIndex];
212
+ if (previous) anchors.set(index, previous.from + previous.node.nodeSize);
213
+ }
214
+ }
215
+ groupStart = groupEnd;
216
+ }
217
+ return anchors;
218
+ };
219
+ const mappedSourcePosition = ({ mapping, position }) => {
220
+ const inverse = mapping.invert();
221
+ const right = inverse.mapResult(position, 1);
222
+ if (!right.deleted) return right.pos;
223
+ const left = inverse.mapResult(position, -1);
224
+ return left.deleted ? null : left.pos;
225
+ };
226
+ const sourceTextBlockIds = (source) => {
227
+ const blocks = /* @__PURE__ */ new Map();
228
+ for (const block of textBlocksOf(source)) {
229
+ const paraId = block.node.attrs["paraId"];
230
+ if (typeof paraId !== "string" || paraId.length === 0) continue;
231
+ blocks.set(paraId, blocks.has(paraId) ? null : block);
232
+ }
233
+ return blocks;
234
+ };
235
+ const sameParagraphSourcePosition = ({ sourceBlocks, reviewed, offset }) => {
236
+ const paraId = reviewed.node.attrs["paraId"];
237
+ if (typeof paraId !== "string" || paraId.length === 0) return null;
238
+ const source = sourceBlocks.get(paraId);
239
+ if (!source || source.node.type !== reviewed.node.type) return null;
240
+ const clean = buildCleanBlockText(source.node, source.from);
241
+ return clean.text === reviewed.cleanText ? clean.offsets[offset] ?? null : null;
242
+ };
243
+ /**
244
+ * Restore field, image, and page-break atoms omitted by text-only comparison operations.
245
+ * Positions come from the legacy review resolver's mapping, never a guessed
246
+ * paragraph correspondence: paragraph-mark deletions can merge paragraphs.
247
+ */
248
+ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges }) => {
249
+ if (!Number.isSafeInteger(maxRanges) || maxRanges < 0) return { status: "budget-exceeded" };
250
+ const targetDocument = sourceDocumentOf(targetSnapshot);
251
+ if (!hasSupportedAtom(state.doc) && !hasSupportedAtom(targetDocument)) return {
252
+ status: "matched",
253
+ transaction: state.tr,
254
+ nextRevisionId: revisionStamp.idSeed,
255
+ changedTargetBlockIds: [],
256
+ rangeCount: 0
257
+ };
258
+ const reviewed = resolveAllChangesInHeadlessStateWithMapping(state, "accept");
259
+ const targetBlockIdAt = targetBlockIdLookup(targetSnapshot);
260
+ let sourceBlocks;
261
+ const liveBlocks = atomBlocksOf(reviewed.state.doc);
262
+ const targetBlocks = atomBlocksOf(targetDocument);
263
+ if (!liveBlocks || !targetBlocks || liveBlocks.length !== targetBlocks.length) return { status: "unalignable" };
264
+ const actions = [];
265
+ for (const [index, live] of liveBlocks.entries()) {
266
+ const target = targetBlocks[index];
267
+ if (!target) return { status: "unalignable" };
268
+ if (live.supported.length === 0 && target.supported.length === 0) continue;
269
+ if (!sameBlockTopology(live, target)) return { status: "unalignable" };
270
+ const matches = matchingAtoms({
271
+ live: live.supported,
272
+ target: target.supported
273
+ });
274
+ const matchedLive = new Set(matches.map(([liveIndex]) => liveIndex));
275
+ const matchedTarget = new Set(matches.map(([, targetIndex]) => targetIndex));
276
+ const anchors = insertionAnchors({
277
+ live: live.supported,
278
+ target: target.supported,
279
+ matches
280
+ });
281
+ for (const [liveIndex, atom] of live.supported.entries()) {
282
+ if (matchedLive.has(liveIndex)) continue;
283
+ const from = mappedSourcePosition({
284
+ mapping: reviewed.mapping,
285
+ position: atom.from
286
+ });
287
+ if (from === null) return { status: "unalignable" };
288
+ actions.push({
289
+ kind: "delete",
290
+ from,
291
+ to: from + atom.node.nodeSize,
292
+ targetBlockId: targetBlockIdAt(target.from)
293
+ });
294
+ }
295
+ for (const [targetIndex, atom] of target.supported.entries()) {
296
+ if (matchedTarget.has(targetIndex)) continue;
297
+ const position = anchors.get(targetIndex) ?? live.offsets[atom.offset];
298
+ if (position === void 0) return { status: "unalignable" };
299
+ const from = mappedSourcePosition({
300
+ mapping: reviewed.mapping,
301
+ position
302
+ }) ?? sameParagraphSourcePosition({
303
+ sourceBlocks: sourceBlocks ??= sourceTextBlockIds(state.doc),
304
+ reviewed: live,
305
+ offset: atom.offset
306
+ });
307
+ if (from === null) return { status: "unalignable" };
308
+ actions.push({
309
+ kind: "insert",
310
+ from,
311
+ node: atom.node,
312
+ targetBlockId: targetBlockIdAt(target.from)
313
+ });
314
+ }
315
+ }
316
+ if (actions.length > maxRanges) return { status: "budget-exceeded" };
317
+ const insertionType = state.schema.marks["insertion"];
318
+ const deletionType = state.schema.marks["deletion"];
319
+ if (!insertionType || !deletionType) return { status: "unalignable" };
320
+ const transaction = state.tr;
321
+ let nextRevisionId = revisionStamp.idSeed;
322
+ const changedTargetBlockIds = /* @__PURE__ */ new Set();
323
+ const ordered = actions.toSorted((left, right) => right.from - left.from || (left.kind === "delete" ? -1 : 1));
324
+ for (const action of ordered) {
325
+ if (action.kind === "insert") {
326
+ const at = transaction.mapping.map(action.from, 1);
327
+ const marked = action.node.mark(insertionType.create({
328
+ revisionId: nextRevisionId++,
329
+ author,
330
+ date: revisionStamp.date
331
+ }).addToSet(action.node.marks));
332
+ transaction.insert(at, marked);
333
+ if (action.targetBlockId) changedTargetBlockIds.add(action.targetBlockId);
334
+ continue;
335
+ }
336
+ const from = transaction.mapping.map(action.from, 1);
337
+ const to = transaction.mapping.map(action.to, -1);
338
+ const node = transaction.doc.nodeAt(from);
339
+ if (!node || node.nodeSize !== to - from || !isSupportedAtom(node)) return { status: "unalignable" };
340
+ const insertion = node.marks.find(({ type }) => type === insertionType);
341
+ if (insertion) {
342
+ const revisionId = insertion.attrs["revisionId"];
343
+ if (typeof revisionId !== "number" || revisionId < originalRevisionIdSeed) return { status: "unalignable" };
344
+ transaction.delete(from, to);
345
+ if (action.targetBlockId) changedTargetBlockIds.add(action.targetBlockId);
346
+ continue;
347
+ }
348
+ if (node.marks.some(({ type }) => type === deletionType)) return { status: "unalignable" };
349
+ transaction.addMark(from, to, deletionType.create({
350
+ revisionId: nextRevisionId++,
351
+ author,
352
+ date: revisionStamp.date
353
+ }));
354
+ if (action.targetBlockId) changedTargetBlockIds.add(action.targetBlockId);
355
+ }
356
+ return {
357
+ status: "matched",
358
+ transaction,
359
+ nextRevisionId,
360
+ changedTargetBlockIds: [...changedTargetBlockIds],
361
+ rangeCount: actions.length
362
+ };
363
+ };
364
+ /** Compare supported inline atom identity after accept or reject projection. */
365
+ const sameInlineAtoms = (leftDocument, rightDocument) => {
366
+ if (!hasSupportedAtom(leftDocument) && !hasSupportedAtom(rightDocument)) return true;
367
+ const leftBlocks = atomBlocksOf(leftDocument);
368
+ const rightBlocks = atomBlocksOf(rightDocument);
369
+ if (!leftBlocks || !rightBlocks || leftBlocks.length !== rightBlocks.length) return false;
370
+ return leftBlocks.every((left, index) => {
371
+ const right = rightBlocks[index];
372
+ if (left.supported.length === 0 && right?.supported.length === 0) return true;
373
+ return right !== void 0 && sameBlockTopology(left, right) && canonicalJson(left.supported.map(({ offset, key }) => ({
374
+ offset,
375
+ key
376
+ }))) === canonicalJson(right.supported.map(({ offset, key }) => ({
377
+ offset,
378
+ key
379
+ })));
380
+ });
381
+ };
382
+ //#endregion
383
+ export { matchInlineAtoms, sameInlineAtoms };
@@ -0,0 +1,36 @@
1
+ import { FolioAIEditSnapshot } from "../ai-edits/types.js";
2
+ import { FolioRevisionStamp } from "../ai-edits/apply.js";
3
+ import { EditorState, Transaction } from "prosemirror-state";
4
+ //#region src/compare/inline-provenance.d.ts
5
+ type InlineProvenanceTargetOptions = {
6
+ targetSnapshot: FolioAIEditSnapshot;
7
+ revisionStamp: FolioRevisionStamp;
8
+ /** First id reserved for this comparison, before other planned operations allocated ids. */
9
+ originalRevisionIdSeed: number;
10
+ maxRanges: number;
11
+ };
12
+ type MatchInlineProvenanceOptions = InlineProvenanceTargetOptions & {
13
+ state: EditorState;
14
+ author: string;
15
+ };
16
+ type MatchInlineProvenanceResult = {
17
+ status: "matched";
18
+ transaction: Transaction;
19
+ nextRevisionId: number;
20
+ changedTargetBlockIds: readonly string[];
21
+ rangeCount: number;
22
+ } | {
23
+ status: "unalignable";
24
+ } | {
25
+ status: "budget-exceeded";
26
+ };
27
+ /**
28
+ * Reconcile direct inline run formatting after the comparison plan has aligned
29
+ * text. It deliberately performs all stream, revision-slot, and range-budget
30
+ * checks before creating a transaction step.
31
+ */
32
+ declare const matchInlineProvenance: ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges }: MatchInlineProvenanceOptions) => MatchInlineProvenanceResult;
33
+ /** Compare full authored run-property provenance in two snapshot source documents. */
34
+ declare const sameAuthoredInlineProvenance: (baseSnapshot: FolioAIEditSnapshot, targetSnapshot: FolioAIEditSnapshot) => boolean;
35
+ //#endregion
36
+ export { InlineProvenanceTargetOptions, MatchInlineProvenanceOptions, MatchInlineProvenanceResult, matchInlineProvenance, sameAuthoredInlineProvenance };