@ansonlai/docx-redline-js 0.5.4 → 0.6.1

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 (55) hide show
  1. package/AGENTS.md +82 -697
  2. package/ARCHITECTURE.md +13 -1
  3. package/CHANGELOG.md +8 -0
  4. package/README.md +177 -45
  5. package/core/paragraph-targeting.js +14 -2
  6. package/dist/docx-redline-js.esm.js +184 -51
  7. package/dist/docx-redline-js.esm.js.map +3 -3
  8. package/dist/docx-redline-js.esm.min.js +77 -77
  9. package/dist/docx-redline-js.esm.min.js.map +4 -4
  10. package/docs/AGENT_FAST_START.md +59 -0
  11. package/docs/AGENT_KNOWLEDGE_BASE.md +878 -0
  12. package/docs/SKILL_AUTHORING.md +126 -0
  13. package/docs/TESTING.md +35 -1
  14. package/docs/schemas/document-operations.schema.json +5 -1
  15. package/docs/validation-reports/2026-09-12-agent-cli-discovery-baseline.md +56 -0
  16. package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +86 -0
  17. package/docs/validation-reports/2026-09-13-agent-cli-efficiency-rollout.md +86 -0
  18. package/engine/oxml-engine.js +80 -13
  19. package/engine/run-builders.js +5 -15
  20. package/index.d.ts +28 -3
  21. package/node/cli-help.js +209 -0
  22. package/node/cli.js +323 -65
  23. package/node/docx-document.js +120 -69
  24. package/node/index.d.ts +6 -2
  25. package/package.json +15 -3
  26. package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
  27. package/services/batch-operation-orchestrator.js +215 -120
  28. package/services/document-inspection.js +89 -11
  29. package/services/document-operation-applier.js +52 -34
  30. package/services/document-operation-contract.js +10 -6
  31. package/services/document-operation-mutations.js +51 -5
  32. package/services/document-operation-session.js +4 -0
  33. package/services/error-recovery.js +174 -0
  34. package/services/operation-batch-compiler.js +394 -0
  35. package/services/operation-preflight.js +91 -72
  36. package/services/standalone-operation-runner.d.ts +17 -1
  37. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
  38. package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -1399
  39. package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
  40. package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
  41. package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
  42. package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
  43. package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
  44. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
  45. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
  46. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
  47. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
  48. package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
  49. package/docs/test-comparison-dashboard.html +0 -4338
  50. package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
  51. package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
  52. package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
  53. package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
  54. package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
  55. package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
@@ -1,4 +1,4 @@
1
- // @ansonlai/docx-redline-js v0.5.4 — https://github.com/AnsonLai/docx-redline-js
1
+ // @ansonlai/docx-redline-js v0.6.1 — https://github.com/AnsonLai/docx-redline-js
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -4890,6 +4890,47 @@ function extractFormattingFromOoxml(xmlDoc) {
4890
4890
  return { existingFormatHints, textSpans, paragraphs };
4891
4891
  }
4892
4892
 
4893
+ // core/revision-cloning.js
4894
+ var REVISION_HISTORY_ELEMENTS = /* @__PURE__ */ new Set([
4895
+ "ins",
4896
+ "del",
4897
+ "moveFrom",
4898
+ "moveTo",
4899
+ "pPrChange",
4900
+ "rPrChange",
4901
+ "tblPrChange",
4902
+ "trPrChange",
4903
+ "tcPrChange",
4904
+ "sectPrChange"
4905
+ ]);
4906
+ function clonePropertiesWithoutRevisionHistory(root) {
4907
+ if (!root) return null;
4908
+ const clone = root.cloneNode(true);
4909
+ const candidates = [clone, ...Array.from(clone.getElementsByTagName?.("*") || [])];
4910
+ for (const node of candidates.reverse()) {
4911
+ if (!REVISION_HISTORY_ELEMENTS.has(String(node.localName || node.nodeName || "").replace(/^.*:/, ""))) continue;
4912
+ node.parentNode?.removeChild(node);
4913
+ }
4914
+ return clone;
4915
+ }
4916
+ function refreshRunPropertyChangeIds(root, allocator = null) {
4917
+ if (!root) return root;
4918
+ const xmlDoc = root.nodeType === 9 ? root : root.ownerDocument;
4919
+ const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : getRevisionIdAllocatorForDocument(xmlDoc) || createRevisionIdAllocator(xmlDoc);
4920
+ const candidates = [root, ...Array.from(root.getElementsByTagName?.("*") || [])];
4921
+ for (const node of candidates) {
4922
+ if (!isWordElement(node, "rPrChange")) continue;
4923
+ const nextId = String(resolvedAllocator.next());
4924
+ if (typeof node.setAttributeNS === "function") {
4925
+ node.setAttributeNS(NS_W, "w:id", nextId);
4926
+ } else {
4927
+ node.setAttribute("w:id", nextId);
4928
+ }
4929
+ resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), "rPrChange");
4930
+ }
4931
+ return root;
4932
+ }
4933
+
4893
4934
  // engine/run-builders.js
