@superdoc/mcp 1.2.1-next.1 → 1.2.1-next.2

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 (2) hide show
  1. package/dist/index.js +504 -98
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -31314,7 +31314,7 @@ var init_host = __esm(() => {
31314
31314
  });
31315
31315
 
31316
31316
  // ../../packages/sdk/langs/node/src/runtime/sdk-version.generated.ts
31317
- var SDK_VERSION = "2.15.1-next.1";
31317
+ var SDK_VERSION = "2.15.1-next.2";
31318
31318
 
31319
31319
  // ../../packages/sdk/langs/node/src/runtime/embedded-platform.ts
31320
31320
  import { chmodSync, existsSync } from "node:fs";
@@ -140090,7 +140090,9 @@ function markFullParagraphInsertions(xml, fragments, structural, index, namespac
140090
140090
  continue;
140091
140091
  const structuralInParagraph = structuralByParagraph.get(`${paragraph.start}:${paragraph.end}`) ?? [];
140092
140092
  const terminalDeletionOfPendingInsertion = marker.structuralKind === "paragraph-mark-insertion" && structuralInParagraph.length === 2 && structuralInParagraph[0] === marker && structuralInParagraph[1]?.structuralKind === "paragraph-mark-deletion";
140093
- if (structuralInParagraph.length !== 1 && !terminalDeletionOfPendingInsertion)
140093
+ const extraStructural = structuralInParagraph.filter((fragment2) => fragment2 !== marker);
140094
+ const extrasArePasteObjects = extraStructural.every((fragment2) => fragment2.structuralKind === "footnote-insertion" || fragment2.structuralKind === "endnote-insertion" || fragment2.structuralKind === "image-insertion" || fragment2.structuralKind === "field-insertion");
140095
+ if (structuralInParagraph.length !== 1 && !terminalDeletionOfPendingInsertion && !(extraStructural.length > 0 && extrasArePasteObjects))
140094
140096
  continue;
140095
140097
  const paragraphFragments = (fragmentsByParagraphStart.get(paragraph.start) ?? []).filter((candidate) => candidate.byteStart >= paragraph.openEnd && candidate.byteEnd <= paragraph.closeStart).sort((left, right) => left.byteStart - right.byteStart);
140096
140098
  const markerIdentity = buildReviewIdentity({
@@ -168023,10 +168025,13 @@ function projectWordLikeReviewGroupsWithLookupContext(lookupContext, input) {
168023
168025
  const partXml = input.partXmlByUri.get(partUri);
168024
168026
  if (!partXml)
168025
168027
  continue;
168026
- if (isBodyStoryId(storyId)) {
168028
+ if (isBodyStoryId(storyId) || isNotesStoryId(storyId)) {
168027
168029
  projectStoryReviewGroups(lookupContext, storyId, partUri, partXml, entries, input);
168028
168030
  }
168029
168031
  projectFullParagraphInsertions(lookupContext, storyId, partUri, partXml, entries, input);
168032
+ if (isBodyStoryId(storyId) || isNotesStoryId(storyId)) {
168033
+ foldSameRsidPastedParagraphParents(storyId, partXml, input);
168034
+ }
168030
168035
  }
168031
168036
  }
168032
168037
  function projectFullParagraphInsertions(lookupContext, storyId, partUri, partXml, entries, input) {
@@ -168190,6 +168195,230 @@ function materializeFullParagraphInsertion(lookupContext, args) {
168190
168195
  for (const fragment2 of objectFragments)
168191
168196
  input.structuralKeyToLogicalId.set(fragment2.entityKey, id);
168192
168197
  }
168198
+ function foldSameRsidPastedParagraphParents(storyId, partXml, input) {
168199
+ const storyEntries = input.byStory.get(storyId);
168200
+ if (!storyEntries)
168201
+ return;
168202
+ const parents = storyEntries.filter((entry) => isFoldablePastedParagraphParent(entry)).sort((left, right) => left.documentOrderStart - right.documentOrderStart);
168203
+ if (parents.length < 2)
168204
+ return;
168205
+ const clusters = [];
168206
+ let current = [];
168207
+ const flush = () => {
168208
+ if (current.length >= 2)
168209
+ clusters.push(current);
168210
+ current = [];
168211
+ };
168212
+ for (const parent of parents) {
168213
+ const previous = current.at(-1);
168214
+ if (!previous) {
168215
+ current = [parent];
168216
+ continue;
168217
+ }
168218
+ if (canFoldPastedParagraphParents(partXml, previous, parent, storyEntries)) {
168219
+ current.push(parent);
168220
+ continue;
168221
+ }
168222
+ flush();
168223
+ current = [parent];
168224
+ }
168225
+ flush();
168226
+ for (const cluster of clusters) {
168227
+ mergePastedParagraphParents(storyId, partXml, cluster, input);
168228
+ }
168229
+ }
168230
+ function isFoldablePastedParagraphParent(entry) {
168231
+ const kind = entry.reviewGroup?.kind;
168232
+ return entry.reviewGroup?.role === "parent" && (kind === "word-full-paragraph-insertion" || kind === "word-inserted-paragraph-range") && entry.subtype !== "table-split" && entry.subtype !== "image-insertion" && entry.subtype !== "list-item-insertion" && entry.subtype !== "field-insertion" && !entry.reviewGroup?.members.some((member) => member.subtype === "field-insertion") && pastedParagraphParentRsid(entry) != null;
168233
+ }
168234
+ function pastedParagraphParentRsid(entry) {
168235
+ const mark = entry.structuralFragments.find((fragment2) => fragment2.structuralKind === "paragraph-mark-insertion" || fragment2.structuralKind === "paragraph-insertion");
168236
+ const rsid = mark?.enclosingParagraphRsid ?? entry.sourceIds.rsids[0];
168237
+ return rsid ? rsid.toUpperCase() : null;
168238
+ }
168239
+ function pastedParagraphParentBounds(entry) {
168240
+ const marks = entry.structuralFragments.filter((fragment2) => fragment2.structuralKind === "paragraph-mark-insertion" || fragment2.structuralKind === "paragraph-insertion");
168241
+ const starts = marks.map((fragment2) => fragment2.enclosingElementStart).filter((value) => value != null);
168242
+ const ends = marks.map((fragment2) => fragment2.enclosingElementEnd).filter((value) => value != null);
168243
+ if (starts.length === 0 || ends.length === 0)
168244
+ return null;
168245
+ return { start: Math.min(...starts), end: Math.max(...ends) };
168246
+ }
168247
+ function canFoldPastedParagraphParents(partXml, previous, next, storyEntries) {
168248
+ if (pastedParagraphParentRsid(previous) !== pastedParagraphParentRsid(next))
168249
+ return false;
168250
+ if (previous.subtype !== next.subtype)
168251
+ return false;
168252
+ if (!isSameReviewGroupIdentity(buildReviewIdentity({
168253
+ name: previous.author.name,
168254
+ email: previous.authorEmail,
168255
+ initials: previous.author.initials
168256
+ }), buildReviewIdentity({
168257
+ name: next.author.name,
168258
+ email: next.authorEmail,
168259
+ initials: next.author.initials
168260
+ }))) {
168261
+ return false;
168262
+ }
168263
+ const previousBounds = pastedParagraphParentBounds(previous);
168264
+ const nextBounds = pastedParagraphParentBounds(next);
168265
+ if (!previousBounds || !nextBounds || nextBounds.start < previousBounds.end)
168266
+ return false;
168267
+ const stripped = partXml.slice(previousBounds.end, nextBounds.start).replace(/<\/?[\w.-]+:(?:footnote|endnote)\b[^>]*>/g, "");
168268
+ if (!isCleanGapXml(stripped))
168269
+ return false;
168270
+ return !hasInterveningVisibleReviewable(previous, next, storyEntries);
168271
+ }
168272
+ function hasInterveningVisibleReviewable(previous, next, storyEntries) {
168273
+ const spanStart = Math.min(previous.documentOrderStart, next.documentOrderStart);
168274
+ const spanEnd = Math.max(previous.documentOrderEnd, next.documentOrderEnd);
168275
+ const ownedIds = new Set([
168276
+ previous.id,
168277
+ next.id,
168278
+ ...previous.reviewGroup?.ownedChildIds ?? [],
168279
+ ...next.reviewGroup?.ownedChildIds ?? []
168280
+ ]);
168281
+ for (const entry of storyEntries) {
168282
+ if (ownedIds.has(entry.id))
168283
+ continue;
168284
+ if (entry.reviewGroup?.role === "child")
168285
+ continue;
168286
+ if (entry.subsumption?.role === "child")
168287
+ continue;
168288
+ if (entry.documentOrderStart > spanStart && entry.documentOrderStart < spanEnd) {
168289
+ return true;
168290
+ }
168291
+ }
168292
+ return false;
168293
+ }
168294
+ function mergePastedParagraphParents(storyId, partXml, cluster, input) {
168295
+ const head = cluster[0];
168296
+ const memberEntries = [];
168297
+ const seenMemberIds = new Set;
168298
+ const addMember = (entry) => {
168299
+ if (!entry || seenMemberIds.has(entry.id))
168300
+ return;
168301
+ seenMemberIds.add(entry.id);
168302
+ memberEntries.push(entry);
168303
+ };
168304
+ for (const parent2 of cluster) {
168305
+ for (const childId of parent2.reviewGroup?.ownedChildIds ?? []) {
168306
+ addMember(input.byId.get(childId));
168307
+ }
168308
+ }
168309
+ if (memberEntries.length === 0 || memberEntries.some((entry) => entry.reviewGroup?.role === "child" && cluster.every((parent2) => parent2.id !== entry.reviewGroup?.parentId))) {
168310
+ return;
168311
+ }
168312
+ const textFragments = uniqueFragments(cluster.flatMap((parent2) => parent2.fragments));
168313
+ const structuralFragments = uniqueStructuralFragments(cluster.flatMap((parent2) => parent2.structuralFragments)).sort((left, right) => left.byteStart - right.byteStart);
168314
+ const members = cluster.flatMap((parent2) => parent2.reviewGroup?.members ?? []);
168315
+ const rsids = uniqueNonEmptyStrings(cluster.flatMap((parent2) => parent2.sourceIds.rsids));
168316
+ const memberWordIds = uniqueNonEmptyStrings([
168317
+ ...cluster.flatMap((parent2) => [
168318
+ parent2.sourceIds.wordIdInsert ?? "",
168319
+ ...parent2.sourceIds.wordIdOther
168320
+ ]),
168321
+ ...members.map((member) => member.wordId ?? "")
168322
+ ]);
168323
+ const wordIdInsert = head.sourceIds.wordIdInsert ?? memberWordIds[0];
168324
+ const wordIdOther = memberWordIds.filter((id) => id !== wordIdInsert);
168325
+ const canonicalText = cluster.map((parent2) => {
168326
+ const text = parent2.after.text ?? "";
168327
+ return text.endsWith(`
168328
+ `) ? text : `${text}
168329
+ `;
168330
+ }).join("");
168331
+ const markParagraphIds = cluster.map((parent2) => {
168332
+ const start = parent2.structuralFragments.find((fragment2) => fragment2.structuralKind === "paragraph-mark-insertion")?.enclosingElementStart;
168333
+ return start != null ? readParagraphIdAt(partXml, start) : null;
168334
+ });
168335
+ const markKey = markParagraphIds.every((paraId) => paraId != null) ? markParagraphIds.map((paraId) => `p:${paraId}`).join(",") : cluster.map((parent2) => parent2.id).join(",");
168336
+ const identityKey = buildGroupIdentityKey(buildReviewIdentity({
168337
+ name: head.author.name,
168338
+ email: head.authorEmail,
168339
+ initials: head.author.initials
168340
+ }));
168341
+ const groupHash = hashGroupKey(["word-inserted-paragraph-range", storyId, identityKey, `pm:${markKey}`, `tx:${textFragments.length}`].join("|"));
168342
+ const groupId = buildLogicalId(storyId, "ins", "word-paragraph-range", groupHash);
168343
+ const noteIds = uniqueNonEmptyStrings(cluster.flatMap((entry) => ownedNoteIds(entry)));
168344
+ const parent = {
168345
+ ...head,
168346
+ id: groupId,
168347
+ type: "insertion",
168348
+ subtype: "text-insertion",
168349
+ documentOrderStart: Math.min(...cluster.map((entry) => entry.documentOrderStart)),
168350
+ documentOrderEnd: Math.max(...cluster.map((entry) => entry.documentOrderEnd)),
168351
+ noteId: noteIds[0] ?? cluster.find((entry) => entry.noteId != null)?.noteId ?? head.noteId,
168352
+ noteIds: noteIds.length > 0 ? noteIds : null,
168353
+ sourceIds: { wordIdInsert, wordIdOther, rsids },
168354
+ revisionGroupId: groupId,
168355
+ after: { text: canonicalText },
168356
+ contributingRsids: rsids,
168357
+ fragments: textFragments,
168358
+ structuralFragments,
168359
+ structuralDelta: null,
168360
+ excerpt: canonicalText,
168361
+ reviewGroup: {
168362
+ kind: "word-inserted-paragraph-range",
168363
+ role: "parent",
168364
+ parentId: null,
168365
+ ownedChildIds: memberEntries.map((entry) => entry.id),
168366
+ duplicateDisplayPolicy: "subsumed",
168367
+ members
168368
+ }
168369
+ };
168370
+ for (const entry of memberEntries) {
168371
+ const mutable = entry;
168372
+ mutable.reviewGroup = {
168373
+ kind: "word-inserted-paragraph-range",
168374
+ role: "child",
168375
+ parentId: groupId,
168376
+ ownedChildIds: [],
168377
+ duplicateDisplayPolicy: "subsumed",
168378
+ members: []
168379
+ };
168380
+ mutable.revisionGroupId = groupId;
168381
+ }
168382
+ const storyArr = input.byStory.get(storyId);
168383
+ if (!storyArr)
168384
+ return;
168385
+ for (const dissolved of cluster) {
168386
+ input.byId.delete(dissolved.id);
168387
+ const index = storyArr.indexOf(dissolved);
168388
+ if (index >= 0)
168389
+ storyArr.splice(index, 1);
168390
+ }
168391
+ input.byId.set(groupId, parent);
168392
+ storyArr.push(parent);
168393
+ for (const fragment2 of textFragments)
168394
+ input.rsidToLogicalId.set(fragment2.rsid, groupId);
168395
+ for (const fragment2 of structuralFragments)
168396
+ input.structuralKeyToLogicalId.set(fragment2.entityKey, groupId);
168397
+ }
168398
+ function uniqueFragments(fragments) {
168399
+ const seen = new Set;
168400
+ const out = [];
168401
+ for (const fragment2 of fragments) {
168402
+ const key2 = `${fragment2.byteStart}:${fragment2.byteEnd}:${fragment2.rsid}`;
168403
+ if (seen.has(key2))
168404
+ continue;
168405
+ seen.add(key2);
168406
+ out.push(fragment2);
168407
+ }
168408
+ return out;
168409
+ }
168410
+ function uniqueStructuralFragments(fragments) {
168411
+ const seen = new Set;
168412
+ const out = [];
168413
+ for (const fragment2 of fragments) {
168414
+ const key2 = fragment2.entityKey;
168415
+ if (seen.has(key2))
168416
+ continue;
168417
+ seen.add(key2);
168418
+ out.push(fragment2);
168419
+ }
168420
+ return out;
168421
+ }
168193
168422
  function classifyFullParagraphInsertion(lookupContext, partXml, paragraphStart, paragraphEnd, paragraphXml) {
168194
168423
  const parent = findImmediateParentElementAtPosition(partXml, paragraphStart, lookupContext);
168195
168424
  if (parent) {
@@ -168385,14 +168614,19 @@ function buildTrackedFieldLocationKey(lookupContext, partXml, fragment2) {
168385
168614
  function extractDeletionText(deletion, partXml) {
168386
168615
  return deletion.fragments.map((fragment2) => extractVisibleTextFromXml(partXml.slice(fragment2.byteStart, fragment2.byteEnd), "del")).join("");
168387
168616
  }
168617
+ function isAbsorbableInlineObjectInsertion(entry, fragment2) {
168618
+ const kind = entry.subtype;
168619
+ return (kind === "footnote-insertion" || kind === "endnote-insertion" || kind === "image-insertion" || kind === "field-insertion") && fragment2.structuralKind === kind && entry.reviewGroup == null && entry.subsumption == null;
168620
+ }
168388
168621
  function projectStoryReviewGroups(lookupContext, storyId, partUri, partXml, entries, input) {
168389
168622
  const markCandidates = collectMarkCandidates(storyId, partXml, entries);
168390
168623
  if (markCandidates.candidatesByParagraphStart.size === 0)
168391
168624
  return;
168392
- const textCandidates = collectTextCandidates(lookupContext, partXml, entries);
168625
+ const textCandidates = collectTextCandidates(lookupContext, partXml, entries, storyId);
168393
168626
  if (textCandidates.length === 0)
168394
168627
  return;
168395
- const paragraphs = buildParagraphInfos(lookupContext, partXml, markCandidates, textCandidates);
168628
+ const objectCandidates = collectObjectCandidates(lookupContext, partXml, entries);
168629
+ const paragraphs = buildParagraphInfos(lookupContext, partXml, markCandidates, textCandidates, objectCandidates);
168396
168630
  if (paragraphs.length === 0)
168397
168631
  return;
168398
168632
  const atoms = buildAtomSequence(partXml, paragraphs);
@@ -168403,17 +168637,33 @@ function projectStoryReviewGroups(lookupContext, storyId, partUri, partXml, entr
168403
168637
  }
168404
168638
  }
168405
168639
  }
168406
- function splitDistinctFullParagraphRuns(run) {
168407
- const members = run.filter((atom) => atom.kind !== "blocker");
168408
- const fullParagraphRsidsByStart = new Map(members.flatMap((atom) => {
168640
+ function paragraphRunHasImageInsertion(paragraphRun) {
168641
+ return paragraphRun.some((atom) => atom.kind === "object" && atom.candidate.entry.subtype === "image-insertion");
168642
+ }
168643
+ function paragraphRunFullParagraphRsid(paragraphRun) {
168644
+ for (const atom of paragraphRun) {
168409
168645
  if (atom.kind !== "mark" || atom.candidate.fragment.fullParagraphInsertion !== true)
168410
- return [];
168646
+ continue;
168411
168647
  const rsid = atom.candidate.fragment.enclosingParagraphRsid;
168412
- return rsid ? [[atom.candidate.paragraphStart, rsid.toUpperCase()]] : [];
168413
- }));
168414
- if (fullParagraphRsidsByStart.size <= 1 || new Set(fullParagraphRsidsByStart.values()).size <= 1) {
168415
- return [run];
168648
+ return rsid ? rsid.toUpperCase() : null;
168649
+ }
168650
+ return null;
168651
+ }
168652
+ function paragraphRunIsFullParagraphInsertion(paragraphRun) {
168653
+ return paragraphRun.some((atom) => atom.kind === "mark" && atom.candidate.fragment.fullParagraphInsertion === true);
168654
+ }
168655
+ function canContinueFullParagraphRun(currentRun, nextParagraphRun, currentRsid, nextRsid, anyStampedFullParagraph) {
168656
+ if (paragraphRunHasImageInsertion(currentRun) !== paragraphRunHasImageInsertion(nextParagraphRun)) {
168657
+ return false;
168416
168658
  }
168659
+ if (!anyStampedFullParagraph)
168660
+ return true;
168661
+ if (currentRsid == null || nextRsid == null)
168662
+ return false;
168663
+ return currentRsid === nextRsid;
168664
+ }
168665
+ function splitDistinctFullParagraphRuns(run) {
168666
+ const members = run.filter((atom) => atom.kind !== "blocker");
168417
168667
  const paragraphRuns = [];
168418
168668
  for (const member of members) {
168419
168669
  const current = paragraphRuns.at(-1);
@@ -168423,6 +168673,11 @@ function splitDistinctFullParagraphRuns(run) {
168423
168673
  paragraphRuns.push([member]);
168424
168674
  }
168425
168675
  }
168676
+ const fullParagraphRuns = paragraphRuns.filter(paragraphRunIsFullParagraphInsertion);
168677
+ if (fullParagraphRuns.length <= 1) {
168678
+ return [run];
168679
+ }
168680
+ const anyStampedFullParagraph = fullParagraphRuns.some((paragraphRun) => paragraphRunFullParagraphRsid(paragraphRun) != null);
168426
168681
  const projectedRuns = [];
168427
168682
  let currentRun = [];
168428
168683
  let currentRsid = null;
@@ -168434,9 +168689,7 @@ function splitDistinctFullParagraphRuns(run) {
168434
168689
  currentRsid = null;
168435
168690
  };
168436
168691
  for (const paragraphRun of paragraphRuns) {
168437
- const paragraphStart = paragraphRun[0].candidate.paragraphStart;
168438
- const paragraphRsid = fullParagraphRsidsByStart.get(paragraphStart) ?? null;
168439
- if (paragraphRsid === null) {
168692
+ if (!paragraphRunIsFullParagraphInsertion(paragraphRun)) {
168440
168693
  if (currentRsid !== null && paragraphRun.every((atom) => atom.kind === "text")) {
168441
168694
  currentRun.push(...paragraphRun);
168442
168695
  continue;
@@ -168445,14 +168698,19 @@ function splitDistinctFullParagraphRuns(run) {
168445
168698
  currentRun = [...paragraphRun];
168446
168699
  continue;
168447
168700
  }
168448
- if (currentRsid === paragraphRsid) {
168701
+ const paragraphRsid = paragraphRunFullParagraphRsid(paragraphRun);
168702
+ if (currentRun.length > 0 && canContinueFullParagraphRun(currentRun, paragraphRun, currentRsid, paragraphRsid, anyStampedFullParagraph)) {
168449
168703
  currentRun.push(...paragraphRun);
168704
+ if (currentRsid == null)
168705
+ currentRsid = paragraphRsid;
168450
168706
  continue;
168451
168707
  }
168452
- if (currentRsid === null && currentRun.every((atom) => atom.kind === "mark")) {
168453
- currentRsid = paragraphRsid;
168454
- currentRun = [...currentRun, ...paragraphRun];
168455
- continue;
168708
+ if (currentRsid === null && currentRun.length > 0 && currentRun.every((atom) => atom.kind === "mark")) {
168709
+ if (canContinueFullParagraphRun(currentRun, paragraphRun, paragraphRsid, paragraphRsid, anyStampedFullParagraph)) {
168710
+ currentRsid = paragraphRsid;
168711
+ currentRun = [...currentRun, ...paragraphRun];
168712
+ continue;
168713
+ }
168456
168714
  }
168457
168715
  flushCurrentRun();
168458
168716
  currentRsid = paragraphRsid;
@@ -168473,10 +168731,10 @@ function collectMarkCandidates(storyId, partXml, entries) {
168473
168731
  continue;
168474
168732
  if (fragment2.enclosingElementStart === undefined || fragment2.enclosingElementEnd === undefined)
168475
168733
  continue;
168476
- const isCandidateShape = entry.subtype === "paragraph-mark-insertion" && fragment2.structuralKind === "paragraph-mark-insertion" && entry.structuralFragments.length === 1 && entry.noteId == null && entry.subsumption == null && entry.reviewGroup == null;
168734
+ const isCandidateShape = entry.subtype === "paragraph-mark-insertion" && fragment2.structuralKind === "paragraph-mark-insertion" && entry.structuralFragments.length === 1 && (entry.noteId == null || isNotesStoryId(storyId)) && entry.subsumption == null && entry.reviewGroup == null;
168477
168735
  if (isCandidateShape) {
168478
168736
  markFragments.push({ entry, fragment: fragment2 });
168479
- } else if (isTerminalDeletionOfPendingFullParagraphInsertion(fragment2, entries)) {} else {
168737
+ } else if (isTerminalDeletionOfPendingFullParagraphInsertion(fragment2, entries)) {} else if (isAbsorbableInlineObjectInsertion(entry, fragment2)) {} else {
168480
168738
  blockedParagraphStarts.add(fragment2.enclosingElementStart);
168481
168739
  }
168482
168740
  }
@@ -168517,12 +168775,12 @@ function isTerminalDeletionOfPendingFullParagraphInsertion(fragment2, entries) {
168517
168775
  const markers = entries.flatMap((entry) => entry.structuralFragments).filter((candidate2) => candidate2.enclosingElementStart === paragraphStart && candidate2.enclosingElementEnd === paragraphEnd && (candidate2.structuralKind === "paragraph-mark-insertion" || candidate2.structuralKind === "paragraph-mark-deletion")).sort((left, right) => left.byteStart - right.byteStart);
168518
168776
  return markers.length === 2 && markers[0]?.structuralKind === "paragraph-mark-insertion" && markers[0].fullParagraphInsertion === true && markers[1] === fragment2;
168519
168777
  }
168520
- function collectTextCandidates(lookupContext, partXml, entries) {
168778
+ function collectTextCandidates(lookupContext, partXml, entries, storyId) {
168521
168779
  const out = [];
168522
168780
  for (const entry of entries) {
168523
168781
  if (entry.type !== "insertion" || entry.subtype !== "text-insertion")
168524
168782
  continue;
168525
- if (entry.noteId != null)
168783
+ if (entry.noteId != null && !isNotesStoryId(storyId))
168526
168784
  continue;
168527
168785
  if (entry.sides || entry.movePair)
168528
168786
  continue;
@@ -168567,14 +168825,60 @@ function collectTextCandidates(lookupContext, partXml, entries) {
168567
168825
  }
168568
168826
  return out;
168569
168827
  }
168828
+ function collectObjectCandidates(lookupContext, partXml, entries) {
168829
+ const out = [];
168830
+ for (const entry of entries) {
168831
+ if (entry.type !== "structural")
168832
+ continue;
168833
+ if (entry.sides || entry.movePair)
168834
+ continue;
168835
+ if (entry.subsumption || entry.reviewGroup)
168836
+ continue;
168837
+ if (entry.structuralFragments.length === 0)
168838
+ continue;
168839
+ if (!entry.structuralFragments.every((fragment2) => isAbsorbableInlineObjectInsertion(entry, fragment2))) {
168840
+ continue;
168841
+ }
168842
+ let paragraphStart = null;
168843
+ let eligible = true;
168844
+ for (const fragment2 of entry.structuralFragments) {
168845
+ const paragraph = findEnclosingElementAtPosition(partXml, fragment2.byteStart, "p", undefined, lookupContext);
168846
+ if (!paragraph) {
168847
+ eligible = false;
168848
+ break;
168849
+ }
168850
+ if (paragraphStart == null)
168851
+ paragraphStart = paragraph.start;
168852
+ if (paragraph.start !== paragraphStart) {
168853
+ eligible = false;
168854
+ break;
168855
+ }
168856
+ }
168857
+ if (!eligible || paragraphStart == null)
168858
+ continue;
168859
+ const headFragment = entry.structuralFragments[0];
168860
+ out.push({
168861
+ entry,
168862
+ fragments: [...entry.structuralFragments].sort((a, b) => a.byteStart - b.byteStart),
168863
+ paragraphStart,
168864
+ identity: buildReviewIdentity({
168865
+ name: entry.author.name,
168866
+ email: entry.authorEmail ?? headFragment.authorEmail,
168867
+ initials: entry.author.initials
168868
+ }),
168869
+ initials: entry.author.initials
168870
+ });
168871
+ }
168872
+ return out;
168873
+ }
168570
168874
  function isSimpleInlineInsertionWrapper(wrapperXml) {
168571
168875
  const innerMatch = /^<[\w.-]+:ins\b[^>]*>([\s\S]*)<\/[\w.-]+:ins>\s*$/.exec(wrapperXml);
168572
168876
  if (!innerMatch)
168573
168877
  return false;
168574
168878
  const inner = innerMatch[1] ?? "";
168575
- return !/<[\w.-]+:(?:ins|del|moveFrom|moveTo|drawing|tbl|p|fldSimple|fldChar|sdt|footnoteReference|endnoteReference|commentReference)\b/.test(inner);
168879
+ return !/<[\w.-]+:(?:ins|del|moveFrom|moveTo)\b/.test(inner);
168576
168880
  }
168577
- function buildParagraphInfos(lookupContext, partXml, marks, textCandidates) {
168881
+ function buildParagraphInfos(lookupContext, partXml, marks, textCandidates, objectCandidates = []) {
168578
168882
  const byStart = new Map;
168579
168883
  const ensure = (start, end) => {
168580
168884
  let info = byStart.get(start);
@@ -168585,6 +168889,7 @@ function buildParagraphInfos(lookupContext, partXml, marks, textCandidates) {
168585
168889
  mark: null,
168586
168890
  markBlocked: marks.blockedParagraphStarts.has(start),
168587
168891
  textFragments: [],
168892
+ objectFragments: [],
168588
168893
  invalid: false
168589
168894
  };
168590
168895
  byStart.set(start, info);
@@ -168604,17 +168909,28 @@ function buildParagraphInfos(lookupContext, partXml, marks, textCandidates) {
168604
168909
  info.textFragments.push({ candidate: candidate2, fragment: fragment2 });
168605
168910
  }
168606
168911
  }
168912
+ for (const candidate2 of objectCandidates) {
168913
+ const paragraph = findEnclosingElementAtPosition(partXml, candidate2.fragments[0].byteStart, "p", undefined, lookupContext);
168914
+ if (!paragraph)
168915
+ continue;
168916
+ const info = ensure(paragraph.start, paragraph.end);
168917
+ for (const fragment2 of candidate2.fragments) {
168918
+ info.objectFragments.push({ candidate: candidate2, fragment: fragment2 });
168919
+ }
168920
+ }
168607
168921
  const paragraphs = Array.from(byStart.values()).sort((a, b) => a.start - b.start);
168608
168922
  for (const paragraph of paragraphs) {
168609
168923
  paragraph.textFragments.sort((a, b) => a.fragment.byteStart - b.fragment.byteStart);
168924
+ paragraph.objectFragments.sort((a, b) => a.fragment.byteStart - b.fragment.byteStart);
168610
168925
  paragraph.invalid = !isEligibleGroupParagraph(lookupContext, partXml, paragraph);
168611
168926
  }
168612
168927
  return paragraphs;
168613
168928
  }
168614
168929
  function isEligibleGroupParagraph(lookupContext, partXml, paragraph) {
168615
168930
  const parent = findImmediateParentElementAtPosition(partXml, paragraph.start, lookupContext);
168616
- if (!parent || parent.localName !== "body")
168931
+ if (!parent || parent.localName !== "body" && parent.localName !== "footnote" && parent.localName !== "endnote") {
168617
168932
  return false;
168933
+ }
168618
168934
  const paragraphXml = partXml.slice(paragraph.start, paragraph.end);
168619
168935
  const pPrXml = readLeadingParagraphPropertyBlock(paragraphXml)?.xml ?? "";
168620
168936
  if (pPrXml && /<[\w.-]+:sectPr\b/.test(pPrXml))
@@ -168659,7 +168975,7 @@ function buildAtomSequence(partXml, paragraphs) {
168659
168975
  for (const paragraph of paragraphs) {
168660
168976
  if (previous) {
168661
168977
  const between = partXml.slice(previous.end, paragraph.start);
168662
- if (!isCleanGapXml(between))
168978
+ if (!isCleanGroupingGap(between, previous, paragraph))
168663
168979
  atoms.push({ kind: "blocker" });
168664
168980
  }
168665
168981
  previous = paragraph;
@@ -168729,17 +169045,21 @@ function appendParagraphAtoms(partXml, paragraph, atoms, hyperlinkTags) {
168729
169045
  return isCleanGapXml(gap + partXml.slice(cursor2, end));
168730
169046
  };
168731
169047
  let cursor = contentStart;
168732
- for (const { candidate: candidate2, fragment: fragment2 } of paragraph.textFragments) {
168733
- if (fragment2.byteStart < cursor || fragment2.byteEnd > contentEnd) {
169048
+ const contentAtoms = [
169049
+ ...paragraph.textFragments.map(({ candidate: candidate2, fragment: fragment2 }) => ({ kind: "text", candidate: candidate2, fragment: fragment2 })),
169050
+ ...paragraph.objectFragments.map(({ candidate: candidate2, fragment: fragment2 }) => ({ kind: "object", candidate: candidate2, fragment: fragment2 }))
169051
+ ].sort((left, right) => left.fragment.byteStart - right.fragment.byteStart);
169052
+ for (const atom of contentAtoms) {
169053
+ if (atom.fragment.byteStart < cursor || atom.fragment.byteEnd > contentEnd) {
168734
169054
  atoms.push({ kind: "blocker" });
168735
- cursor = Math.max(cursor, fragment2.byteEnd);
169055
+ cursor = Math.max(cursor, atom.fragment.byteEnd);
168736
169056
  continue;
168737
169057
  }
168738
- if (!cleanGap(cursor, fragment2.byteStart)) {
169058
+ if (!cleanGap(cursor, atom.fragment.byteStart)) {
168739
169059
  atoms.push({ kind: "blocker" });
168740
169060
  }
168741
- atoms.push({ kind: "text", candidate: candidate2, fragment: fragment2 });
168742
- cursor = fragment2.byteEnd;
169061
+ atoms.push(atom);
169062
+ cursor = atom.fragment.byteEnd;
168743
169063
  }
168744
169064
  if (!cleanGap(cursor, contentEnd)) {
168745
169065
  atoms.push({ kind: "blocker" });
@@ -168757,6 +169077,16 @@ function isCleanGapXml(xml) {
168757
169077
  return false;
168758
169078
  return containsOnlyAllowedPairingMarkers(xml);
168759
169079
  }
169080
+ function isCleanGroupingGap(between, previous, next) {
169081
+ const stripped = between.replace(/<\/?[\w.-]+:(?:footnote|endnote)\b[^>]*>/g, "");
169082
+ if (!isCleanGapXml(stripped))
169083
+ return false;
169084
+ if (stripped === between)
169085
+ return true;
169086
+ const left = previous.mark?.fragment.enclosingParagraphRsid;
169087
+ const right = next.mark?.fragment.enclosingParagraphRsid;
169088
+ return Boolean(left && right && left.toUpperCase() === right.toUpperCase());
169089
+ }
168760
169090
  function collectSameIdentityRuns(atoms) {
168761
169091
  const runs = [];
168762
169092
  let current = [];
@@ -168791,17 +169121,17 @@ function isSameGroupIdentity(a, b) {
168791
169121
  function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, input) {
168792
169122
  const textAtoms = run.filter((atom) => atom.kind === "text");
168793
169123
  const markAtoms = run.filter((atom) => atom.kind === "mark");
169124
+ const objectAtoms = run.filter((atom) => atom.kind === "object");
168794
169125
  if (textAtoms.length === 0 || markAtoms.length === 0)
168795
169126
  return;
168796
169127
  const carrierParagraphStarts = new Set([
168797
169128
  ...textAtoms.map((atom) => atom.candidate.paragraphStart),
169129
+ ...objectAtoms.map((atom) => atom.candidate.paragraphStart),
168798
169130
  ...markAtoms.map((atom) => atom.candidate.paragraphStart)
168799
169131
  ]);
168800
- const fullParagraphStarts = new Set(markAtoms.filter((atom) => atom.candidate.fragment.fullParagraphInsertion === true && atom.candidate.fragment.enclosingParagraphRsid !== undefined).map((atom) => atom.candidate.paragraphStart));
168801
- let listInsertion;
168802
- if ([...carrierParagraphStarts].every((paragraphStart) => fullParagraphStarts.has(paragraphStart))) {
168803
- if (markAtoms.length !== 1)
168804
- return;
169132
+ const fullParagraphStarts = new Set(markAtoms.filter((atom) => atom.candidate.fragment.fullParagraphInsertion === true).map((atom) => atom.candidate.paragraphStart));
169133
+ let fullParagraphOwner;
169134
+ if (markAtoms.length === 1 && [...carrierParagraphStarts].every((paragraphStart) => fullParagraphStarts.has(paragraphStart))) {
168805
169135
  const marker = markAtoms[0].candidate.fragment;
168806
169136
  const paragraphStart = marker.enclosingElementStart;
168807
169137
  const paragraphEnd = marker.enclosingElementEnd;
@@ -168811,12 +169141,12 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168811
169141
  const subtype = classifyFullParagraphInsertion(lookupContext, partXml, paragraphStart, paragraphEnd, paragraphXml).subtype;
168812
169142
  if (subtype === "table-split")
168813
169143
  return;
168814
- if (subtype === "list-item-insertion") {
169144
+ if (subtype === "list-item-insertion" || objectAtoms.length > 0) {
168815
169145
  const candidate2 = markAtoms[0].candidate;
168816
- const ownsStructuralContent = input.byStory.get(storyId)?.some((entry) => entry.type === "structural" && entry !== candidate2.entry && entry.structuralFragments.some((fragment2) => fragment2.byteStart >= paragraphStart && fragment2.byteEnd <= paragraphEnd));
169146
+ const ownsStructuralContent = input.byStory.get(storyId)?.some((entry) => entry.type === "structural" && entry !== candidate2.entry && !objectAtoms.some((atom) => atom.candidate.entry === entry) && entry.structuralFragments.some((fragment2) => fragment2.byteStart >= paragraphStart && fragment2.byteEnd <= paragraphEnd));
168817
169147
  if (ownsStructuralContent)
168818
169148
  return;
168819
- listInsertion = candidate2;
169149
+ fullParagraphOwner = candidate2;
168820
169150
  }
168821
169151
  }
168822
169152
  const memberEntries = [];
@@ -168839,15 +169169,15 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168839
169169
  const markKey = markParagraphIds.every((paraId) => paraId != null) ? markParagraphIds.map((paraId) => `p:${paraId}`).join(",") : markOrdinals.join(",");
168840
169170
  const groupHash = hashGroupKey(["word-inserted-paragraph-range", storyId, identityKey, `pm:${markKey}`, `tx:${textAtoms.length}`].join("|"));
168841
169171
  const groupId = buildLogicalId(storyId, "ins", "word-paragraph-range", groupHash);
168842
- if (listInsertion) {
169172
+ if (fullParagraphOwner) {
168843
169173
  materializeFullParagraphInsertion(lookupContext, {
168844
169174
  storyId,
168845
169175
  partUri,
168846
169176
  partXml,
168847
- structuralEntry: listInsertion.entry,
168848
- marker: listInsertion.fragment,
169177
+ structuralEntry: fullParagraphOwner.entry,
169178
+ marker: fullParagraphOwner.fragment,
168849
169179
  textEntries: uniqueEntries(textAtoms.map((atom) => atom.candidate.entry)),
168850
- objectEntries: [],
169180
+ objectEntries: uniqueEntries(objectAtoms.map((atom) => atom.candidate.entry)),
168851
169181
  logicalId: groupId,
168852
169182
  input
168853
169183
  });
@@ -168855,8 +169185,11 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168855
169185
  }
168856
169186
  const canonicalText = buildCanonicalGroupText(partXml, run);
168857
169187
  const fragments = textAtoms.map((atom) => atom.fragment);
168858
- const structuralFragments = markAtoms.map((atom) => atom.candidate.fragment);
168859
- const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? { start: atom.fragment.byteStart, end: atom.fragment.byteEnd } : { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd });
169188
+ const structuralFragments = [
169189
+ ...objectAtoms.map((atom) => atom.fragment),
169190
+ ...markAtoms.map((atom) => atom.candidate.fragment)
169191
+ ].sort((left, right) => left.byteStart - right.byteStart);
169192
+ const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "mark" ? { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd } : { start: atom.fragment.byteStart, end: atom.fragment.byteEnd });
168860
169193
  const documentOrderStart = Math.min(...semanticPositions.map((position) => position.start));
168861
169194
  const documentOrderEnd = Math.max(...semanticPositions.map((position) => position.end));
168862
169195
  const textEntries = uniqueEntries(textAtoms.map((atom) => atom.candidate.entry));
@@ -168872,26 +169205,45 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168872
169205
  ...structuralFragments.map((fragment2) => fragment2.date)
168873
169206
  ]);
168874
169207
  const headEntry = memberEntries[0];
168875
- const members = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? {
168876
- logicalId: atom.candidate.entry.id,
168877
- carrier: "text",
168878
- partUri,
168879
- storyId,
168880
- rsid: atom.fragment.rsid,
168881
- ...atom.fragment.wId ? { wordId: atom.fragment.wId } : {},
168882
- subtype: "text-insertion",
168883
- byteStart: atom.fragment.byteStart,
168884
- byteEnd: atom.fragment.byteEnd
168885
- } : {
168886
- logicalId: atom.candidate.entry.id,
168887
- carrier: "structural",
168888
- partUri,
168889
- storyId,
168890
- entityKey: atom.candidate.fragment.entityKey,
168891
- ...atom.candidate.fragment.wId ? { wordId: atom.candidate.fragment.wId } : {},
168892
- subtype: "paragraph-mark-insertion",
168893
- byteStart: atom.candidate.fragment.byteStart,
168894
- byteEnd: atom.candidate.fragment.byteEnd
169208
+ const ownedNotes = uniqueNonEmptyStrings(memberEntries.flatMap((entry) => ownedNoteIds(entry)));
169209
+ const members = run.filter((atom) => atom.kind !== "blocker").map((atom) => {
169210
+ if (atom.kind === "text") {
169211
+ return {
169212
+ logicalId: atom.candidate.entry.id,
169213
+ carrier: "text",
169214
+ partUri,
169215
+ storyId,
169216
+ rsid: atom.fragment.rsid,
169217
+ ...atom.fragment.wId ? { wordId: atom.fragment.wId } : {},
169218
+ subtype: "text-insertion",
169219
+ byteStart: atom.fragment.byteStart,
169220
+ byteEnd: atom.fragment.byteEnd
169221
+ };
169222
+ }
169223
+ if (atom.kind === "object") {
169224
+ return {
169225
+ logicalId: atom.candidate.entry.id,
169226
+ carrier: "structural",
169227
+ partUri,
169228
+ storyId,
169229
+ entityKey: atom.fragment.entityKey,
169230
+ ...atom.fragment.wId ? { wordId: atom.fragment.wId } : {},
169231
+ subtype: atom.fragment.structuralKind,
169232
+ byteStart: atom.fragment.byteStart,
169233
+ byteEnd: atom.fragment.byteEnd
169234
+ };
169235
+ }
169236
+ return {
169237
+ logicalId: atom.candidate.entry.id,
169238
+ carrier: "structural",
169239
+ partUri,
169240
+ storyId,
169241
+ entityKey: atom.candidate.fragment.entityKey,
169242
+ ...atom.candidate.fragment.wId ? { wordId: atom.candidate.fragment.wId } : {},
169243
+ subtype: "paragraph-mark-insertion",
169244
+ byteStart: atom.candidate.fragment.byteStart,
169245
+ byteEnd: atom.candidate.fragment.byteEnd
169246
+ };
168895
169247
  });
168896
169248
  const parent = {
168897
169249
  id: groupId,
@@ -168900,7 +169252,8 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168900
169252
  state: "open",
168901
169253
  storyId,
168902
169254
  partUri,
168903
- noteId: null,
169255
+ noteId: ownedNotes[0] ?? memberEntries.find((entry) => entry.noteId != null)?.noteId ?? null,
169256
+ noteIds: ownedNotes.length > 0 ? ownedNotes : null,
168904
169257
  documentOrderStart,
168905
169258
  documentOrderEnd,
168906
169259
  author: { name: headEntry.author.name, initials: headEntry.author.initials },
@@ -169014,9 +169367,17 @@ function uniqueNonEmptyStrings(values) {
169014
169367
  }
169015
169368
  return out;
169016
169369
  }
169370
+ function ownedNoteIds(entry) {
169371
+ if (entry.noteIds && entry.noteIds.length > 0)
169372
+ return [...entry.noteIds];
169373
+ return entry.noteId != null ? [entry.noteId] : [];
169374
+ }
169017
169375
  function isBodyStoryId(storyId) {
169018
169376
  return storyId.startsWith("main:") || storyId.endsWith(":/word/document.xml");
169019
169377
  }
169378
+ function isNotesStoryId(storyId) {
169379
+ return storyId.startsWith("footnotes:") || storyId.startsWith("endnotes:");
169380
+ }
169020
169381
  function buildBodyParagraphOrdinalIndexWithLookupContext(lookupContext, partXml) {
169021
169382
  const bodyOpen = /<([\w.-]+):body\b[^>]*>/.exec(partXml);
169022
169383
  if (!bodyOpen) {
@@ -169997,13 +170358,14 @@ function materializeDeletionGroup(storyId, partUri, partXml, run, carrierOrdinal
169997
170358
  ].filter((ordinal) => ordinal !== undefined).sort((a, b) => a - b);
169998
170359
  const memberParagraphTokens = run.filter((atom) => atom.kind !== "blocker").map((atom) => {
169999
170360
  const paraId = readParagraphIdAt(partXml, atom.candidate.paragraphStart);
170000
- return paraId != null ? `${atom.kind === "text" ? "t" : "m"}@p:${paraId}` : null;
170361
+ const kindToken = atom.kind === "mark" ? "m" : atom.kind === "object" ? "o" : "t";
170362
+ return paraId != null ? `${kindToken}@p:${paraId}` : null;
170001
170363
  });
170002
170364
  const memberKey = memberParagraphTokens.every((token) => token != null) ? memberParagraphTokens.join(",") : carrierOrdinals.join(",");
170003
170365
  const groupHash = hashGroupKey(["word-deleted-paragraph-range", storyId, partUri, `co:${memberKey}`].join("|"));
170004
170366
  const groupId = buildLogicalId(storyId, "del", "word-deleted-paragraph-range", groupHash);
170005
170367
  const canonicalText = buildCanonicalDeletionGroupText(partXml, run);
170006
- const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? { start: atom.fragment.byteStart, end: atom.fragment.byteEnd } : { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd });
170368
+ const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "mark" ? { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd } : { start: atom.fragment.byteStart, end: atom.fragment.byteEnd });
170007
170369
  const documentOrderStart = Math.min(...semanticPositions.map((position) => position.start));
170008
170370
  const documentOrderEnd = Math.max(...semanticPositions.map((position) => position.end));
170009
170371
  const textEntries = uniqueEntries(textAtoms.map((atom) => atom.candidate.entry));
@@ -170021,7 +170383,7 @@ function materializeDeletionGroup(storyId, partUri, partXml, run, carrierOrdinal
170021
170383
  const headEntry = memberEntries[0];
170022
170384
  const hasDeletedText = textAtoms.length > 0;
170023
170385
  const structuralDelta = hasDeletedText ? null : buildDeletedParagraphMarkRangeStructuralDelta(partXml, structuralFragments);
170024
- const members = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? {
170386
+ const members = run.filter((atom) => atom.kind === "text" || atom.kind === "mark").map((atom) => atom.kind === "text" ? {
170025
170387
  logicalId: atom.candidate.entry.id,
170026
170388
  carrier: "text",
170027
170389
  partUri,
@@ -174115,6 +174477,7 @@ function buildStructuralLogical(lookupContext, entityKey, group, noteId, partXml
174115
174477
  documentOrderStart,
174116
174478
  documentOrderEnd,
174117
174479
  author: { name: head.author.name, initials: head.author.initials },
174480
+ authorEmail: head.authorEmail ?? null,
174118
174481
  date: head.date,
174119
174482
  sourceIds,
174120
174483
  revisionGroupId: id,
@@ -180832,7 +181195,7 @@ function reserveRevisionWIdBlock(state, count) {
180832
181195
  }
180833
181196
  return base2;
180834
181197
  }
180835
- function planRevisionWIdBlock(state, count) {
181198
+ function planRevisionWIdBlock(state, count, options) {
180836
181199
  let max2 = state.idReservations.revisionWIds.max;
180837
181200
  if (!state.idReservations.revisionWIds.seeded) {
180838
181201
  for (const [, bytes] of state.packageBytes.entries()) {
@@ -180841,6 +181204,9 @@ function planRevisionWIdBlock(state, count) {
180841
181204
  max2 = value;
180842
181205
  }
180843
181206
  }
181207
+ if (options?.afterMax != null && options.afterMax > max2) {
181208
+ max2 = options.afterMax;
181209
+ }
180844
181210
  const size = Math.max(1, Math.floor(count));
180845
181211
  return Array.from({ length: size }, (_, index) => max2 + index + 1);
180846
181212
  }
@@ -216733,7 +217099,8 @@ function compilePastedNotesMetadata(input) {
216733
217099
  paragraph,
216734
217100
  type,
216735
217101
  includeMarker: index === 0,
216736
- paraSalt: paraSalt += 1
217102
+ paraSalt: paraSalt += 1,
217103
+ trackedStamp: input.trackedStamp ?? null
216737
217104
  })).join("");
216738
217105
  fragments.push(`<w:${type} w:id="${escapeXmlAttribute(destinationId)}">${bodyXml}</w:${type}>`);
216739
217106
  }
@@ -216837,7 +217204,18 @@ function renderNoteParagraphXml(input) {
216837
217204
  const markerStyle = input.type === "footnote" ? "FootnoteReference" : "EndnoteReference";
216838
217205
  const markerLocal = input.type === "footnote" ? "footnoteRef" : "endnoteRef";
216839
217206
  const marker = input.includeMarker ? `<w:r><w:rPr><w:rStyle w:val="${markerStyle}"/></w:rPr><w:${markerLocal}/></w:r>` : "";
216840
- return `<w:p w14:paraId="${escapeXmlAttribute(paraId)}">` + `<w:pPr><w:pStyle w:val="${escapeXmlAttribute(style)}"/></w:pPr>` + `${marker}${renderPasteRuns(noteBodyRuns(input.paragraph))}</w:p>`;
217207
+ const runs = noteBodyRuns(input.paragraph);
217208
+ const inner = `${marker}${renderPasteRuns(runs)}`;
217209
+ const pPrXml = `<w:pPr><w:pStyle w:val="${escapeXmlAttribute(style)}"/></w:pPr>`;
217210
+ const stamp = input.trackedStamp;
217211
+ const pPr = stamp ? renderTrackedParagraphMarkProperties(pPrXml, stamp) : pPrXml;
217212
+ const body = stamp && inner ? wrapNoteTrackedInsertion(inner, stamp) : inner;
217213
+ const rsid = stamp ? ` w:rsidR="${escapeXmlAttribute(stamp.rsid)}"` : "";
217214
+ return `<w:p w14:paraId="${escapeXmlAttribute(paraId)}"${rsid}>` + `${pPr}${body}</w:p>`;
217215
+ }
217216
+ function wrapNoteTrackedInsertion(inner, stamp) {
217217
+ const id = stamp.allocateWId();
217218
+ return `${renderTrackedChangeOpen("ins", id, stamp, true)}${inner}</w:ins>`;
216841
217219
  }
216842
217220
  function noteBodyRuns(paragraph) {
216843
217221
  if (!paragraph.inlines)
@@ -232266,12 +232644,19 @@ function preparePasteFragmentMutation(input) {
232266
232644
  if (!review.ok)
232267
232645
  return rejected18(txId, review.reason);
232268
232646
  const mergedSidecarPerPart = { ...listExtras.perPart, ...imageExtras.value.perPart, ...review.perPart };
232647
+ const pastedNotes = op.fragment.notes ?? [];
232648
+ const noteParagraphs = pastedNotes.flatMap((note) => note.paragraphs);
232649
+ const notesStamp = changeMode === "tracked" && pastedNotes.length > 0 ? pickTrackedStamp(input, trackedFragmentCarrierBudget(noteParagraphs) + noteParagraphs.length) : null;
232650
+ if (changeMode === "tracked" && pastedNotes.length > 0 && !notesStamp) {
232651
+ return rejected18(txId, "no-author-configured");
232652
+ }
232269
232653
  const notes = compilePastedNotesMetadata({
232270
232654
  state,
232271
232655
  blocks: resolvedBlocks,
232272
- notes: op.fragment.notes ?? [],
232656
+ notes: pastedNotes,
232273
232657
  documentRelsBytes: plannedPackagePartBytes(state, mergedSidecarPerPart, DOCUMENT_RELS_PART_URI9),
232274
- contentTypesBytes: plannedPackagePartBytes(state, mergedSidecarPerPart, CONTENT_TYPES_PART_URI11)
232658
+ contentTypesBytes: plannedPackagePartBytes(state, mergedSidecarPerPart, CONTENT_TYPES_PART_URI11),
232659
+ trackedStamp: notesStamp
232275
232660
  });
232276
232661
  if (!notes.ok)
232277
232662
  return rejected18(txId, notes.reason);
@@ -232287,7 +232672,8 @@ function preparePasteFragmentMutation(input) {
232287
232672
  patchMaps: { ...listExtras.patchMaps, ...imageExtras.value.patchMaps, ...review.patchMaps, ...notes.patchMaps },
232288
232673
  createdRefs: review.createdRefs,
232289
232674
  commentIdMap: review.commentIdMap,
232290
- noteIdMap: notes.noteIdMap
232675
+ noteIdMap: notes.noteIdMap,
232676
+ revisionWIds: usedTrackedWIds(notesStamp)
232291
232677
  };
232292
232678
  if (isStoryTarget(pasteTarget)) {
232293
232679
  if (preflight.plan.target.kind !== "story")
@@ -232495,7 +232881,9 @@ function runTrackedWholeStoryReplaceCommit(input, target, rendered, blocks, extr
232495
232881
  const preview = prepare();
232496
232882
  if (!preview)
232497
232883
  return rejected18(txId, "paste-tracked-structural-unsupported");
232498
- const revisionWIds = planRevisionWIdBlock(state, preview.wordIds.length);
232884
+ const revisionWIds = planRevisionWIdBlock(state, preview.wordIds.length, {
232885
+ afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds)
232886
+ });
232499
232887
  const replacement = prepare(revisionWIds);
232500
232888
  if (!replacement)
232501
232889
  return rejected18(txId, "paste-tracked-structural-unsupported");
@@ -232525,7 +232913,7 @@ function runTrackedWholeStoryReplaceCommit(input, target, rendered, blocks, extr
232525
232913
  extraPatchMaps: extras.patchMaps,
232526
232914
  packagePartUris: inlineExtras.partUris,
232527
232915
  storyWideProjection: true,
232528
- revisionWIds,
232916
+ revisionWIds: [...revisionWIds, ...extras.revisionWIds ?? []],
232529
232917
  indexRefreshScope: { perPart: { [target.partUri]: {
232530
232918
  beforeWindow: { startByte: target.byteStart, endByte: target.byteEnd },
232531
232919
  kind: "wide-rewrite",
@@ -232611,7 +232999,7 @@ function runParagraphBlockPasteCommit(input, target, blocks, extras, inlineExtra
232611
232999
  return rejected18(txId, "paste-target-unsupported");
232612
233000
  const changeMode = op.changeMode ?? "direct";
232613
233001
  const anchorXml = TD64.decode(bytes.subarray(anchor.byteStart, anchor.byteEnd));
232614
- const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + (target.placement === "replace" ? trackedBlockDeletionCarrierBudget(anchorXml) : 2)) : null;
233002
+ const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + (target.placement === "replace" ? trackedBlockDeletionCarrierBudget(anchorXml) : 2), { afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds) }) : null;
232615
233003
  if (changeMode === "tracked" && !trackedStamp)
232616
233004
  return rejected18(txId, "no-author-configured");
232617
233005
  const trackedDeletedAnchor = trackedStamp && target.placement === "replace" ? renderTrackedBlockDeletion({
@@ -232763,7 +233151,7 @@ function runParagraphBlockPasteCommit(input, target, blocks, extras, inlineExtra
232763
233151
  extraPatchMaps: { ...extras.patchMaps, ...commentCascade.patchMaps, ...resourceCleanup.patchMaps },
232764
233152
  packagePartUris: [...inlineExtras.partUris, ...resourceCleanup.packagePartUris],
232765
233153
  storyWideProjection: target.placement === "replace" || blocks.some((block) => block.kind === "table"),
232766
- revisionWIds: usedTrackedWIds(trackedStamp),
233154
+ revisionWIds: usedTrackedWIds(trackedStamp, extras.revisionWIds),
232767
233155
  ...target.placement === "replace" ? {
232768
233156
  indexRefreshScope: {
232769
233157
  perPart: {
@@ -232788,7 +233176,7 @@ function runWholeTableReplaceCommit(input, target, blocks, extras, inlineExtras)
232788
233176
  return rejected18(txId, "paste-target-unsupported");
232789
233177
  }
232790
233178
  const replacedParagraphs = index.entries.filter((entry) => entry.byteStart >= target.byteStart && entry.byteEnd <= target.byteEnd);
232791
- const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + table.rows.length + 2) : null;
233179
+ const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + table.rows.length + 2, { afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds) }) : null;
232792
233180
  if (changeMode === "tracked" && !trackedStamp)
232793
233181
  return rejected18(txId, "no-author-configured");
232794
233182
  const replacedXml = TD64.decode(bytes.subarray(target.byteStart, target.byteEnd));
@@ -232893,7 +233281,7 @@ function runWholeTableReplaceCommit(input, target, blocks, extras, inlineExtras)
232893
233281
  extraPatchMaps: { ...extras.patchMaps, ...commentCascade.patchMaps },
232894
233282
  packagePartUris: inlineExtras.partUris,
232895
233283
  storyWideProjection: true,
232896
- revisionWIds: usedTrackedWIds(trackedStamp),
233284
+ revisionWIds: usedTrackedWIds(trackedStamp, extras.revisionWIds),
232897
233285
  indexRefreshScope: {
232898
233286
  perPart: {
232899
233287
  [target.partUri]: {
@@ -232928,7 +233316,7 @@ function runCrossParagraphTrackedPasteCommit(input, target, blocks, extras, inli
232928
233316
  if (!rangeIntegrity.ok)
232929
233317
  return rejected18(txId, "invalid-range");
232930
233318
  const coveredEntries = index.entries.filter((entry) => entry.ordinal >= start.ordinal && entry.ordinal <= end.ordinal);
232931
- const stamp = pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + coveredEntries.length * 3 + 4);
233319
+ const stamp = pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + coveredEntries.length * 3 + 4, { afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds) });
232932
233320
  if (!stamp)
232933
233321
  return rejected18(txId, "no-author-configured");
232934
233322
  const patches = [];
@@ -233030,7 +233418,7 @@ function runCrossParagraphTrackedPasteCommit(input, target, blocks, extras, inli
233030
233418
  extraPatchMaps: extras.patchMaps,
233031
233419
  packagePartUris: inlineExtras.partUris,
233032
233420
  storyWideProjection: true,
233033
- revisionWIds: usedTrackedWIds(stamp),
233421
+ revisionWIds: usedTrackedWIds(stamp, extras.revisionWIds),
233034
233422
  indexRefreshScope: {
233035
233423
  perPart: {
233036
233424
  [target.partUri]: {
@@ -233070,7 +233458,9 @@ function runCrossParagraphPasteCommit(input, target, blocks, extras, inlineExtra
233070
233458
  return rejected18(txId, "invalid-range");
233071
233459
  const endpointXml = TD64.decode(bytes.subarray(start.byteStart, start.byteEnd)) + TD64.decode(bytes.subarray(end.byteStart, end.byteEnd));
233072
233460
  const wrapperCount = [...endpointXml.matchAll(/<(?:[\w.-]+:)?(?:ins|del|moveFrom|moveTo)\b/g)].length;
233073
- const plannedWIds = planRevisionWIdBlock(state, Math.max(1, wrapperCount * 2 + 4));
233461
+ const plannedWIds = planRevisionWIdBlock(state, Math.max(1, wrapperCount * 2 + 4), {
233462
+ afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds)
233463
+ });
233074
233464
  let usedWIds = 0;
233075
233465
  let splicePlan;
233076
233466
  let mintSalt = 0;
@@ -233147,7 +233537,7 @@ function runCrossParagraphPasteCommit(input, target, blocks, extras, inlineExtra
233147
233537
  packagePartUris: inlineExtras.partUris,
233148
233538
  storyWideProjection: true,
233149
233539
  indexRefreshScope: contribution.indexRefreshScope,
233150
- revisionWIds: plannedWIds.slice(0, usedWIds)
233540
+ revisionWIds: [...plannedWIds.slice(0, usedWIds), ...extras.revisionWIds ?? []]
233151
233541
  });
233152
233542
  }
233153
233543
  function collectRemovedBookmarkRefs(ownerXml, removedXml, storyId) {
@@ -233223,7 +233613,7 @@ function tryRunTableRowPasteCommit(input, pasteTarget, pastedBlocks, additionalP
233223
233613
  scopeHintOpenTag: matchParagraphOpenTag(targetParagraphXml) ?? ""
233224
233614
  });
233225
233615
  const createdParagraphIds = [];
233226
- const trackedStamp = (input.op.changeMode ?? "direct") === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(pastedBlocks) + 2) : null;
233616
+ const trackedStamp = (input.op.changeMode ?? "direct") === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(pastedBlocks) + 2, { afterMax: afterMaxFromPlannedRevisionWIds(additionalPatches.revisionWIds) }) : null;
233227
233617
  if ((input.op.changeMode ?? "direct") === "tracked" && !trackedStamp) {
233228
233618
  return rejected18(transactionId, "no-author-configured");
233229
233619
  }
@@ -233310,7 +233700,7 @@ function tryRunTableRowPasteCommit(input, pasteTarget, pastedBlocks, additionalP
233310
233700
  provisionalToFinal: Object.fromEntries(createdParagraphIds.map((paragraphId, index) => [`${transactionId}:paste:${index}`, paragraphId])),
233311
233701
  extraPatchMaps: additionalPatches.patchMaps,
233312
233702
  storyWideProjection: true,
233313
- revisionWIds: usedTrackedWIds(trackedStamp)
233703
+ revisionWIds: usedTrackedWIds(trackedStamp, additionalPatches.revisionWIds)
233314
233704
  });
233315
233705
  }
233316
233706
  function findContainingTableCell(tableCatalog, targetParagraph) {
@@ -233710,7 +234100,7 @@ function runPasteSpliceCommit(input, target, resolvedBlocks, listExtras, imageEx
233710
234100
  const changeMode = input.op.changeMode ?? "direct";
233711
234101
  const wrapAllTracked = changeMode === "tracked";
233712
234102
  const fragmentTracked = fragmentHasTrackedInsertion(resolvedBlocks);
233713
- const trackedStamp = wrapAllTracked || fragmentTracked ? pickTrackedStamp(input, trackedFragmentCarrierBudget(resolvedBlocks) + 4) : null;
234103
+ const trackedStamp = wrapAllTracked || fragmentTracked ? pickTrackedStamp(input, trackedFragmentCarrierBudget(resolvedBlocks) + 4, { afterMax: afterMaxFromPlannedRevisionWIds(listExtras.revisionWIds) }) : null;
233714
234104
  if ((wrapAllTracked || fragmentTracked) && !trackedStamp)
233715
234105
  return rejected18(txId, "no-author-configured");
233716
234106
  let mintSalt = 0;
@@ -233840,7 +234230,7 @@ function runPasteSpliceCommit(input, target, resolvedBlocks, listExtras, imageEx
233840
234230
  extraPatchMaps: listExtras.patchMaps,
233841
234231
  storyWideProjection: resolvedBlocks.some((block) => block.kind === "table"),
233842
234232
  packagePartUris: imageExtras.partUris,
233843
- revisionWIds: usedTrackedWIds(trackedStamp),
234233
+ revisionWIds: usedTrackedWIds(trackedStamp, listExtras.revisionWIds),
233844
234234
  indexRefreshScope: {
233845
234235
  perPart: {
233846
234236
  [partUri]: {
@@ -234860,11 +235250,11 @@ function injectTrackedMarkIntoLastParagraph(xml, stamp, kind) {
234860
235250
  const replacement = injectTrackedParagraphMarkIntoXml(match[0], stamp, kind);
234861
235251
  return xml.slice(0, match.index) + replacement + xml.slice(match.index + match[0].length);
234862
235252
  }
234863
- function pickTrackedStamp(input, capacity) {
235253
+ function pickTrackedStamp(input, capacity, options) {
234864
235254
  const author = input.authorOverride ?? input.author;
234865
235255
  if (!author)
234866
235256
  return null;
234867
- const plannedWIds = planRevisionWIdBlock(input.state, Math.max(1, capacity));
235257
+ const plannedWIds = planRevisionWIdBlock(input.state, Math.max(1, capacity), options);
234868
235258
  const stamp = {
234869
235259
  rsid: allocateTxRsid(input.txId),
234870
235260
  plannedWIds,
@@ -234881,8 +235271,17 @@ function pickTrackedStamp(input, capacity) {
234881
235271
  };
234882
235272
  return stamp;
234883
235273
  }
234884
- function usedTrackedWIds(stamp) {
234885
- return stamp ? stamp.plannedWIds.slice(0, stamp.usedWIdCount) : undefined;
235274
+ function afterMaxFromPlannedRevisionWIds(revisionWIds) {
235275
+ if (!revisionWIds || revisionWIds.length === 0)
235276
+ return;
235277
+ return Math.max(...revisionWIds);
235278
+ }
235279
+ function usedTrackedWIds(stamp, extra) {
235280
+ const ids = [
235281
+ ...stamp ? stamp.plannedWIds.slice(0, stamp.usedWIdCount) : [],
235282
+ ...extra ?? []
235283
+ ];
235284
+ return ids.length > 0 ? ids : undefined;
234886
235285
  }
234887
235286
  function replayTrackedStamp(source) {
234888
235287
  const replay = {
@@ -357366,7 +357765,14 @@ function logicalEntryMatchesStoryFilter(entry, filter) {
357366
357765
  return isBodyStoryId2(entry.storyId);
357367
357766
  if (!filter.storyIds?.has(entry.storyId))
357368
357767
  return false;
357369
- return !filter.noteId || entry.noteId === filter.noteId;
357768
+ if (!filter.noteId)
357769
+ return true;
357770
+ return ownedLogicalNoteIds(entry).includes(filter.noteId);
357771
+ }
357772
+ function ownedLogicalNoteIds(entry) {
357773
+ if (entry.noteIds && entry.noteIds.length > 0)
357774
+ return entry.noteIds;
357775
+ return entry.noteId != null ? [entry.noteId] : [];
357370
357776
  }
357371
357777
  function isVisibleLogicalChange(entry) {
357372
357778
  return isVisibleLogicalTrackedChange(entry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/mcp",
3
- "version": "1.2.1-next.1",
3
+ "version": "1.2.1-next.2",
4
4
  "description": "Model Context Protocol server for SuperDoc, giving an AI agent tools to read and edit .docx files.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -24,13 +24,13 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@modelcontextprotocol/sdk": "^1.26.0",
27
- "@superdoc/sdk": "2.15.1-next.1",
27
+ "@superdoc/sdk": "2.15.1-next.2",
28
28
  "debug": "^4.4.3",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {
32
- "superdoc": "2.18.1-next.1",
33
- "@superdoc/docx-engine": "0.17.1-next.1",
32
+ "superdoc": "2.18.1-next.2",
33
+ "@superdoc/docx-engine": "0.17.1-next.2",
34
34
  "@superdoc/document-api": "0.1.0-alpha.0",
35
35
  "@types/bun": "^1.3.8",
36
36
  "@types/node": "22.19.2",