@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.
- package/dist/ai-edits/apply.js +275 -88
- package/dist/ai-edits/clean-text.js +12 -1
- package/dist/ai-edits/headless.d.ts +110 -0
- package/dist/ai-edits/headless.js +569 -40
- package/dist/ai-edits/snapshot.d.ts +24 -1
- package/dist/ai-edits/snapshot.js +206 -9
- package/dist/ai-edits/table-geometry.d.ts +7 -7
- package/dist/ai-edits/table-geometry.js +43 -27
- package/dist/ai-edits/table-template.d.ts +2 -1
- package/dist/ai-edits/table-template.js +9 -3
- package/dist/ai-edits/types.d.ts +37 -15
- package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
- package/dist/compare/__fixtures__/body-sequence.js +5 -3
- package/dist/compare/compare.d.ts +26 -2
- package/dist/compare/compare.js +444 -16
- package/dist/compare/content-alignment.js +3 -2
- package/dist/compare/content-types.d.ts +34 -2
- package/dist/compare/content.d.ts +13 -10
- package/dist/compare/content.js +69 -23
- package/dist/compare/formatting.d.ts +4 -22
- package/dist/compare/formatting.js +8 -112
- package/dist/compare/inline-atom-resources.d.ts +6 -0
- package/dist/compare/inline-atom-resources.js +26 -0
- package/dist/compare/inline-atoms.d.ts +35 -0
- package/dist/compare/inline-atoms.js +383 -0
- package/dist/compare/inline-provenance.d.ts +36 -0
- package/dist/compare/inline-provenance.js +334 -0
- package/dist/compare/plan.d.ts +4 -1
- package/dist/compare/plan.js +32 -4
- package/dist/compare/scenario.d.ts +4 -6
- package/dist/compare/section-boundary-properties.d.ts +63 -0
- package/dist/compare/section-boundary-properties.js +235 -0
- package/dist/compare/style-resources.d.ts +30 -0
- package/dist/compare/style-resources.js +404 -0
- package/dist/compare/types.d.ts +50 -11
- package/dist/compare/types.js +3 -1
- package/dist/compare/verification.d.ts +3 -5
- package/dist/compare/verification.js +8 -39
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +3 -3
- package/dist/document-operations.d.ts +5 -3
- package/dist/document-operations.js +112 -5
- package/dist/docx/blockContentParser.js +14 -2
- package/dist/docx/drawingIdNormalization.js +43 -2
- package/dist/docx/drawingRelationships.d.ts +10 -0
- package/dist/docx/drawingRelationships.js +28 -0
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/hyperlinkParser.js +3 -5
- package/dist/docx/imageRawXml.d.ts +8 -0
- package/dist/docx/imageRawXml.js +13 -0
- package/dist/docx/newImage.d.ts +2 -2
- package/dist/docx/newImage.js +3 -3
- package/dist/docx/numberingParser.js +30 -27
- package/dist/docx/paragraphParser.js +11 -4
- package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
- package/dist/docx/removeHeaderFooterParts.js +87 -0
- package/dist/docx/rezip.d.ts +3 -1
- package/dist/docx/rezip.js +143 -41
- package/dist/docx/runParser.js +16 -5
- package/dist/docx/sectionParser.js +3 -0
- package/dist/docx/sectionReferenceHistory.d.ts +17 -0
- package/dist/docx/sectionReferenceHistory.js +59 -0
- package/dist/docx/selectiveSave.js +11 -31
- package/dist/docx/selectiveXmlPatch.d.ts +8 -1
- package/dist/docx/selectiveXmlPatch.js +62 -2
- package/dist/docx/serializer/paragraphSerializer.js +2 -0
- package/dist/docx/serializer/partNamespaces.d.ts +1 -1
- package/dist/docx/serializer/partNamespaces.js +4 -0
- package/dist/docx/serializer/runSerializer.js +2 -1
- package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
- package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
- package/dist/docx/serializer/textFormattingSerializer.js +3 -1
- package/dist/docx/tableParser.js +21 -11
- package/dist/docx/xmlParser.d.ts +10 -1
- package/dist/docx/xmlParser.js +23 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/internal/compare/inline-presentation.d.ts +20 -0
- package/dist/internal/compare/inline-presentation.js +222 -0
- package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
- package/dist/internal/paragraphFormattingSerialization.js +8 -1
- package/dist/internal/sectionReferenceResolution.d.ts +21 -0
- package/dist/internal/sectionReferenceResolution.js +72 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/markdown/renderRuns.js +1 -0
- package/dist/markdown/renderTable.js +1 -0
- package/dist/prosemirror/attrs/index.js +20 -1
- package/dist/prosemirror/commands/comments.d.ts +11 -1
- package/dist/prosemirror/commands/comments.js +68 -6
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
- package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
- package/dist/prosemirror/conversion/fromProseDoc.js +16 -8
- package/dist/prosemirror/conversion/toProseDoc.js +16 -57
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
- package/dist/prosemirror/imageCommit.js +7 -9
- package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
- package/dist/prosemirror/paragraphIndentation.js +72 -0
- package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
- package/dist/prosemirror/plugins/documentNumbering.js +7 -1
- package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
- package/dist/prosemirror/plugins/documentStyles.js +7 -1
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
- package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
- package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
- package/dist/prosemirror/runStyleFormatting.js +76 -2
- package/dist/prosemirror/schema/marks.d.ts +3 -1
- package/dist/prosemirror/schema/marks.js +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +3 -0
- package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
- package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/redline.js +15 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/version-comparison.d.ts +2 -0
- package/dist/version-comparison.js +7 -2
- package/package.json +2 -2
package/dist/compare/compare.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { FolioDocxReviewer, getFolioDocxComparisonAccess } from "../ai-edits/headless.js";
|
|
2
|
-
import { numberingReferenceKeysOf, storyTablesOf } from "../ai-edits/snapshot.js";
|
|
2
|
+
import { detachFolioAIEditSnapshotExternalHyperlinks, numberingReferenceKeysOf, remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences, sourceDocumentOf, storyTablesOf } from "../ai-edits/snapshot.js";
|
|
3
3
|
import { projectTableGeometry } from "../ai-edits/table-geometry.js";
|
|
4
4
|
import { tableTemplateCanCrossPackageLosslessly } from "../ai-edits/table-template.js";
|
|
5
5
|
import { createScopedWordDiffOptions } from "../ai-edits/word-diff.js";
|
|
6
6
|
import "../document-operations.js";
|
|
7
7
|
import { pairFolioDocumentStories } from "../document-stories.js";
|
|
8
|
+
import { sectionReferenceHistory } from "../docx/sectionReferenceHistory.js";
|
|
9
|
+
import { sameCanonicalInlinePresentation } from "../internal/compare/inline-presentation.js";
|
|
10
|
+
import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
|
|
11
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
8
12
|
import { createContentComparisonWorkSession } from "./content.js";
|
|
13
|
+
import { sameInlineAtoms } from "./inline-atoms.js";
|
|
14
|
+
import { sameAuthoredInlineProvenance } from "./inline-provenance.js";
|
|
9
15
|
import { planStoryCompare } from "./plan.js";
|
|
10
16
|
import { withFixedPackageDates } from "./reproducible-package.js";
|
|
17
|
+
import { compareSectionBoundaryProperties } from "./section-boundary-properties.js";
|
|
11
18
|
import { CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "./types.js";
|
|
12
|
-
import { classifyGeometryMismatch, classifyProjectionMismatch,
|
|
19
|
+
import { classifyGeometryMismatch, classifyProjectionMismatch, revisedFinalParagraphMarks } from "./verification.js";
|
|
13
20
|
import { Result, panic } from "better-result";
|
|
14
21
|
//#region src/compare/compare.ts
|
|
15
22
|
/**
|
|
@@ -78,7 +85,7 @@ const formattingRoundTripFailure = ({ invariant, story, changes, actualBlocks, e
|
|
|
78
85
|
story,
|
|
79
86
|
detail: "a text-equal aligned block could not be projected for formatting verification"
|
|
80
87
|
};
|
|
81
|
-
if (
|
|
88
|
+
if (!sameCanonicalInlinePresentation(actual, expected)) return {
|
|
82
89
|
invariant,
|
|
83
90
|
cause: "inline-formatting",
|
|
84
91
|
story,
|
|
@@ -113,8 +120,86 @@ const compareNumbering = (base, target, referenced) => {
|
|
|
113
120
|
});
|
|
114
121
|
return changes.toSorted((left, right) => left.numId - right.numId || left.level - right.level);
|
|
115
122
|
};
|
|
123
|
+
const withoutSectionChangeHistory = ({ propertyChanges: _propertyChanges, ...properties }) => properties;
|
|
124
|
+
const sectionRelationshipMap = (pairs) => {
|
|
125
|
+
const mapped = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const { baseStory, targetStory } of pairs) {
|
|
127
|
+
if (baseStory.type !== "header" && baseStory.type !== "footer" || targetStory.type !== "header" && targetStory.type !== "footer" || baseStory.type !== targetStory.type) continue;
|
|
128
|
+
mapped.set(targetStory.relationshipId, baseStory.relationshipId);
|
|
129
|
+
}
|
|
130
|
+
return mapped;
|
|
131
|
+
};
|
|
132
|
+
const remapSectionReferences = (references, relationshipIds) => {
|
|
133
|
+
if (references === void 0) return void 0;
|
|
134
|
+
const remapped = [];
|
|
135
|
+
for (const reference of references) {
|
|
136
|
+
const relationshipId = relationshipIds.get(reference.rId);
|
|
137
|
+
if (relationshipId === void 0) return null;
|
|
138
|
+
remapped.push({
|
|
139
|
+
...reference,
|
|
140
|
+
rId: relationshipId
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return remapped;
|
|
144
|
+
};
|
|
145
|
+
const safelyStageSectionProperties = ({ current, target, relationshipIds, revisionFormat }) => {
|
|
146
|
+
const targetHeaders = remapSectionReferences(target.headerReferences, relationshipIds);
|
|
147
|
+
const targetFooters = remapSectionReferences(target.footerReferences, relationshipIds);
|
|
148
|
+
if (targetHeaders === null || targetFooters === null || revisionFormat === "word" && (canonicalJson(targetHeaders) !== canonicalJson(current.headerReferences) || canonicalJson(targetFooters) !== canonicalJson(current.footerReferences)) || target.printerSettingsRelationshipId !== current.printerSettingsRelationshipId) return null;
|
|
149
|
+
const safeTarget = {
|
|
150
|
+
...withoutSectionChangeHistory(target),
|
|
151
|
+
...targetHeaders !== void 0 && { headerReferences: targetHeaders },
|
|
152
|
+
...targetFooters !== void 0 && { footerReferences: targetFooters },
|
|
153
|
+
...current.printerSettingsRelationshipId !== void 0 && { printerSettingsRelationshipId: current.printerSettingsRelationshipId }
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
previous: withoutSectionChangeHistory(current),
|
|
157
|
+
target: safeTarget
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const stageInsertedSectionProperties = ({ target, relationshipIds }) => {
|
|
161
|
+
if (target.printerSettingsRelationshipId !== void 0) return null;
|
|
162
|
+
const headers = remapSectionReferences(target.headerReferences, relationshipIds);
|
|
163
|
+
const footers = remapSectionReferences(target.footerReferences, relationshipIds);
|
|
164
|
+
if (headers === null || footers === null) return null;
|
|
165
|
+
return {
|
|
166
|
+
...withoutSectionChangeHistory(target),
|
|
167
|
+
...headers !== void 0 && { headerReferences: headers },
|
|
168
|
+
...footers !== void 0 && { footerReferences: footers }
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const compareFinalSectionProperties = ({ base, target, pairs, revisionFormat }) => {
|
|
172
|
+
if (base === void 0 || target === void 0) return base === target ? { status: "same" } : {
|
|
173
|
+
status: "unsupported",
|
|
174
|
+
detail: "final section presence differs"
|
|
175
|
+
};
|
|
176
|
+
if (base.propertyChanges !== void 0 || target.propertyChanges !== void 0) return {
|
|
177
|
+
status: "unsupported",
|
|
178
|
+
detail: "input final section already carries tracked changes"
|
|
179
|
+
};
|
|
180
|
+
const staged = safelyStageSectionProperties({
|
|
181
|
+
current: base,
|
|
182
|
+
target,
|
|
183
|
+
relationshipIds: sectionRelationshipMap(pairs),
|
|
184
|
+
revisionFormat
|
|
185
|
+
});
|
|
186
|
+
if (!staged) return {
|
|
187
|
+
status: "unsupported",
|
|
188
|
+
detail: "final section relationship references differ"
|
|
189
|
+
};
|
|
190
|
+
return canonicalJson(staged.previous) === canonicalJson(staged.target) ? { status: "same" } : {
|
|
191
|
+
status: "stage",
|
|
192
|
+
...staged
|
|
193
|
+
};
|
|
194
|
+
};
|
|
116
195
|
/** Stage 1: both packages to editor models, paired story by story. */
|
|
117
196
|
const parseComparison = async (base, target, options) => {
|
|
197
|
+
const revisionFormat = options.revisionFormat ?? "word";
|
|
198
|
+
if (revisionFormat !== "word" && revisionFormat !== "folio-exact") return Result.err(new InvalidCompareDocxOptionsError({
|
|
199
|
+
message: "revisionFormat must be word or folio-exact.",
|
|
200
|
+
option: "revisionFormat",
|
|
201
|
+
receivedValue: revisionFormat
|
|
202
|
+
}));
|
|
118
203
|
const packageDate = new Date(options.timestamp);
|
|
119
204
|
if (Number.isNaN(packageDate.getTime())) return Result.err(new InvalidCompareDocxOptionsError({
|
|
120
205
|
message: "timestamp must be a date the package can be stamped with.",
|
|
@@ -142,13 +227,34 @@ const parseComparison = async (base, target, options) => {
|
|
|
142
227
|
targetSnapshots.set(handle, snapshot);
|
|
143
228
|
}
|
|
144
229
|
const pairs = [];
|
|
230
|
+
const importedHeaderFooterTargetSnapshots = /* @__PURE__ */ new Set();
|
|
145
231
|
const unsupported = [];
|
|
146
232
|
const referencedNumberingLevels = /* @__PURE__ */ new Set();
|
|
233
|
+
let terminalTableCarrierStaged = false;
|
|
147
234
|
const collectNumberingReferences = (snapshot) => {
|
|
148
235
|
if (!snapshot) return;
|
|
149
236
|
for (const referenceKey of numberingReferenceKeysOf(snapshot)) referencedNumberingLevels.add(referenceKey);
|
|
150
237
|
};
|
|
151
|
-
for (const
|
|
238
|
+
for (const storyPair of pairFolioDocumentStories(baseStories, targetStories)) {
|
|
239
|
+
let { baseStory, revisedStory: targetStory } = storyPair;
|
|
240
|
+
let targetHeaderFooterWasImported = false;
|
|
241
|
+
let targetHeaderFooterNeedsExternalHyperlinkRebinding = false;
|
|
242
|
+
if (!baseStory && targetStory && (targetStory.type === "header" || targetStory.type === "footer")) {
|
|
243
|
+
baseStory = await getFolioDocxComparisonAccess(reviewer).createComparisonHeaderFooter(targetReviewer, targetStory);
|
|
244
|
+
if (baseStory) {
|
|
245
|
+
targetHeaderFooterWasImported = true;
|
|
246
|
+
targetHeaderFooterNeedsExternalHyperlinkRebinding = true;
|
|
247
|
+
baseSnapshots.set(baseStory, reviewer.snapshotStory(baseStory));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!targetStory && baseStory && revisionFormat === "folio-exact" && (baseStory.type === "header" || baseStory.type === "footer")) continue;
|
|
251
|
+
if (!targetStory && baseStory && (baseStory.type === "header" || baseStory.type === "footer")) {
|
|
252
|
+
targetStory = await getFolioDocxComparisonAccess(targetReviewer).createComparisonHeaderFooter(reviewer, baseStory);
|
|
253
|
+
if (targetStory) {
|
|
254
|
+
targetHeaderFooterNeedsExternalHyperlinkRebinding = true;
|
|
255
|
+
targetSnapshots.set(targetStory, targetReviewer.snapshotStory(targetStory));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
152
258
|
if (!baseStory) {
|
|
153
259
|
if (!targetStory) panic("A story pair contained neither a base nor a target story");
|
|
154
260
|
collectNumberingReferences(targetSnapshots.get(targetStory));
|
|
@@ -168,8 +274,8 @@ const parseComparison = async (base, target, options) => {
|
|
|
168
274
|
});
|
|
169
275
|
continue;
|
|
170
276
|
}
|
|
171
|
-
|
|
172
|
-
|
|
277
|
+
let baseSnapshot = baseSnapshots.get(baseStory);
|
|
278
|
+
let targetSnapshot = targetSnapshots.get(targetStory);
|
|
173
279
|
collectNumberingReferences(baseSnapshot);
|
|
174
280
|
collectNumberingReferences(targetSnapshot);
|
|
175
281
|
if (!baseSnapshot || !targetSnapshot) {
|
|
@@ -180,15 +286,70 @@ const parseComparison = async (base, target, options) => {
|
|
|
180
286
|
});
|
|
181
287
|
continue;
|
|
182
288
|
}
|
|
289
|
+
const rawBaseSnapshot = baseSnapshot;
|
|
290
|
+
if (revisionFormat === "folio-exact" && baseStory.type === "main" && getFolioDocxComparisonAccess(reviewer).stageTerminalTableReviewCarrier(sourceDocumentOf(targetSnapshot))) {
|
|
291
|
+
const staged = reviewer.snapshotStory(baseStory);
|
|
292
|
+
if (!staged) panic("A staged terminal-table carrier lost the main story snapshot");
|
|
293
|
+
baseSnapshot = staged;
|
|
294
|
+
baseSnapshots.set(baseStory, staged);
|
|
295
|
+
terminalTableCarrierStaged = true;
|
|
296
|
+
}
|
|
297
|
+
if (targetHeaderFooterNeedsExternalHyperlinkRebinding) {
|
|
298
|
+
const detached = detachFolioAIEditSnapshotExternalHyperlinks(targetSnapshot);
|
|
299
|
+
if (!detached) {
|
|
300
|
+
unsupported.push({
|
|
301
|
+
reason: "story-not-editable",
|
|
302
|
+
baseStory,
|
|
303
|
+
targetStory
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
targetSnapshot = detached;
|
|
308
|
+
targetSnapshots.set(targetStory, targetSnapshot);
|
|
309
|
+
}
|
|
310
|
+
if (targetHeaderFooterWasImported) importedHeaderFooterTargetSnapshots.add(targetSnapshot);
|
|
183
311
|
pairs.push({
|
|
184
312
|
baseStory,
|
|
185
313
|
targetStory,
|
|
186
314
|
baseSnapshot,
|
|
315
|
+
...terminalTableCarrierStaged && rawBaseSnapshot !== baseSnapshot && { rawBaseSnapshot },
|
|
187
316
|
targetSnapshot
|
|
188
317
|
});
|
|
189
318
|
}
|
|
319
|
+
const styleImport = getFolioDocxComparisonAccess(reviewer).stageTargetStyles(targetReviewer, pairs.map(({ targetSnapshot }) => targetSnapshot), [...importedHeaderFooterTargetSnapshots]);
|
|
320
|
+
const styleAlignedPairs = styleImport.status === "unalignable" ? pairs : pairs.map(({ baseStory, targetStory, baseSnapshot, rawBaseSnapshot, targetSnapshot }) => ({
|
|
321
|
+
baseStory,
|
|
322
|
+
targetStory,
|
|
323
|
+
baseSnapshot,
|
|
324
|
+
rawBaseSnapshot: rawBaseSnapshot ?? baseSnapshot,
|
|
325
|
+
targetSnapshot: remapFolioAIEditSnapshotStyleReferences({
|
|
326
|
+
snapshot: targetSnapshot,
|
|
327
|
+
styleIdMap: styleImport.styleIdMap,
|
|
328
|
+
defaultParagraphStyleId: styleImport.defaultParagraphStyleId,
|
|
329
|
+
importedStyleResolver: createStyleResolver(styleImport.styles),
|
|
330
|
+
reconcileAuthoredFormatting: true
|
|
331
|
+
})
|
|
332
|
+
}));
|
|
333
|
+
const targetNumbering = getFolioDocxComparisonAccess(targetReviewer).numberingDefinitions();
|
|
334
|
+
const targetNumberingReferences = styleAlignedPairs.flatMap(({ targetSnapshot }) => targetSnapshot.blocks.flatMap((block) => block.listReference ? [block.listReference] : []));
|
|
335
|
+
const targetNumberingReferenceMap = getFolioDocxComparisonAccess(reviewer).planTargetNumberingReferences(targetNumbering, targetNumberingReferences);
|
|
336
|
+
const comparisonPairs = targetNumberingReferenceMap === null ? styleAlignedPairs : styleAlignedPairs.map(({ baseStory, targetStory, baseSnapshot, rawBaseSnapshot, targetSnapshot }) => ({
|
|
337
|
+
baseStory,
|
|
338
|
+
targetStory,
|
|
339
|
+
baseSnapshot,
|
|
340
|
+
rawBaseSnapshot: rawBaseSnapshot ?? baseSnapshot,
|
|
341
|
+
targetSnapshot: remapFolioAIEditSnapshotNumberingReferences(targetSnapshot, targetNumberingReferenceMap)
|
|
342
|
+
}));
|
|
343
|
+
const finalSectionComparison = compareFinalSectionProperties({
|
|
344
|
+
base: getFolioDocxComparisonAccess(reviewer).finalSectionProperties(),
|
|
345
|
+
target: getFolioDocxComparisonAccess(targetReviewer).finalSectionProperties(),
|
|
346
|
+
pairs: comparisonPairs,
|
|
347
|
+
revisionFormat
|
|
348
|
+
});
|
|
190
349
|
return Result.ok({
|
|
191
350
|
granularity: options.granularity ?? "word",
|
|
351
|
+
revisionFormat,
|
|
352
|
+
terminalTableCarrierStaged,
|
|
192
353
|
baseBuffer: base,
|
|
193
354
|
baseCarriedRevisions: baseProjection.revisions.present,
|
|
194
355
|
reviewer,
|
|
@@ -197,8 +358,13 @@ const parseComparison = async (base, target, options) => {
|
|
|
197
358
|
idSeed: baseProjection.revisions.highestId + 1
|
|
198
359
|
},
|
|
199
360
|
packageDate,
|
|
200
|
-
pairs,
|
|
361
|
+
pairs: comparisonPairs,
|
|
201
362
|
numberingChanges: compareNumbering(reviewer, targetReviewer, referencedNumberingLevels),
|
|
363
|
+
targetNumbering,
|
|
364
|
+
targetNumberingReferences,
|
|
365
|
+
targetNumberingReferenceMap: targetNumberingReferenceMap ?? /* @__PURE__ */ new Map(),
|
|
366
|
+
finalSectionComparison,
|
|
367
|
+
styleImportFailure: styleImport.status === "unalignable" ? styleImport.detail : void 0,
|
|
202
368
|
unsupported
|
|
203
369
|
});
|
|
204
370
|
};
|
|
@@ -328,18 +494,58 @@ const resolveTableTemplates = (targetTables, requests) => {
|
|
|
328
494
|
* what you could not represent" and "give me nothing unless you can prove it"
|
|
329
495
|
* are both legitimate asks and only the caller knows which one it is making.
|
|
330
496
|
*/
|
|
331
|
-
const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChanges }, planned) => {
|
|
497
|
+
const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat, terminalTableCarrierStaged, numberingChanges, targetNumbering, targetNumberingReferences, targetNumberingReferenceMap, finalSectionComparison, styleImportFailure, pairs }, planned) => {
|
|
498
|
+
const relationshipIds = sectionRelationshipMap(pairs);
|
|
499
|
+
const plannedOperationCount = planned.reduce((count, { plan }) => count + plan.operations.length, 0);
|
|
500
|
+
const finalSectionOperationCount = finalSectionComparison.status === "stage" ? 1 : 0;
|
|
501
|
+
if (plannedOperationCount + finalSectionOperationCount > 1e4) return Result.err(new CompareDocxOperationLimitError({
|
|
502
|
+
message: "The comparison needs more operations than the engine generates.",
|
|
503
|
+
limit: MAX_COMPARE_OPERATIONS
|
|
504
|
+
}));
|
|
332
505
|
const comparisonAccess = getFolioDocxComparisonAccess(reviewer);
|
|
506
|
+
const numberingStage = comparisonAccess.stageTargetNumbering(targetNumbering, targetNumberingReferences, targetNumberingReferenceMap);
|
|
333
507
|
const changes = [...numberingChanges];
|
|
334
508
|
const failures = [];
|
|
335
|
-
|
|
336
|
-
|
|
509
|
+
if (styleImportFailure !== void 0) failures.push({
|
|
510
|
+
invariant: "accept-reproduces-target",
|
|
511
|
+
cause: "style",
|
|
512
|
+
story: { type: "main" },
|
|
513
|
+
detail: styleImportFailure
|
|
514
|
+
});
|
|
515
|
+
const finalSectionChanged = finalSectionComparison.status === "stage" && comparisonAccess.stageFinalSectionProperties({
|
|
516
|
+
target: finalSectionComparison.target,
|
|
517
|
+
previous: finalSectionComparison.previous,
|
|
518
|
+
revision: revisionStamp
|
|
519
|
+
});
|
|
520
|
+
if (finalSectionChanged) changes.push({
|
|
521
|
+
kind: "section-properties",
|
|
522
|
+
location: { story: { type: "main" } }
|
|
523
|
+
});
|
|
524
|
+
if (finalSectionComparison.status === "unsupported") failures.push({
|
|
525
|
+
invariant: "accept-reproduces-target",
|
|
526
|
+
cause: "section-properties",
|
|
527
|
+
story: { type: "main" },
|
|
528
|
+
detail: finalSectionComparison.detail
|
|
529
|
+
});
|
|
530
|
+
let idSeed = revisionStamp.idSeed + (finalSectionChanged ? 1 : 0);
|
|
531
|
+
if (numberingStage === "conflict") failures.push({
|
|
532
|
+
invariant: "accept-reproduces-target",
|
|
533
|
+
cause: "list-level",
|
|
534
|
+
story: { type: "main" },
|
|
535
|
+
detail: "target numbering definitions cannot be imported without changing existing references"
|
|
536
|
+
});
|
|
537
|
+
const folioRequirements = /* @__PURE__ */ new Set();
|
|
538
|
+
if (finalSectionChanged && finalSectionComparison.status === "stage" && sectionReferenceHistory(finalSectionComparison) !== void 0) folioRequirements.add("section-reference-history");
|
|
539
|
+
if (terminalTableCarrierStaged) folioRequirements.add("terminal-table-carrier");
|
|
540
|
+
let documentChanged = numberingStage === "staged" || finalSectionChanged;
|
|
541
|
+
let remainingProvenanceRanges = MAX_COMPARE_OPERATIONS - plannedOperationCount - finalSectionOperationCount;
|
|
337
542
|
const wordDiff = createScopedWordDiffOptions({ granularity });
|
|
338
543
|
for (const { pair, plan } of planned) {
|
|
339
544
|
changes.push(...plan.changes);
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
const
|
|
545
|
+
failures.push(...plan.verificationFailures);
|
|
546
|
+
const rawBaseSnapshot = pair.rawBaseSnapshot ?? pair.baseSnapshot;
|
|
547
|
+
const baseBefore = rawBaseSnapshot.blocks;
|
|
548
|
+
const baseBeforeGeometry = projectTableGeometry(storyTablesOf(rawBaseSnapshot));
|
|
343
549
|
const targetTables = new Map(storyTablesOf(pair.targetSnapshot).map(({ index, node }) => [index, node]));
|
|
344
550
|
const afterGeometry = reviewer.matchStoryTableGeometry({
|
|
345
551
|
story: pair.baseStory,
|
|
@@ -353,7 +559,6 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
353
559
|
const geometryChanged = afterGeometry > idSeed;
|
|
354
560
|
documentChanged ||= geometryChanged;
|
|
355
561
|
idSeed = afterGeometry;
|
|
356
|
-
if (plan.operations.length === 0 && !geometryChanged) continue;
|
|
357
562
|
if (plan.operations.length > 0) {
|
|
358
563
|
const { skipped, nextRevisionId } = reviewer.applyDocumentOperationsToStory({
|
|
359
564
|
story: pair.baseStory,
|
|
@@ -379,6 +584,171 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
379
584
|
skipped: refused
|
|
380
585
|
}));
|
|
381
586
|
}
|
|
587
|
+
const describedTargetIds = /* @__PURE__ */ new Set();
|
|
588
|
+
for (const change of plan.changes) switch (change.kind) {
|
|
589
|
+
case "insert":
|
|
590
|
+
case "replace":
|
|
591
|
+
case "move":
|
|
592
|
+
case "merge":
|
|
593
|
+
case "paragraph-format":
|
|
594
|
+
case "format":
|
|
595
|
+
case "run-format":
|
|
596
|
+
case "inline-atom":
|
|
597
|
+
describedTargetIds.add(change.targetBlockId);
|
|
598
|
+
break;
|
|
599
|
+
case "split":
|
|
600
|
+
case "table-insert":
|
|
601
|
+
case "table-row-insert":
|
|
602
|
+
case "table-column-insert":
|
|
603
|
+
for (const id of change.targetBlockIds) describedTargetIds.add(id);
|
|
604
|
+
break;
|
|
605
|
+
case "delete":
|
|
606
|
+
case "section-properties":
|
|
607
|
+
case "table-delete":
|
|
608
|
+
case "table-row-delete":
|
|
609
|
+
case "table-column-delete":
|
|
610
|
+
case "numbering": break;
|
|
611
|
+
default: return panic("Unhandled comparison change", { change });
|
|
612
|
+
}
|
|
613
|
+
const atoms = comparisonAccess.matchInlineAtoms({
|
|
614
|
+
story: pair.baseStory,
|
|
615
|
+
targetSnapshot: pair.targetSnapshot,
|
|
616
|
+
revisionStamp: {
|
|
617
|
+
date: revisionStamp.date,
|
|
618
|
+
idSeed
|
|
619
|
+
},
|
|
620
|
+
originalRevisionIdSeed: revisionStamp.idSeed,
|
|
621
|
+
maxRanges: remainingProvenanceRanges
|
|
622
|
+
});
|
|
623
|
+
switch (atoms.status) {
|
|
624
|
+
case "matched": {
|
|
625
|
+
idSeed = atoms.nextRevisionId;
|
|
626
|
+
remainingProvenanceRanges -= atoms.rangeCount;
|
|
627
|
+
documentChanged ||= atoms.documentChanged;
|
|
628
|
+
const changedAtomTargetIds = new Set(atoms.changedTargetBlockIds);
|
|
629
|
+
for (const block of pair.targetSnapshot.blocks) {
|
|
630
|
+
if (!changedAtomTargetIds.has(block.id) || describedTargetIds.has(block.id)) continue;
|
|
631
|
+
changes.push({
|
|
632
|
+
kind: "inline-atom",
|
|
633
|
+
location: {
|
|
634
|
+
story: pair.baseStory,
|
|
635
|
+
...block.table ? { cell: block.table } : {}
|
|
636
|
+
},
|
|
637
|
+
targetBlockId: block.id,
|
|
638
|
+
text: block.text
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
case "unalignable":
|
|
644
|
+
failures.push({
|
|
645
|
+
invariant: "accept-reproduces-target",
|
|
646
|
+
cause: "inline-structure",
|
|
647
|
+
story: pair.baseStory,
|
|
648
|
+
detail: "supported inline atoms could not be aligned to the revised content"
|
|
649
|
+
});
|
|
650
|
+
break;
|
|
651
|
+
case "budget-exceeded": return Result.err(new CompareDocxOperationLimitError({
|
|
652
|
+
message: "The comparison exceeds its inline presentation range budget.",
|
|
653
|
+
limit: MAX_COMPARE_OPERATIONS
|
|
654
|
+
}));
|
|
655
|
+
default: return panic("Unhandled inline atom projection result", { result: atoms });
|
|
656
|
+
}
|
|
657
|
+
const provenance = comparisonAccess.matchInlineProvenance({
|
|
658
|
+
story: pair.baseStory,
|
|
659
|
+
targetSnapshot: pair.targetSnapshot,
|
|
660
|
+
revisionStamp: {
|
|
661
|
+
date: revisionStamp.date,
|
|
662
|
+
idSeed
|
|
663
|
+
},
|
|
664
|
+
originalRevisionIdSeed: revisionStamp.idSeed,
|
|
665
|
+
maxRanges: remainingProvenanceRanges
|
|
666
|
+
});
|
|
667
|
+
switch (provenance.status) {
|
|
668
|
+
case "matched": {
|
|
669
|
+
idSeed = provenance.nextRevisionId;
|
|
670
|
+
remainingProvenanceRanges -= provenance.rangeCount;
|
|
671
|
+
documentChanged ||= provenance.documentChanged;
|
|
672
|
+
const changedTargetIds = new Set(provenance.changedTargetBlockIds);
|
|
673
|
+
for (const block of pair.targetSnapshot.blocks) {
|
|
674
|
+
if (!changedTargetIds.has(block.id) || describedTargetIds.has(block.id)) continue;
|
|
675
|
+
changes.push({
|
|
676
|
+
kind: "run-format",
|
|
677
|
+
location: {
|
|
678
|
+
story: pair.baseStory,
|
|
679
|
+
...block.table ? { cell: block.table } : {}
|
|
680
|
+
},
|
|
681
|
+
targetBlockId: block.id,
|
|
682
|
+
text: block.text
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
case "unalignable":
|
|
688
|
+
failures.push({
|
|
689
|
+
invariant: "accept-reproduces-target",
|
|
690
|
+
cause: "inline-formatting",
|
|
691
|
+
story: pair.baseStory,
|
|
692
|
+
detail: "authored run properties could not be aligned to the revised content"
|
|
693
|
+
});
|
|
694
|
+
break;
|
|
695
|
+
case "budget-exceeded": return Result.err(new CompareDocxOperationLimitError({
|
|
696
|
+
message: "The comparison exceeds its inline presentation range budget.",
|
|
697
|
+
limit: MAX_COMPARE_OPERATIONS
|
|
698
|
+
}));
|
|
699
|
+
default: return panic("Unhandled authored inline projection result", { result: provenance });
|
|
700
|
+
}
|
|
701
|
+
if (pair.baseStory.type === "main") {
|
|
702
|
+
let boundaryRequiresFolio = false;
|
|
703
|
+
const insertedBoundaries = comparisonAccess.stageMappedSectionBoundaries({
|
|
704
|
+
target: sourceDocumentOf(pair.targetSnapshot),
|
|
705
|
+
originalRevisionIdSeed: revisionStamp.idSeed,
|
|
706
|
+
maxRanges: remainingProvenanceRanges,
|
|
707
|
+
revision: {
|
|
708
|
+
date: revisionStamp.date,
|
|
709
|
+
idSeed
|
|
710
|
+
},
|
|
711
|
+
mapTargetProperties: ({ kind, current, target }) => {
|
|
712
|
+
if (kind === "inserted") {
|
|
713
|
+
const inserted = stageInsertedSectionProperties({
|
|
714
|
+
target,
|
|
715
|
+
relationshipIds
|
|
716
|
+
});
|
|
717
|
+
return inserted ? {
|
|
718
|
+
kind: "inserted",
|
|
719
|
+
target: inserted
|
|
720
|
+
} : null;
|
|
721
|
+
}
|
|
722
|
+
if (current === void 0) return null;
|
|
723
|
+
const staged = safelyStageSectionProperties({
|
|
724
|
+
current,
|
|
725
|
+
target,
|
|
726
|
+
relationshipIds,
|
|
727
|
+
revisionFormat
|
|
728
|
+
});
|
|
729
|
+
if (staged && sectionReferenceHistory(staged) !== void 0) boundaryRequiresFolio = true;
|
|
730
|
+
return staged ? {
|
|
731
|
+
kind: "retained",
|
|
732
|
+
...staged
|
|
733
|
+
} : null;
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
switch (insertedBoundaries.status) {
|
|
737
|
+
case "matched":
|
|
738
|
+
remainingProvenanceRanges -= insertedBoundaries.rangeCount;
|
|
739
|
+
idSeed = insertedBoundaries.nextRevisionId;
|
|
740
|
+
documentChanged ||= insertedBoundaries.documentChanged;
|
|
741
|
+
if (boundaryRequiresFolio) folioRequirements.add("section-reference-history");
|
|
742
|
+
break;
|
|
743
|
+
case "unalignable": break;
|
|
744
|
+
case "budget-exceeded": return Result.err(new CompareDocxOperationLimitError({
|
|
745
|
+
message: "The comparison exceeds its section boundary range budget.",
|
|
746
|
+
limit: MAX_COMPARE_OPERATIONS
|
|
747
|
+
}));
|
|
748
|
+
default: return panic("Unhandled inserted section boundary result", { result: insertedBoundaries });
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (plan.operations.length === 0 && !geometryChanged && atoms.status === "matched" && atoms.rangeCount === 0 && provenance.status === "matched" && provenance.rangeCount === 0 && pair.baseStory.type !== "main") continue;
|
|
382
752
|
const acceptedSnapshot = comparisonAccess.snapshotReviewedStory({
|
|
383
753
|
story: pair.baseStory,
|
|
384
754
|
view: "final"
|
|
@@ -401,6 +771,34 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
401
771
|
});
|
|
402
772
|
if (formattingFailure) failures.push(formattingFailure);
|
|
403
773
|
}
|
|
774
|
+
if (acceptedSnapshot && !sameAuthoredInlineProvenance(acceptedSnapshot, pair.targetSnapshot)) failures.push({
|
|
775
|
+
invariant: "accept-reproduces-target",
|
|
776
|
+
cause: "inline-formatting",
|
|
777
|
+
story: pair.baseStory,
|
|
778
|
+
detail: "accepted authored run properties differ from the revised document"
|
|
779
|
+
});
|
|
780
|
+
if (acceptedSnapshot && !sameInlineAtoms(sourceDocumentOf(acceptedSnapshot), sourceDocumentOf(pair.targetSnapshot))) failures.push({
|
|
781
|
+
invariant: "accept-reproduces-target",
|
|
782
|
+
cause: "inline-structure",
|
|
783
|
+
story: pair.baseStory,
|
|
784
|
+
detail: "accepted inline atoms differ from the revised document"
|
|
785
|
+
});
|
|
786
|
+
if (pair.baseStory.type === "main" && acceptedSnapshot) {
|
|
787
|
+
const sectionBoundaries = compareSectionBoundaryProperties({
|
|
788
|
+
current: sourceDocumentOf(acceptedSnapshot),
|
|
789
|
+
target: sourceDocumentOf(pair.targetSnapshot),
|
|
790
|
+
mapTargetProperties: (target) => stageInsertedSectionProperties({
|
|
791
|
+
target,
|
|
792
|
+
relationshipIds
|
|
793
|
+
})
|
|
794
|
+
});
|
|
795
|
+
if (sectionBoundaries.status === "unalignable" || sectionBoundaries.changes.length > 0) failures.push({
|
|
796
|
+
invariant: "accept-reproduces-target",
|
|
797
|
+
cause: "section-properties",
|
|
798
|
+
story: pair.baseStory,
|
|
799
|
+
detail: sectionBoundaries.status === "unalignable" ? sectionBoundaries.detail : "accepted paragraph section properties differ from the revised document"
|
|
800
|
+
});
|
|
801
|
+
}
|
|
404
802
|
const rejectedSnapshot = comparisonAccess.snapshotReviewedStory({
|
|
405
803
|
story: pair.baseStory,
|
|
406
804
|
view: "original"
|
|
@@ -418,11 +816,35 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
418
816
|
story: pair.baseStory,
|
|
419
817
|
changes: plan.changes,
|
|
420
818
|
actualBlocks: rejectedSnapshot?.blocks ?? [],
|
|
421
|
-
expectedBlocks:
|
|
819
|
+
expectedBlocks: rawBaseSnapshot.blocks,
|
|
422
820
|
expectedBlockId: ({ baseBlockId }) => baseBlockId
|
|
423
821
|
});
|
|
424
822
|
if (formattingFailure) failures.push(formattingFailure);
|
|
425
823
|
}
|
|
824
|
+
if (rejectedSnapshot && !sameAuthoredInlineProvenance(rejectedSnapshot, rawBaseSnapshot)) failures.push({
|
|
825
|
+
invariant: "reject-reproduces-base",
|
|
826
|
+
cause: "inline-formatting",
|
|
827
|
+
story: pair.baseStory,
|
|
828
|
+
detail: "rejected authored run properties differ from the base document"
|
|
829
|
+
});
|
|
830
|
+
if (rejectedSnapshot && !sameInlineAtoms(sourceDocumentOf(rejectedSnapshot), sourceDocumentOf(rawBaseSnapshot))) failures.push({
|
|
831
|
+
invariant: "reject-reproduces-base",
|
|
832
|
+
cause: "inline-structure",
|
|
833
|
+
story: pair.baseStory,
|
|
834
|
+
detail: "rejected inline atoms differ from the base document"
|
|
835
|
+
});
|
|
836
|
+
if (pair.baseStory.type === "main" && rejectedSnapshot) {
|
|
837
|
+
const sectionBoundaries = compareSectionBoundaryProperties({
|
|
838
|
+
current: sourceDocumentOf(rejectedSnapshot),
|
|
839
|
+
target: sourceDocumentOf(rawBaseSnapshot)
|
|
840
|
+
});
|
|
841
|
+
if (sectionBoundaries.status === "unalignable" || sectionBoundaries.changes.length > 0) failures.push({
|
|
842
|
+
invariant: "reject-reproduces-base",
|
|
843
|
+
cause: "section-properties",
|
|
844
|
+
story: pair.baseStory,
|
|
845
|
+
detail: sectionBoundaries.status === "unalignable" ? sectionBoundaries.detail : "rejected paragraph section properties differ from the base document"
|
|
846
|
+
});
|
|
847
|
+
}
|
|
426
848
|
const geometryAcceptFailure = classifyGeometryMismatch({
|
|
427
849
|
invariant: "accept-reproduces-target",
|
|
428
850
|
story: pair.baseStory,
|
|
@@ -438,13 +860,18 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
438
860
|
});
|
|
439
861
|
if (geometryRejectFailure) failures.push(geometryRejectFailure);
|
|
440
862
|
}
|
|
863
|
+
const firstFolioRequirement = [...folioRequirements].at(0);
|
|
441
864
|
return Result.ok({
|
|
442
865
|
changes,
|
|
443
866
|
verification: failures.length === 0 ? { status: "verified" } : {
|
|
444
867
|
status: "unverified",
|
|
445
868
|
failures
|
|
446
869
|
},
|
|
447
|
-
documentChanged
|
|
870
|
+
documentChanged,
|
|
871
|
+
compatibility: firstFolioRequirement === void 0 ? { status: "standard-ooxml" } : {
|
|
872
|
+
status: "requires-folio",
|
|
873
|
+
reasons: [firstFolioRequirement, ...[...folioRequirements].slice(1)]
|
|
874
|
+
}
|
|
448
875
|
});
|
|
449
876
|
};
|
|
450
877
|
/**
|
|
@@ -519,6 +946,7 @@ const compareDocx = async (base, target, options) => {
|
|
|
519
946
|
buffer: serialized.value,
|
|
520
947
|
changes,
|
|
521
948
|
verification,
|
|
949
|
+
compatibility: applied.value.compatibility,
|
|
522
950
|
unsupported: parsed.value.unsupported
|
|
523
951
|
});
|
|
524
952
|
};
|
|
@@ -710,7 +710,7 @@ const ALIGNMENT_DIRECTION = {
|
|
|
710
710
|
baseOnly: 2,
|
|
711
711
|
revisedOnly: 3
|
|
712
712
|
};
|
|
713
|
-
const alignProfiledContentSequence = ({ base, revised, workSession, canPair = () => true, pairSoleStructuralSlot = false, primaryEvidence = "stable", similarityFactor = () => 1 }) => {
|
|
713
|
+
const alignProfiledContentSequence = ({ base, revised, workSession, canPair = () => true, pairSoleStructuralSlot = false, soleShiftedPairResiduePolicy = "conservative", primaryEvidence = "stable", similarityFactor = () => 1 }) => {
|
|
714
714
|
if (base.length === 0 || revised.length === 0) return unpairedContentSequence(base.map(({ item }) => item), revised.map(({ item }) => item));
|
|
715
715
|
if ((base.length !== 1 || revised.length !== 1) && !claimFolioContentAlignmentCells(base.length, revised.length, workSession)) return unpairedContentSequence(base.map(({ item }) => item), revised.map(({ item }) => item));
|
|
716
716
|
const stablePairs = stableContentSequencePairs(base, revised);
|
|
@@ -882,7 +882,7 @@ const alignProfiledContentSequence = ({ base, revised, workSession, canPair = ()
|
|
|
882
882
|
});
|
|
883
883
|
hasRevisedOnly = true;
|
|
884
884
|
}
|
|
885
|
-
if (pairCount === 1 && solePairIsShifted && hasBaseOnly && hasRevisedOnly) return aligned.flatMap((entry) => entry.type === "pair" ? [{
|
|
885
|
+
if ((soleShiftedPairResiduePolicy === "conservative" || base.length === revised.length) && pairCount === 1 && solePairIsShifted && hasBaseOnly && hasRevisedOnly) return aligned.flatMap((entry) => entry.type === "pair" ? [{
|
|
886
886
|
type: "baseOnly",
|
|
887
887
|
item: entry.base
|
|
888
888
|
}, {
|
|
@@ -954,6 +954,7 @@ const pairTableRows = ({ baseRows, revisedRows, workSession, idStability }) => {
|
|
|
954
954
|
revised: revisedRows.map(profile),
|
|
955
955
|
workSession,
|
|
956
956
|
pairSoleStructuralSlot: true,
|
|
957
|
+
soleShiftedPairResiduePolicy: "row-count-evidence",
|
|
957
958
|
primaryEvidence: "exact",
|
|
958
959
|
similarityFactor: (base, revised) => base.profile.physicalCellCount === revised.profile.physicalCellCount ? 1 : .5
|
|
959
960
|
}).map((alignment) => {
|