4894
4935
  function createTrackChange(xmlDoc, type, run, author, revisionMetadata = null) {
4895
4936
  const wrapper = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
@@ -5078,13 +5119,8 @@ function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
5078
5119
  rPrChange.setAttribute("w:id", String(metadata.id));
5079
5120
  rPrChange.setAttribute("w:author", metadata.author);
5080
5121
  rPrChange.setAttribute("w:date", dateStr || metadata.date);
5081
- const previousRPr = createWordElement(xmlDoc, "w:rPr");
5082
5122
  const source = sourceNode || rPr;
5083
- Array.from(source.childNodes).forEach((child) => {
5084
- if (child.nodeName !== "w:rPrChange") {
5085
- previousRPr.appendChild(child.cloneNode(true));
5086
- }
5087
- });
5123
+ const previousRPr = clonePropertiesWithoutRevisionHistory(source) || createWordElement(xmlDoc, "w:rPr");
5088
5124
  rPrChange.appendChild(previousRPr);
5089
5125
  const existing = getFirstElementByTag(rPr, "w:rPrChange");
5090
5126
  if (existing) {
@@ -5894,25 +5930,6 @@ function describeInsertionBoundary(spanIndex, pos, fallbackParagraph = null) {
5894
5930
  };
5895
5931
  }
5896
5932
 
5897
- // core/revision-cloning.js
5898
- function refreshRunPropertyChangeIds(root, allocator = null) {
5899
- if (!root) return root;
5900
- const xmlDoc = root.nodeType === 9 ? root : root.ownerDocument;
5901
- const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : getRevisionIdAllocatorForDocument(xmlDoc) || createRevisionIdAllocator(xmlDoc);
5902
- const candidates = [root, ...Array.from(root.getElementsByTagName?.("*") || [])];
5903
- for (const node of candidates) {
5904
- if (!isWordElement(node, "rPrChange")) continue;
5905
- const nextId = String(resolvedAllocator.next());
5906
- if (typeof node.setAttributeNS === "function") {
5907
- node.setAttributeNS(NS_W, "w:id", nextId);
5908
- } else {
5909
- node.setAttribute("w:id", nextId);
5910
- }
5911
- resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), "rPrChange");
5912
- }
5913
- return root;
5914
- }
5915
-
5916
5933
  // engine/surgical-run-splitting.js
5917
5934
  var TRACK_CHANGE_CARRIERS = /* @__PURE__ */ new Set(["ins", "del"]);
5918
5935
  function getRunContentPieces(runElement) {
@@ -8415,6 +8432,51 @@ function getCommentIdsInOoxml(node) {
8415
8432
  }
8416
8433
  return [...ids].sort((a, b) => Number(a) - Number(b) || a.localeCompare(b));
8417
8434
  }
8435
+ function directWordChild(node, localName2) {
8436
+ return Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.namespaceURI === NS_W && child.localName === localName2) || null;
8437
+ }
8438
+ function insertedParagraphMarkMetadata(paragraph, author) {
8439
+ const marker = directWordChild(directWordChild(directWordChild(paragraph, "pPr"), "rPr"), "ins");
8440
+ if (!marker) return null;
8441
+ const markerAuthor = marker.getAttribute("w:author") || marker.getAttributeNS(NS_W, "author") || "";
8442
+ if (markerAuthor.trim().toLowerCase() !== String(author || "").trim().toLowerCase()) return null;
8443
+ return {
8444
+ id: marker.getAttribute("w:id") || marker.getAttributeNS(NS_W, "id") || "",
8445
+ author: markerAuthor,
8446
+ date: marker.getAttribute("w:date") || marker.getAttributeNS(NS_W, "date") || ""
8447
+ };
8448
+ }
8449
+ function emptyParagraphBaseline(paragraph, serializer) {
8450
+ const clone = paragraph.cloneNode(false);
8451
+ const pPr = directWordChild(paragraph, "pPr");
8452
+ if (pPr) clone.appendChild(clonePropertiesWithoutRevisionHistory(pPr));
8453
+ return serializer.serializeToString(clone);
8454
+ }
8455
+ function restoreInsertedParagraphMark(oxml, metadata) {
8456
+ if (!metadata || typeof oxml !== "string" || !oxml.trim()) return oxml;
8457
+ const parsed = parseOoxmlSafe(oxml, "text/xml");
8458
+ if (!parsed.doc || parsed.error) return oxml;
8459
+ const paragraph = parsed.doc.documentElement?.localName === "p" ? parsed.doc.documentElement : getDocumentParagraphs(parsed.doc)[0];
8460
+ if (!paragraph) return oxml;
8461
+ let pPr = directWordChild(paragraph, "pPr");
8462
+ if (!pPr) {
8463
+ pPr = createWordElement(parsed.doc, "w:pPr");
8464
+ paragraph.insertBefore(pPr, paragraph.firstChild);
8465
+ }
8466
+ let rPr = directWordChild(pPr, "rPr");
8467
+ if (!rPr) {
8468
+ rPr = createWordElement(parsed.doc, "w:rPr");
8469
+ pPr.appendChild(rPr);
8470
+ }
8471
+ if (!directWordChild(rPr, "ins")) {
8472
+ const marker = createWordElement(parsed.doc, "w:ins");
8473
+ if (metadata.id) marker.setAttribute("w:id", metadata.id);
8474
+ marker.setAttribute("w:author", metadata.author);
8475
+ if (metadata.date) marker.setAttribute("w:date", metadata.date);
8476
+ rPr.appendChild(marker);
8477
+ }
8478
+ return serializeXml(parsed.doc);
8479
+ }
8418
8480
  async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
8419
8481
  const inputOoxml = oxml;
8420
8482
  let workingOoxml = oxml;
@@ -8426,6 +8488,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
8426
8488
  let parseWarnings = [];
8427
8489
  const operationWarnings = [];
8428
8490
  let normalizedExistingRevisions = false;
8491
+ let preservedInsertedParagraphMark = null;
8429
8492
  const existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
8430
8493
  const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
8431
8494
  const finalize = (result) => {
@@ -8456,6 +8519,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
8456
8519
  if (!withStatus.status) {
8457
8520
  withStatus.status = withStatus.hasChanges ? "ok" : "no-op";
8458
8521
  }
8522
+ if (preservedInsertedParagraphMark && withStatus.hasChanges && typeof withStatus.oxml === "string") {
8523
+ withStatus.oxml = restoreInsertedParagraphMark(withStatus.oxml, preservedInsertedParagraphMark);
8524
+ }
8459
8525
  return withOoxmlSourceType(withStatus);
8460
8526
  };
8461
8527
  const finalizeUnchanged = () => {
@@ -8521,9 +8587,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
8521
8587
  });
8522
8588
  }
8523
8589
  log("[OxmlEngine] Existing revisions from same author detected; rejecting previous changes to merge against baseline");
8590
+ const soleParagraph = inputParagraphs.length === 1 ? inputParagraphs[0] : null;
8591
+ preservedInsertedParagraphMark = insertedParagraphMarkMetadata(soleParagraph, author);
8524
8592
  const rejected = rejectTrackedChangesInOoxml(inputOoxml, { author });
8525
8593
  if (rejected.status === "error") return finalize(rejected);
8526
- workingOoxml = rejected.oxml;
8594
+ workingOoxml = preservedInsertedParagraphMark && !String(rejected.oxml || "").trim() ? emptyParagraphBaseline(soleParagraph, serializer) : rejected.oxml;
8527
8595
  normalizedExistingRevisions = true;
8528
8596
  const rejectedParsed = parseOoxmlSafe(workingOoxml, "text/xml");
8529
8597
  parseWarnings.push(...rejectedParsed.warnings);
@@ -8565,7 +8633,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
8565
8633
  status: "error",
8566
8634
  error: {
8567
8635
  code: "EXISTING_REVISIONS",
8568
- message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(", ") : "unattributed"}). Pass existingRevisions: "accept-all-first" or resolve revisions first.`
8636
+ message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(", ") : "unattributed"}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
8637
+ revisionAuthors: authors,
8638
+ currentPolicy: existingRevisionsPolicy
8569
8639
  }
8570
8640
  });
8571
8641
  } else {
@@ -9173,9 +9243,12 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
9173
9243
  );
9174
9244
  }
9175
9245
  if (descriptor.fingerprint && descriptor.fingerprint !== actualFingerprint) {
9246
+ const alternateView = revisionView === "rejected" ? "accepted" : "rejected";
9247
+ const alternateFingerprint = createParagraphFingerprint(byId, { revisionView: alternateView });
9248
+ const viewHint = descriptor.fingerprint === alternateFingerprint ? ` The supplied fingerprint matches the ${alternateView} view; set target.revisionView to "${alternateView}" or use a fingerprint extracted from the ${revisionView} view.` : "";
9176
9249
  throw createTargetError(
9177
9250
  "TARGET_FINGERPRINT_MISMATCH",
9178
- `Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.`,
9251
+ `Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.${viewHint}`,
9179
9252
  cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
9180
9253
  );
9181
9254
  }
@@ -9187,9 +9260,14 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
9187
9260
  );
9188
9261
  }
9189
9262
  if (cleanTargetText && actualText !== normalizeWhitespaceForTargeting(cleanTargetText)) {
9263
+ const alternateView = revisionView === "rejected" ? "accepted" : "rejected";
9264
+ const alternateText = normalizeWhitespaceForTargeting(
9265
+ extractCanonicalParagraphText(byId, { revisionView: alternateView })
9266
+ );
9267
+ const viewHint = alternateText === normalizeWhitespaceForTargeting(cleanTargetText) ? ` The supplied text matches the ${alternateView} view; set target.revisionView to "${alternateView}".` : "";
9190
9268
  throw createTargetError(
9191
9269
  "TARGET_TEXT_MISMATCH",
9192
- `Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.`,
9270
+ `Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.${viewHint}`,
9193
9271
  cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
9194
9272
  );
9195
9273
  }
@@ -11388,6 +11466,70 @@ function collectDocumentCommentAnchors(paragraphNodes, revisionView) {
11388
11466
  });
11389
11467
  return anchors;
11390
11468
  }
11469
+ function selectInspectionParagraphs(allParagraphs, options) {
11470
+ let matches = allParagraphs;
11471
+ if (options.revisedOnly) matches = matches.filter((item) => item.hasRevisions);
11472
+ if (options.inTable != null) matches = matches.filter((item) => item.inTable === !!options.inTable);
11473
+ if (options.skipEmpty) matches = matches.filter((item) => item.text.length > 0);
11474
+ if (options.search) {
11475
+ const needle = String(options.search).toLowerCase();
11476
+ matches = matches.filter((item) => item.text.toLowerCase().includes(needle));
11477
+ }
11478
+ if (Array.isArray(options.indexes)) {
11479
+ const indexes = new Set(options.indexes);
11480
+ matches = matches.filter((item) => indexes.has(item.index));
11481
+ }
11482
+ let rangeStart = 1;
11483
+ let rangeEnd = allParagraphs.length;
11484
+ if (options.range) {
11485
+ rangeStart = Number(options.range.start ?? options.range[0]);
11486
+ rangeEnd = Number(options.range.end ?? options.range[1]);
11487
+ matches = matches.filter((item) => item.index >= rangeStart && item.index <= rangeEnd);
11488
+ }
11489
+ const totalMatches = matches.length;
11490
+ const after = Number.isInteger(options.after) && options.after > 0 ? options.after : null;
11491
+ const remaining = after == null ? matches : matches.filter((item) => item.index > after);
11492
+ const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : null;
11493
+ const selectedMatches = limit == null ? remaining : remaining.slice(0, limit);
11494
+ const truncated = selectedMatches.length < remaining.length;
11495
+ const around = Number.isInteger(options.around) && options.around > 0 ? options.around : 0;
11496
+ const exposeSelection = !!options.search || around > 0 || limit != null || after != null;
11497
+ let paragraphs = selectedMatches;
11498
+ if (around > 0 && options.search) {
11499
+ const directIndexes = new Set(selectedMatches.map((item) => item.index));
11500
+ const contextFor = /* @__PURE__ */ new Map();
11501
+ for (const match of selectedMatches) {
11502
+ const start = Math.max(rangeStart, match.index - around);
11503
+ const end = Math.min(rangeEnd, match.index + around);
11504
+ for (let index = start; index <= end; index++) {
11505
+ if (directIndexes.has(index)) continue;
11506
+ const owners = contextFor.get(index) || [];
11507
+ owners.push(match.index);
11508
+ contextFor.set(index, owners);
11509
+ }
11510
+ }
11511
+ const returnedIndexes = /* @__PURE__ */ new Set([...directIndexes, ...contextFor.keys()]);
11512
+ paragraphs = allParagraphs.filter((item) => returnedIndexes.has(item.index)).map((item) => directIndexes.has(item.index) ? { ...item, selectionRole: "match" } : { ...item, selectionRole: "context", contextFor: contextFor.get(item.index) || [] });
11513
+ } else if (exposeSelection) {
11514
+ paragraphs = selectedMatches.map((item) => ({ ...item, selectionRole: "match" }));
11515
+ }
11516
+ return {
11517
+ paragraphs,
11518
+ ...exposeSelection ? {
11519
+ selection: {
11520
+ ...options.search ? { search: String(options.search), caseSensitive: false } : {},
11521
+ totalMatches,
11522
+ returnedMatches: selectedMatches.length,
11523
+ returnedParagraphs: paragraphs.length,
11524
+ truncated,
11525
+ nextAfter: truncated && selectedMatches.length > 0 ? selectedMatches[selectedMatches.length - 1].index : null,
11526
+ ...limit != null ? { limit } : {},
11527
+ ...after != null ? { after } : {},
11528
+ ...around > 0 ? { around } : {}
11529
+ }
11530
+ } : {}
11531
+ };
11532
+ }
11391
11533
  function inspectDocumentParts(parts, options = {}) {
11392
11534
  const documentPart = parseXml2(parts?.documentXml, "word/document.xml", true);
11393
11535
  if (documentPart.error) return { status: "error", error: documentPart.error, paragraphs: [], comments: [], warnings: [] };
@@ -11403,9 +11545,10 @@ function inspectDocumentParts(parts, options = {}) {
11403
11545
  const resolveNumbering = createNumberingResolver(numberingPart.doc);
11404
11546
  let nearestHeading = null;
11405
11547
  const paragraphNodes = getDocumentParagraphNodes(documentPart.doc);
11406
- const commentAnchors = collectDocumentCommentAnchors(paragraphNodes, options.revisionView || "accepted");
11548
+ const revisionView = options.revisionView === "rejected" ? "rejected" : "accepted";
11549
+ const commentAnchors = collectDocumentCommentAnchors(paragraphNodes, revisionView);
11407
11550
  let paragraphs = paragraphNodes.map((paragraph, zeroIndex) => {
11408
- const text = extractCanonicalParagraphText(paragraph, { revisionView: options.revisionView || "accepted" });
11551
+ const text = extractCanonicalParagraphText(paragraph, { revisionView });
11409
11552
  const level = headingLevel(paragraph);
11410
11553
  if (level) nearestHeading = { level, text };
11411
11554
  const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
@@ -11416,17 +11559,20 @@ function inspectDocumentParts(parts, options = {}) {
11416
11559
  const index = zeroIndex + 1;
11417
11560
  const provision = list?.label && list.format !== "bullet" ? list.label : null;
11418
11561
  const headingText = nearestHeading?.text || null;
11419
- const humanReference = [provision, headingText, text.slice(0, options.excerptLength || 120)].filter(Boolean).join(" \u2014 ");
11562
+ const excerpt = text.slice(0, options.excerptLength || 120);
11563
+ const humanReference = level ? text : provision ? [provision, headingText].filter(Boolean).join(" \u2014 ") : headingText ? [headingText, excerpt].filter(Boolean).join(" \u2014 ") : excerpt;
11420
11564
  const segments = extractParagraphRevisionSegments(paragraph);
11421
11565
  return {
11422
11566
  index,
11423
11567
  ref: `P${index}`,
11424
11568
  paragraphId: getParagraphId2(paragraph),
11425
- fingerprint: createParagraphFingerprint(paragraph),
11569
+ fingerprint: createParagraphFingerprint(paragraph, { text, index, revisionView }),
11570
+ revisionView,
11426
11571
  text,
11427
11572
  exactText: text,
11428
- excerpt: text.slice(0, options.excerptLength || 120),
11573
+ excerpt,
11429
11574
  humanReference,
11575
+ provision,
11430
11576
  inTable: hasAncestor(paragraph, "tc"),
11431
11577
  table: structure.table,
11432
11578
  styleId,
@@ -11447,22 +11593,8 @@ function inspectDocumentParts(parts, options = {}) {
11447
11593
  definition.anchoredText ?? (definition.anchoredText = commentAnchors.get(id) || paragraph.text);
11448
11594
  comments.set(id, definition);
11449
11595
  }
11450
- if (options.revisedOnly) paragraphs = paragraphs.filter((item) => item.hasRevisions);
11451
- if (options.inTable != null) paragraphs = paragraphs.filter((item) => item.inTable === !!options.inTable);
11452
- if (options.skipEmpty) paragraphs = paragraphs.filter((item) => item.text.length > 0);
11453
- if (options.search) {
11454
- const needle = String(options.search).toLowerCase();
11455
- paragraphs = paragraphs.filter((item) => item.text.toLowerCase().includes(needle));
11456
- }
11457
- if (Array.isArray(options.indexes)) {
11458
- const indexes = new Set(options.indexes);
11459
- paragraphs = paragraphs.filter((item) => indexes.has(item.index));
11460
- }
11461
- if (options.range) {
11462
- const start = Number(options.range.start ?? options.range[0]);
11463
- const end = Number(options.range.end ?? options.range[1]);
11464
- paragraphs = paragraphs.filter((item) => item.index >= start && item.index <= end);
11465
- }
11596
+ const selected = selectInspectionParagraphs(paragraphs, options);
11597
+ paragraphs = selected.paragraphs;
11466
11598
  const allRevisionAuthors = [...new Set(paragraphs.flatMap((item) => item.revisionAuthors))].sort();
11467
11599
  const coveredEntries = extractDocumentPartsEntries(parts);
11468
11600
  const coveredParts = coveredEntries.map((e) => e.name).sort();
@@ -11479,6 +11611,7 @@ function inspectDocumentParts(parts, options = {}) {
11479
11611
  revisionToken,
11480
11612
  coveredParts,
11481
11613
  paragraphs,
11614
+ ...selected.selection ? { selection: selected.selection } : {},
11482
11615
  comments: [...comments.values()],
11483
11616
  revisionAuthors: allRevisionAuthors,
11484
11617
  commentAuthors: [...new Set([...comments.values()].map((item) => item.author).filter(Boolean))].sort(),