@superdoc/mcp 1.4.0-next.8 → 1.4.0-next.9

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 +466 -41
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -31330,7 +31330,7 @@ var init_host = __esm(() => {
31330
31330
  });
31331
31331
 
31332
31332
  // ../../packages/sdk/langs/node/src/runtime/sdk-version.generated.ts
31333
- var SDK_VERSION = "2.17.0-next.8";
31333
+ var SDK_VERSION = "2.17.0-next.9";
31334
31334
 
31335
31335
  // ../../packages/sdk/langs/node/src/runtime/embedded-platform.ts
31336
31336
  import { accessSync, chmodSync, constants, existsSync, statSync } from "node:fs";
@@ -135279,7 +135279,28 @@ function control(xml, element) {
135279
135279
  const locks = children.filter((child) => is(child, "lock"));
135280
135280
  const deletable = locks.every((lock) => attr(lock, "val") === "unlocked" || attr(lock, "val") === "contentLocked");
135281
135281
  const supportedProperties = children.every((child) => child.tag.uri === W3 ? ["id", "tag", "alias", "text", "richText", "lock", "rPr", "placeholder", "showingPlcHdr", "temporary"].includes(child.tag.local) : child.tag.uri === "http://schemas.microsoft.com/office/word/2012/wordml" && child.tag.local === "appearance");
135282
- return { id, key: `sdt:${attr(key2, "val")}`, xml: fragment2(xml, element), deletable: deletable && supportedProperties };
135282
+ const contentXml = xml.slice(content[0].start, content[0].end);
135283
+ const contentOpenEnd = contentXml.indexOf(">") + 1;
135284
+ const contentCloseStart = contentXml.lastIndexOf("</");
135285
+ const contentRuns = contentCloseStart < contentOpenEnd ? "" : contentXml.slice(contentOpenEnd, contentCloseStart);
135286
+ const offset = content[0].start - element.start;
135287
+ const itemXml = fragment2(xml, element);
135288
+ const head = itemXml.slice(0, offset + contentOpenEnd);
135289
+ const tail = itemXml.slice(offset + Math.max(contentOpenEnd, contentCloseStart));
135290
+ const unlocked = locks.every((lock) => attr(lock, "val") === "unlocked");
135291
+ const editable = locks.every((lock) => attr(lock, "val") === "unlocked" || attr(lock, "val") === "sdtLocked");
135292
+ return {
135293
+ id,
135294
+ key: `sdt:${attr(key2, "val")}`,
135295
+ xml: itemXml,
135296
+ contentXml: contentRuns,
135297
+ head,
135298
+ tail,
135299
+ deletable: deletable && supportedProperties,
135300
+ insertable: unlocked && supportedProperties,
135301
+ editable: editable && supportedProperties,
135302
+ supportedProperties
135303
+ };
135283
135304
  }
135284
135305
  function inspectInlineControlRemoval(beforeXml, afterXml, namespaces = {}) {
135285
135306
  const before = elements(beforeXml, namespaces);
@@ -135366,6 +135387,137 @@ function bodyOperationsCoverInlineControlRemoval(base2, target, operations, matc
135366
135387
  }
135367
135388
  return removed.size === changes.length && changes.every((entry) => entry.baseId !== null && removed.has(entry.baseId));
135368
135389
  }
135390
+ function inspectInlineControlReplay(beforeXml, afterXml, namespaces = {}) {
135391
+ const before = elements(beforeXml, namespaces);
135392
+ const after = elements(afterXml, namespaces);
135393
+ if (!before || !after)
135394
+ return null;
135395
+ const xmlns = "http://www.w3.org/2000/xmlns/";
135396
+ const attributes = (root, allowed) => Object.values(root.tag.attributes).every((attribute) => attribute.uri === xmlns || allowed(attribute.uri, attribute.local));
135397
+ const runProperties = (root) => is(root, "rPr") && attributes(root, () => false) && root.children.every((child) => child.tag.uri === W3 && RUN_PROPERTIES.has(child.tag.local) && !child.children.length && attributes(child, (uri) => uri === W3));
135398
+ const simpleRun = (root) => safeRun(root) && attributes(root, (uri, local) => uri === W3 && ["rsidR", "rsidRPr", "rsidDel"].includes(local)) && root.children.filter((child) => is(child, "t")).length === 1 && root.children.every((child) => runProperties(child) || is(child, "t") && !child.tag.isSelfClosing && attributes(child, (uri, local) => uri === "http://www.w3.org/XML/1998/namespace" && local === "space"));
135399
+ const safeControlProperties = (root) => {
135400
+ if (!attributes(root, (uri, local) => uri === W3 && ["rsidR", "rsidRPr", "rsidDel"].includes(local)))
135401
+ return false;
135402
+ const properties = root.children.find((child) => is(child, "sdtPr"));
135403
+ const content = root.children.find((child) => is(child, "sdtContent"));
135404
+ if (!attributes(properties, () => false) || !attributes(content, () => false) || content.tag.isSelfClosing)
135405
+ return false;
135406
+ if (properties.children.filter((child) => is(child, "text") || is(child, "richText")).length > 1)
135407
+ return false;
135408
+ const seen = new Set;
135409
+ for (const child of properties.children) {
135410
+ if (seen.has(child.tag.name))
135411
+ return false;
135412
+ seen.add(child.tag.name);
135413
+ if (!attributes(child, (uri) => uri === W3 || uri === child.tag.uri))
135414
+ return false;
135415
+ if (is(child, "rPr")) {
135416
+ if (!runProperties(child))
135417
+ return false;
135418
+ } else if (is(child, "placeholder")) {
135419
+ if (child.children.some((item) => !is(item, "docPart") || item.children.length || !attributes(item, (uri) => uri === W3)))
135420
+ return false;
135421
+ } else if (child.children.length)
135422
+ return false;
135423
+ }
135424
+ return root.children.filter((child) => is(child, "sdtEndPr")).every((child) => attributes(child, () => false) && child.children.every(runProperties));
135425
+ };
135426
+ const inspect = (xml, roots) => {
135427
+ const result = [];
135428
+ for (const [index, root] of roots.entries()) {
135429
+ if (simpleRun(root))
135430
+ continue;
135431
+ const item = control(xml, root);
135432
+ if (!item || !item.supportedProperties || !safeControlProperties(root) || !root.children.find((child) => is(child, "sdtContent"))?.children.every(simpleRun) || result.some((other) => other.id === item.id || other.key === item.key))
135433
+ return null;
135434
+ result.push({ ...item, index });
135435
+ }
135436
+ return result;
135437
+ };
135438
+ const previous = inspect(beforeXml, before);
135439
+ const next = inspect(afterXml, after);
135440
+ if (!previous || !next || !previous.length && !next.length)
135441
+ return null;
135442
+ const normalized = (xml) => signature(`<scope ${Object.entries(namespaces).filter(([prefix]) => prefix && prefix !== "xml").map(([prefix, uri]) => `xmlns:${prefix}=${JSON.stringify(uri)}`).join(" ")}>${xml}</scope>`);
135443
+ const anchors = [];
135444
+ for (const old of previous) {
135445
+ const current = next.find((item) => item.id === old.id);
135446
+ if (!current || old.key !== current.key || normalized(old.head + old.tail) !== normalized(current.head + current.tail))
135447
+ return null;
135448
+ const changed = normalized(old.xml) !== normalized(current.xml);
135449
+ if (changed && !old.editable)
135450
+ return null;
135451
+ anchors.push({ beforeIndex: old.index, afterIndex: current.index, before: old, after: current, changed });
135452
+ }
135453
+ if (anchors.some((anchor, index) => index > 0 && anchor.afterIndex <= anchors[index - 1].afterIndex))
135454
+ return null;
135455
+ const inserted = next.filter((item) => !previous.some((old) => old.id === item.id));
135456
+ if (inserted.some((item) => !item.insertable))
135457
+ return null;
135458
+ return {
135459
+ anchors,
135460
+ inserted,
135461
+ updated: anchors.filter((anchor) => anchor.changed),
135462
+ beforeRoots: before.map((root) => fragment2(beforeXml, root)),
135463
+ afterRoots: after.map((root) => fragment2(afterXml, root))
135464
+ };
135465
+ }
135466
+ function bodyOperationsCoverInlineControlReplay(base2, target, operations, match) {
135467
+ const family = match.families.find((item) => item.family === "content-controls");
135468
+ const changes = family?.entityMatch?.entries.filter((entry) => entry.disposition !== "matched" || entry.changed || entry.moved);
135469
+ if (!changes?.length || family?.hasAmbiguity || changes.some((entry) => entry.disposition !== "inserted" && entry.disposition !== "matched" || entry.moved))
135470
+ return false;
135471
+ const counts = (snapshot2) => {
135472
+ const result = new Map;
135473
+ const visit = (root) => {
135474
+ if (is(root, "sdtPr"))
135475
+ for (const child of root.children.filter((item) => is(item, "id"))) {
135476
+ const id = numericIdentity(attr(child, "val"));
135477
+ if (id !== null)
135478
+ result.set(id, (result.get(id) ?? 0) + 1);
135479
+ }
135480
+ root.children.forEach(visit);
135481
+ };
135482
+ for (const entry of [...snapshot2.body, ...(snapshot2.stories ?? []).flatMap((story) => story.entries)]) {
135483
+ const roots = elements(entry.runXml ?? "", {}, true);
135484
+ if (!roots)
135485
+ return null;
135486
+ roots.forEach(visit);
135487
+ }
135488
+ return result;
135489
+ };
135490
+ const oldCounts = counts(base2), newCounts = counts(target);
135491
+ if (!oldCounts || !newCounts)
135492
+ return false;
135493
+ const covered = new Map;
135494
+ for (const operation of operations) {
135495
+ if (operation.kind !== "edit" && operation.kind !== "formatEdit")
135496
+ continue;
135497
+ const previous = base2.body.filter((entry) => entry.blockId === operation.blockId);
135498
+ const next = target.body.filter((entry) => entry.blockId === operation.blockId);
135499
+ if (previous.length !== 1 || next.length !== 1)
135500
+ continue;
135501
+ const before = previous[0], after = next[0];
135502
+ const proof = inspectInlineControlReplay(before.runXml ?? "", after.runXml ?? "");
135503
+ if (!proof)
135504
+ continue;
135505
+ if (!/^[0-9A-Fa-f]{8}$/.test(before.blockId) || (before.structuralScope ?? "body") !== "body" || (after.structuralScope ?? "body") !== "body" || before.trackedChangeAnnotationKeys?.length || before.structuralChangeAnnotationKeys?.length || after.trackedChangeAnnotationKeys?.length || after.structuralChangeAnnotationKeys?.length || operation.targetRunXml !== after.runXml)
135506
+ return false;
135507
+ for (const item of proof.inserted) {
135508
+ if (covered.has(item.key) || oldCounts.has(item.id) || newCounts.get(item.id) !== 1)
135509
+ return false;
135510
+ covered.set(item.key, "inserted");
135511
+ }
135512
+ for (const anchor of proof.updated) {
135513
+ const item = anchor.after;
135514
+ if (covered.has(item.key) || oldCounts.get(item.id) !== 1 || newCounts.get(item.id) !== 1)
135515
+ return false;
135516
+ covered.set(item.key, "matched");
135517
+ }
135518
+ }
135519
+ return covered.size === changes.length && changes.every((entry) => entry.targetId !== null && covered.get(entry.targetId) === entry.disposition);
135520
+ }
135369
135521
  var import_saxes3, W3 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main", attr = (element, name) => Object.values(element.tag.attributes).find((attribute) => attribute.uri === W3 && attribute.local === name)?.value, is = (element, name) => element.tag.uri === W3 && element.tag.local === name, fragment2 = (xml, element) => xml.slice(element.start, element.end), signature = (xml) => canonicalizeXmlForSignature(xml), RUN_PROPERTIES;
135370
135522
  var init_inline_control_removal = __esm(() => {
135371
135523
  init_run_signature();
@@ -136241,7 +136393,7 @@ function parseXmlStartTag(rawTag) {
136241
136393
  return null;
136242
136394
  }
136243
136395
  function validateRunContents(xml, options) {
136244
- if (options.baseRunXml !== undefined && inspectInlineControlRemoval(options.baseRunXml, xml)) {
136396
+ if (options.baseRunXml !== undefined && (inspectInlineControlRemoval(options.baseRunXml, xml) || inspectInlineControlReplay(options.baseRunXml, xml))) {
136245
136397
  return { ok: true };
136246
136398
  }
136247
136399
  const baseHasNoteMarker = options.baseRunXml !== undefined && /<(?:[A-Za-z_][\w.-]*:)?(?:footnoteRef|endnoteRef)\b/.test(options.baseRunXml);
@@ -140072,7 +140224,7 @@ function planCompare(base2, target) {
140072
140224
  const trackedRevisionSetChanged = semanticAnalysis.familyDeltas.find((delta) => delta.family === "tracked-changes")?.detectedChange === true;
140073
140225
  const revisionReplay = deriveCompareBodyOperationsWithRevisionReplay(base2, target, matchResult);
140074
140226
  const bodyOperations = revisionReplay.operations;
140075
- if (bodyOperationsCoverInlineControlRemoval(base2, target, bodyOperations, matchResult)) {
140227
+ if (bodyOperationsCoverInlineControlRemoval(base2, target, bodyOperations, matchResult) || bodyOperationsCoverInlineControlReplay(base2, target, bodyOperations, matchResult)) {
140076
140228
  semanticAnalysis = markFamilySupported(semanticAnalysis, "content-controls");
140077
140229
  }
140078
140230
  const computedStoryOperations = buildStoryOperations(base2, target, semanticAnalysis);
@@ -164393,6 +164545,9 @@ function planDirectInlineEdit(paragraphBytes, startOffset, endOffset, newText, c
164393
164545
  ...enclosingSdts.length > 0 ? { requiresCollaborationSessionSync: true } : {}
164394
164546
  };
164395
164547
  }
164548
+ const sdtProtection = validateSelectedSdtProtection(model.pieces, startOffset, endOffset);
164549
+ if (sdtProtection)
164550
+ return { ok: false, reason: sdtProtection };
164396
164551
  const tocPlan = planTocEntryEdit(paragraphBytes, xml, model, leaves, startOffset, endOffset, newText, inlineContext, authoredTocEntry);
164397
164552
  if (tocPlan)
164398
164553
  return tocPlan;
@@ -164445,7 +164600,13 @@ function planDirectInlineEdits(paragraphBytes, edits, inheritedNamespaceBindings
164445
164600
  return { ok: false, reason: "invalid-range" };
164446
164601
  }
164447
164602
  }
164448
- const batchLeaves = parseParagraphInlineMap(TD22.decode(paragraphBytes), inheritedNamespaceBindings).leaves();
164603
+ const batchModel = parseParagraphInlineMap(TD22.decode(paragraphBytes), inheritedNamespaceBindings);
164604
+ for (const edit of ordered) {
164605
+ const blocked = validateSelectedSdtProtection(batchModel.pieces, edit.startOffset, edit.endOffset);
164606
+ if (blocked)
164607
+ return { ok: false, reason: blocked };
164608
+ }
164609
+ const batchLeaves = batchModel.leaves();
164449
164610
  if (batchLeaves.some((piece) => piece.kind === "noteReference" && !piece.deleted && ordered.some((edit) => edit.startOffset < piece.visibleEnd && edit.endOffset > piece.visibleStart))) {
164450
164611
  return { ok: false, reason: "unsupported-context" };
164451
164612
  }
@@ -165022,6 +165183,27 @@ function hasNonWordMarkerAtCollapsedMoveBoundary(pieces, at, coordinateSpace = "
165022
165183
  function firstNonFieldRangeStartMarker(markers) {
165023
165184
  return markers.find((piece) => piece.role === "range-start" && !isFieldMarker(piece.markerKind)) ?? null;
165024
165185
  }
165186
+ function validateSelectedSdtProtection(pieces, start, end) {
165187
+ if (start === end)
165188
+ return null;
165189
+ for (const piece of pieces) {
165190
+ if (piece.kind !== "container" && piece.kind !== "tracked")
165191
+ continue;
165192
+ if (piece.kind === "container" && piece.containerKind === "sdt" && rangeCrossesContainer(piece, start, end)) {
165193
+ const policy = classifySdtMutationPolicyInternal(piece, true);
165194
+ if (!policy.ok)
165195
+ return policy.reason;
165196
+ const contentOnly = containerContainsRange(piece, start, end);
165197
+ const wholeWrapper = start <= piece.visibleStart && piece.visibleEnd <= end;
165198
+ if (contentOnly && !policy.contentEditable || !contentOnly && !policy.wrapperDeletable || !contentOnly && !wholeWrapper && !policy.contentEditable)
165199
+ return "text-range-in-sdt";
165200
+ }
165201
+ const blocked = validateSelectedSdtProtection(piece.children, start, end);
165202
+ if (blocked)
165203
+ return blocked;
165204
+ }
165205
+ return null;
165206
+ }
165025
165207
  function resolveEditScope(pieces, start, end, collapsedInlineAffinity, collapsedOutsideContentControl = false) {
165026
165208
  let current = pieces;
165027
165209
  let descended = false;
@@ -165076,11 +165258,11 @@ function planCrossBoundarySdts(pieces, start, end) {
165076
165258
  const policy = classifySdtMutationPolicy(piece);
165077
165259
  if (!policy.ok)
165078
165260
  return policy;
165261
+ if (!policy.wrapperDeletable)
165262
+ return { ok: false, reason: "text-range-in-sdt" };
165079
165263
  requiresCollaborationSessionSync = true;
165080
165264
  const wholeWrapper = start <= piece.visibleStart && piece.visibleEnd <= end;
165081
165265
  if (wholeWrapper) {
165082
- if (!policy.wrapperDeletable)
165083
- return { ok: false, reason: "text-range-in-sdt" };
165084
165266
  const blocked = findBlockingSdtChild(piece.children);
165085
165267
  if (blocked)
165086
165268
  return { ok: false, reason: blocked };
@@ -192934,7 +193116,7 @@ function tryRangeReplacementAcrossTokens(input, allTokens, touched) {
192934
193116
  if (!firstSelected)
192935
193117
  return null;
192936
193118
  const touchedInsertions = touched.filter((entry) => entry.token.kind === "ins");
192937
- if (touchedInsertions.some(({ token }) => token.innerRuns && token.innerRuns.length > 1 || token.nestedDeletions && token.nestedDeletions.length > 0)) {
193119
+ if (touchedInsertions.some(({ token }) => token.innerRuns && token.innerRuns.length > 1 && (token !== firstSelected.token || token.commentedBoundary != null) || token.nestedDeletions && token.nestedDeletions.length > 0)) {
192938
193120
  return null;
192939
193121
  }
192940
193122
  const result = input.changeMode === "tracked" ? authorTrackedPureDelete(input, allTokens, touched) : authorDirectPureDelete(input, allTokens, touched);
@@ -193038,7 +193220,7 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193038
193220
  continue;
193039
193221
  }
193040
193222
  const head2 = token.text.slice(0, localStart2);
193041
- const removed2 = token.text.slice(localStart2, localEnd2);
193223
+ const removed = token.text.slice(localStart2, localEnd2);
193042
193224
  const tail2 = token.text.slice(localEnd2);
193043
193225
  const headXml = head2.length > 0 ? renderPlainRun(head2, token.runOpen, token.runClose, token.rPr) : "";
193044
193226
  const delXml = buildDelWrapperXml({
@@ -193047,7 +193229,7 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193047
193229
  author: currentAuthor,
193048
193230
  date: input.txTimestamp,
193049
193231
  rsidDel: input.rsid,
193050
- inner: { runOpen: token.runOpen, runClose: token.runClose, rPr: token.rPr, text: removed2 }
193232
+ inner: { runOpen: token.runOpen, runClose: token.runClose, rPr: token.rPr, text: removed }
193051
193233
  });
193052
193234
  const insertXml = !replacementInserted && input.newText.length > 0 ? buildInsWrapperXml({
193053
193235
  nsPrefix,
@@ -193080,7 +193262,6 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193080
193262
  continue;
193081
193263
  }
193082
193264
  const head = token.inner.text.slice(0, localStart);
193083
- const removed = token.inner.text.slice(localStart, localEnd);
193084
193265
  const tail = token.inner.text.slice(localEnd);
193085
193266
  const classification = token._classification;
193086
193267
  removedAnything = true;
@@ -193109,8 +193290,13 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193109
193290
  updatedRsids.push(rsid);
193110
193291
  continue;
193111
193292
  }
193112
- const nestedHead = renderPlainRun(head, token.inner.runOpen, token.inner.runClose, token.inner.rPr);
193113
- const nestedTail = renderPlainRun(tail, token.inner.runOpen, token.inner.runClose, token.inner.rPr);
193293
+ const runs = token.innerRuns ?? [token.inner];
193294
+ const headRuns = sliceWrappedRuns(runs, 0, localStart);
193295
+ const removedRuns = sliceWrappedRuns(runs, localStart, localEnd);
193296
+ const tailRuns = sliceWrappedRuns(runs, localEnd, token.inner.text.length);
193297
+ const nestedHead = buildInsInnerXmlFromRuns(headRuns);
193298
+ const nestedTail = buildInsInnerXmlFromRuns(tailRuns);
193299
+ const insertMeta = runMetaAtInsertionPoint(token, localStart, true);
193114
193300
  const childIns = !replacementInserted && input.newText.length > 0 && input.rangeStart >= token.visibleStart && input.rangeStart < token.visibleEnd && input.rangeEnd > token.visibleEnd ? buildInsWrapperXml({
193115
193301
  namespaceReferenceOpenTag: token.openTag,
193116
193302
  nsPrefix: token.nsPrefix,
@@ -193119,9 +193305,9 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193119
193305
  date: input.txTimestamp,
193120
193306
  rsidR: input.insRsid,
193121
193307
  inner: {
193122
- runOpen: token.inner.runOpen,
193123
- runClose: token.inner.runClose,
193124
- rPr: token.inner.rPr,
193308
+ runOpen: insertMeta.runOpen,
193309
+ runClose: insertMeta.runClose,
193310
+ rPr: rPrAtInsertionPoint(token, localStart, true),
193125
193311
  text: input.newText
193126
193312
  }
193127
193313
  }) : "";
@@ -193132,10 +193318,8 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193132
193318
  author: currentAuthor,
193133
193319
  date: input.txTimestamp,
193134
193320
  rsidDel: input.rsid,
193135
- inner: { runOpen: token.inner.runOpen, runClose: token.inner.runClose, rPr: token.inner.rPr, text: removed },
193136
- ...token.commentedBoundary ? {
193137
- innerXml: renderDelRun(removed, token.inner.runOpen, token.inner.runClose, token.inner.rPr) + token.commentedBoundary.endXml + token.commentedBoundary.referenceRunXml
193138
- } : {}
193321
+ inner: removedRuns[0],
193322
+ innerXml: buildDelInnerXmlFromRuns(removedRuns) + (token.commentedBoundary?.endXml ?? "") + (token.commentedBoundary?.referenceRunXml ?? "")
193139
193323
  });
193140
193324
  if (token.commentedBoundary) {
193141
193325
  if (!childIns || tail.length > 0)
@@ -193156,7 +193340,8 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
193156
193340
  author: token.author,
193157
193341
  date: token.date,
193158
193342
  rsidR: token.rsidR,
193159
- inner: { ...token.inner, text: head }
193343
+ inner: headRuns[0],
193344
+ innerXml: nestedHead
193160
193345
  }) : "";
193161
193346
  const tailIns = buildInsWrapperXml({
193162
193347
  openTag: token.openTag,
@@ -193523,6 +193708,20 @@ function foldInsideOwnInsertion(input, allTokens, parent) {
193523
193708
  const rebuilt = rebuildParagraph(allTokens, parent, newInsXml);
193524
193709
  return rebuilt.ok ? markTextInsertionRefined(markExistingRevisionUpdated(rebuilt, input, parent), input, parent, false) : rebuilt;
193525
193710
  }
193711
+ function sliceWrappedRuns(runs, start, end) {
193712
+ const result = [];
193713
+ let offset = 0;
193714
+ for (const run of runs) {
193715
+ const runEnd = offset + run.text.length;
193716
+ if (start < runEnd && end > offset) {
193717
+ result.push({ ...run, text: run.text.slice(Math.max(0, start - offset), Math.min(run.text.length, end - offset)) });
193718
+ }
193719
+ offset = runEnd;
193720
+ if (offset >= end)
193721
+ break;
193722
+ }
193723
+ return result;
193724
+ }
193526
193725
  function flattenWrappedRuns(runs) {
193527
193726
  const out = [];
193528
193727
  for (const run of runs) {
@@ -193743,8 +193942,10 @@ function splitInsertionAroundChildChange(input, allTokens, parent) {
193743
193942
  detail: "overlap compiler: edit range straddles an existing nested tracked deletion."
193744
193943
  };
193745
193944
  }
193746
- const headOwnedNested = parent.nestedDeletions?.filter((n) => n.localOffset <= localStart) ?? [];
193747
- const tailOwnedNested = parent.nestedDeletions?.filter((n) => n.localOffset > localStart) ?? [];
193945
+ const precedesBoundaryDeletion = input.changeMode === "tracked" && input.newText.length > 0 && input.rangeStart === input.rangeEnd && (input.coordinateSpace ?? "visible") === "visible" && input.collapsedInlineAffinity === "upstream";
193946
+ const belongsToHead = (n) => n.localOffset < localStart || n.localOffset === localStart && !precedesBoundaryDeletion;
193947
+ const headOwnedNested = nestedDeletions.filter(belongsToHead);
193948
+ const tailOwnedNested = nestedDeletions.filter((n) => !belongsToHead(n));
193748
193949
  const flat = flattenWrappedRuns(parent.innerRuns ?? [parent.inner]);
193749
193950
  const headRuns = coalesceWrappedRuns(flat.slice(0, localStart));
193750
193951
  const removedRuns = coalesceWrappedRuns(flat.slice(localStart, localEnd));
@@ -193874,7 +194075,7 @@ function splitInsertionAroundChildChange(input, allTokens, parent) {
193874
194075
  ok: true,
193875
194076
  xml,
193876
194077
  emittedRsids,
193877
- ...removedText.length > 0 || tailParentWId !== parent.wId || liftComment ? { requiresCollaborationSessionSync: true } : {}
194078
+ ...removedText.length > 0 || tailParentWId !== parent.wId || liftComment || precedesBoundaryDeletion && tailOwnedNested.some((n) => n.localOffset === localStart) ? { requiresCollaborationSessionSync: true } : {}
193878
194079
  };
193879
194080
  }
193880
194081
  function insertAtForeignDeletion(input, allTokens, parent, sameUser, side = "after") {
@@ -198958,6 +199159,15 @@ function rewriteTrackedPartialSdtSelection(paragraphXml, startOffset, endOffset,
198958
199159
  const sdts = [...intersectingSdts].sort((left, right) => left.visibleStart - right.visibleStart);
198959
199160
  if (sdts.some((sdt, index) => index > 0 && sdt.visibleStart < sdts[index - 1].visibleEnd))
198960
199161
  return null;
199162
+ for (const sdt of sdts) {
199163
+ if (sdt.visibleStart <= startOffset && endOffset <= sdt.visibleEnd)
199164
+ continue;
199165
+ const policy = classifySdtMutationPolicy(sdt);
199166
+ if (!policy.ok)
199167
+ return policy;
199168
+ if (!policy.wrapperDeletable)
199169
+ return { ok: false, reason: "text-range-in-sdt" };
199170
+ }
198961
199171
  const segments = [];
198962
199172
  let cursor = startOffset;
198963
199173
  for (const sdt of sdts) {
@@ -216135,7 +216345,10 @@ function runParagraphRangeCollapseCommit(input) {
216135
216345
  if (startOffsetMap.charToByte(op.on.startOffset) !== op.on.startByte || endOffsetMap.charToByte(op.on.endOffset) !== op.on.endByte) {
216136
216346
  return rejected8(input.txId, "base-revision-drift");
216137
216347
  }
216138
- const structuralFailure = validateRangeCollapseStructure(bytes, index.entries, start, end, changeMode, op.startObjectBoundary, op.endObjectBoundary, { allowSectionBoundary: changeMode === "direct" && op.replacementText === undefined && !deleteFullBlocks });
216348
+ const structuralFailure = validateRangeCollapseStructure(bytes, index.entries, start, end, changeMode, op.startObjectBoundary, op.endObjectBoundary, {
216349
+ allowSectionBoundary: changeMode === "direct" && op.replacementText === undefined && !deleteFullBlocks,
216350
+ inlineRange: { startOffset: op.on.startOffset, endOffset: op.on.endOffset, deleteFullBlocks: op.deleteFinalParagraphMark === true }
216351
+ });
216139
216352
  if (structuralFailure)
216140
216353
  return rejected8(input.txId, structuralFailure);
216141
216354
  let replacementBlockId;
@@ -216192,7 +216405,7 @@ function runParagraphRangeCollapseCommit(input) {
216192
216405
  op = expanded.op;
216193
216406
  start = expanded.start;
216194
216407
  end = expanded.end;
216195
- const expandedStructure = validateRangeCollapseStructure(bytes, index.entries, start, end, changeMode, undefined, undefined, { allowSectionBoundary: true });
216408
+ const expandedStructure = validateRangeCollapseStructure(bytes, index.entries, start, end, changeMode, undefined, undefined, { allowSectionBoundary: true, inlineRange: { startOffset: op.on.startOffset, endOffset: op.on.endOffset } });
216196
216409
  if (expandedStructure)
216197
216410
  return rejected8(input.txId, expandedStructure);
216198
216411
  rangeIntegrity = validateParagraphRangeIntegrity({
@@ -217942,6 +218155,11 @@ function validateRangeCollapseStructure(bytes, entries, start, end, changeMode,
217942
218155
  if (!options.structuralMap && directBodyRangeIsStructurallyPlain(bytes, entries, start, end, changeMode)) {
217943
218156
  return null;
217944
218157
  }
218158
+ if (options.inlineRange) {
218159
+ const failure2 = validateRangeInlineSdtProtection(bytes, entries, start, end, options.inlineRange);
218160
+ if (failure2)
218161
+ return failure2;
218162
+ }
217945
218163
  if (!options.structuralMap)
217946
218164
  recordSDTxPerf("rangeCollapseStructuralFullScans", 1);
217947
218165
  const map3 = options.structuralMap ?? buildStructuralBoundaryMap(bytes);
@@ -218013,6 +218231,46 @@ function validateRangeCollapseStructure(bytes, entries, start, end, changeMode,
218013
218231
  }
218014
218232
  return null;
218015
218233
  }
218234
+ function validateRangeInlineSdtProtection(bytes, entries, start, end, range) {
218235
+ for (const entry of entries) {
218236
+ if (entry.ordinal < start.ordinal || entry.ordinal > end.ordinal)
218237
+ continue;
218238
+ const xml = decodeByteRange(bytes, entry.byteStart, entry.byteEnd);
218239
+ if (!/<\s*(?:[\w.-]+:)?sdt\b/.test(xml))
218240
+ continue;
218241
+ const namespaces = namespaceBindingsAtPosition(bytes, entry.byteStart, matchParagraphOpenTag(xml) ?? "");
218242
+ const selectedStart = entry.blockId === start.blockId ? range.startOffset : 0;
218243
+ const selectedEnd = entry.blockId === end.blockId ? range.endOffset : entry.textLength;
218244
+ const wholeParagraph = range.deleteFullBlocks === true || entry.ordinal > start.ordinal && entry.ordinal < end.ordinal;
218245
+ const visit = (pieces) => {
218246
+ for (const piece of pieces) {
218247
+ if (piece.kind !== "container" && piece.kind !== "tracked")
218248
+ continue;
218249
+ if (piece.kind === "container" && piece.containerKind === "sdt") {
218250
+ const touched = wholeParagraph || (piece.visibleStart === piece.visibleEnd ? (entry.blockId !== start.blockId || selectedStart < piece.visibleStart) && piece.visibleEnd <= selectedEnd : selectedStart < piece.visibleEnd && piece.visibleStart < selectedEnd);
218251
+ if (!touched)
218252
+ continue;
218253
+ const policy = classifySdtMutationPolicy(piece);
218254
+ if (!policy.ok)
218255
+ return policy.reason;
218256
+ if (!policy.wrapperDeletable)
218257
+ return "text-range-in-sdt";
218258
+ const wholeWrapper = wholeParagraph || selectedStart <= piece.visibleStart && piece.visibleEnd <= selectedEnd;
218259
+ if (!wholeWrapper && !policy.contentEditable)
218260
+ return "text-range-in-sdt";
218261
+ }
218262
+ const failure3 = visit(piece.children);
218263
+ if (failure3)
218264
+ return failure3;
218265
+ }
218266
+ return null;
218267
+ };
218268
+ const failure2 = visit(parseParagraphInlineMap(xml, namespaces).pieces);
218269
+ if (failure2)
218270
+ return failure2;
218271
+ }
218272
+ return null;
218273
+ }
218016
218274
  function directBodyRangeIsStructurallyPlain(bytes, entries, start, end, changeMode) {
218017
218275
  if (!Number.isSafeInteger(start.ordinal) || !Number.isSafeInteger(end.ordinal) || start.ordinal < 0 || end.ordinal <= start.ordinal || end.ordinal >= entries.length || entries[start.ordinal]?.blockId !== start.blockId || entries[start.ordinal]?.byteStart !== start.byteStart || entries[start.ordinal]?.byteEnd !== start.byteEnd || entries[end.ordinal]?.blockId !== end.blockId || entries[end.ordinal]?.byteStart !== end.byteStart || entries[end.ordinal]?.byteEnd !== end.byteEnd || !Number.isSafeInteger(start.byteStart) || !Number.isSafeInteger(end.byteEnd) || start.byteStart < 0 || end.byteEnd <= start.byteStart || end.byteEnd > bytes.byteLength)
218018
218276
  return false;
@@ -224330,7 +224588,7 @@ function runBodyStep(input, storyId, noteId, step2, changeMode, options, pushHis
224330
224588
  if ((step2.kind === "edit" || step2.kind === "formatEdit") && step2.targetRunXml !== undefined) {
224331
224589
  const before = liveCompareBodyRunXml(state, storyId, step2.blockId);
224332
224590
  const partBytes = state.packageBytes.get(storyPartUriFromStoryId2(storyId));
224333
- if (before !== undefined && partBytes && inspectInlineControlRemoval(before, step2.targetRunXml, textboxNamespaceBindingsFromPartBytes(partBytes))) {
224591
+ if (before !== undefined && partBytes && (inspectInlineControlRemoval(before, step2.targetRunXml, textboxNamespaceBindingsFromPartBytes(partBytes)) || inspectInlineControlReplay(before, step2.targetRunXml, textboxNamespaceBindingsFromPartBytes(partBytes)))) {
224334
224592
  return runInlineControlRemovalCommit(input, storyId, step2, changeMode, options, pushHistory);
224335
224593
  }
224336
224594
  }
@@ -225091,6 +225349,134 @@ function retainedNoteMarkerRuns(oldRuns, targetRuns, storyId) {
225091
225349
  targetRuns: targetMarker.remainingRunXml
225092
225350
  };
225093
225351
  }
225352
+ function equivalentSimpleRunContent(before, after) {
225353
+ const previous = parseSimpleRunTokens(before), next = parseSimpleRunTokens(after);
225354
+ if (!previous || !next)
225355
+ return false;
225356
+ const spans = (tokens) => tokens.flatMap((token) => Array.from(token.text, (char) => `${canonicalizeXmlForSignature(token.openTag)}:${char}`));
225357
+ const old = spans(previous), target = spans(next);
225358
+ return old.length === target.length && old.every((value, index) => value === target[index]);
225359
+ }
225360
+ function inlineComparePieces(proof) {
225361
+ const pieces = [];
225362
+ const gap = (before, roots, rootOffset) => {
225363
+ const tokens = parseSimpleRunTokens(before);
225364
+ if (!tokens)
225365
+ return false;
225366
+ const oldText = tokens.map((token) => token.text).join("");
225367
+ const targets = roots.map((xml, index) => {
225368
+ const control2 = proof.inserted.find((item) => item.index === index + rootOffset);
225369
+ const text = projectCompareReplayCurrentText(control2?.contentXml ?? xml);
225370
+ return text === null ? null : { xml, control: control2, text };
225371
+ });
225372
+ if (targets.some((item) => item === null))
225373
+ return false;
225374
+ const nextText = targets.map((item) => item.text).join("");
225375
+ const operations = getWordChanges(oldText, nextText);
225376
+ const mapOffset = (offset) => {
225377
+ let oldCursor3 = 0, nextCursor3 = 0;
225378
+ for (const op of operations) {
225379
+ const start = op.type === "insert" ? op.insertAt : op.oldFrom;
225380
+ const equalEnd = nextCursor3 + start - oldCursor3;
225381
+ if (offset < equalEnd)
225382
+ return oldCursor3 + offset - nextCursor3;
225383
+ const oldEnd = op.type === "insert" ? start : op.oldTo;
225384
+ const nextEnd = equalEnd + (op.type === "delete" ? 0 : op.newText.length);
225385
+ if (offset < nextEnd)
225386
+ return start;
225387
+ oldCursor3 = oldEnd;
225388
+ nextCursor3 = nextEnd;
225389
+ }
225390
+ return oldCursor3 + offset - nextCursor3;
225391
+ };
225392
+ let oldCursor2 = 0, nextCursor2 = 0;
225393
+ let ordinaryTarget = "";
225394
+ const appendOrdinary = (end) => {
225395
+ pieces.push({
225396
+ beforeXml: renderCompareRunRange(tokens, oldCursor2, end, "passthrough"),
225397
+ afterXml: ordinaryTarget,
225398
+ ordinary: true
225399
+ });
225400
+ ordinaryTarget = "";
225401
+ oldCursor2 = end;
225402
+ };
225403
+ for (const target of targets) {
225404
+ if (!target)
225405
+ return false;
225406
+ if (!target.control) {
225407
+ ordinaryTarget += target.xml;
225408
+ nextCursor2 += target.text.length;
225409
+ continue;
225410
+ }
225411
+ const start = mapOffset(nextCursor2), end = mapOffset(nextCursor2 + target.text.length);
225412
+ if (start < oldCursor2 || end < start || end > oldText.length)
225413
+ return false;
225414
+ appendOrdinary(start);
225415
+ const selected = renderCompareRunRange(tokens, start, end, "passthrough");
225416
+ if (equivalentSimpleRunContent(selected, target.control.contentXml)) {
225417
+ pieces.push({ beforeXml: target.control.contentXml, afterXml: target.xml });
225418
+ } else {
225419
+ pieces.push({ beforeXml: selected, afterXml: "", ordinary: true });
225420
+ pieces.push({ beforeXml: "", afterXml: target.xml });
225421
+ }
225422
+ oldCursor2 = end;
225423
+ nextCursor2 += target.text.length;
225424
+ }
225425
+ appendOrdinary(oldText.length);
225426
+ return true;
225427
+ };
225428
+ let oldCursor = 0, nextCursor = 0;
225429
+ for (const anchor of proof.anchors) {
225430
+ if (!gap(proof.beforeRoots.slice(oldCursor, anchor.beforeIndex).join(""), proof.afterRoots.slice(nextCursor, anchor.afterIndex), nextCursor))
225431
+ return null;
225432
+ if (anchor.changed) {
225433
+ pieces.push({ beforeXml: anchor.before.xml, afterXml: anchor.after.xml });
225434
+ } else
225435
+ pieces.push({ beforeXml: anchor.before.xml, afterXml: anchor.before.xml });
225436
+ oldCursor = anchor.beforeIndex + 1;
225437
+ nextCursor = anchor.afterIndex + 1;
225438
+ }
225439
+ if (!gap(proof.beforeRoots.slice(oldCursor).join(""), proof.afterRoots.slice(nextCursor), nextCursor))
225440
+ return null;
225441
+ return pieces;
225442
+ }
225443
+ function inspectInlineControlCompareBodyReplay(bytes, startByte, endByte, targetRunXml) {
225444
+ const xml = TD60.decode(bytes);
225445
+ const start = TD60.decode(bytes.subarray(0, startByte)).length;
225446
+ const end = start + TD60.decode(bytes.subarray(startByte, endByte)).length;
225447
+ const parsed = collectXmlElementRanges(xml);
225448
+ if (parsed.errors.length)
225449
+ return null;
225450
+ const body = parsed.ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "body");
225451
+ if (!body || !directXmlChildRanges(parsed.ranges, body).some((range) => range.start === start && range.end === end && range.tag.namespaceUri === NS_W && range.tag.localName === "p"))
225452
+ return null;
225453
+ const proof = inspectInlineControlReplay(extractCompareParagraphRunXml(xml.slice(start, end)), targetRunXml, textboxNamespaceBindingsFromPartBytes(bytes));
225454
+ if (!proof)
225455
+ return null;
225456
+ const counts = new Map;
225457
+ for (const range of parsed.ranges) {
225458
+ if (range.tag.namespaceUri !== NS_W || range.tag.localName !== "sdtPr")
225459
+ continue;
225460
+ for (const id of directXmlChildRanges(parsed.ranges, range).filter((child) => child.tag.namespaceUri === NS_W && child.tag.localName === "id")) {
225461
+ const raw = readXmlAttribute(id.tag, NS_W, "val")?.value;
225462
+ if (raw !== undefined && /^-?\d+$/.test(raw) && Number.isSafeInteger(Number(raw))) {
225463
+ const key2 = String(Number(raw));
225464
+ counts.set(key2, (counts.get(key2) ?? 0) + 1);
225465
+ }
225466
+ }
225467
+ }
225468
+ if (proof.anchors.some((anchor) => counts.get(anchor.before.id) !== 1) || proof.inserted.some((control2) => counts.has(control2.id)))
225469
+ return null;
225470
+ return proof;
225471
+ }
225472
+ function directlyOwnedBodyParagraph(bytes, startByte, endByte) {
225473
+ const xml = TD60.decode(bytes);
225474
+ const start = TD60.decode(bytes.subarray(0, startByte)).length;
225475
+ const end = start + TD60.decode(bytes.subarray(startByte, endByte)).length;
225476
+ const parsed = collectXmlElementRanges(xml);
225477
+ const body = parsed.ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "body");
225478
+ return !parsed.errors.length && !!body && directXmlChildRanges(parsed.ranges, body).some((range) => range.start === start && range.end === end && range.tag.namespaceUri === NS_W && range.tag.localName === "p");
225479
+ }
225094
225480
  function runInlineControlRemovalCommit(input, storyId, step2, changeMode, options, pushHistory) {
225095
225481
  const { state, txId } = input;
225096
225482
  const partUri = storyPartUriFromStoryId2(storyId);
@@ -225106,7 +225492,10 @@ function runInlineControlRemovalCommit(input, storyId, step2, changeMode, option
225106
225492
  const before = extractCompareParagraphRunXml(paragraph);
225107
225493
  const namespaces = textboxNamespaceBindingsFromPartBytes(bytes);
225108
225494
  const proof = inspectInlineControlRemoval(before, step2.targetRunXml, namespaces);
225109
- if (!proof || /<(?:[\w.-]+:)?\w+Change\b/.test(properties))
225495
+ const replay = inspectInlineControlCompareBodyReplay(bytes, entry.byteStart, entry.byteEnd, step2.targetRunXml);
225496
+ if (replay && !/^[0-9A-Fa-f]{8}$/.test(step2.blockId))
225497
+ return rejected14(txId, "unsupported-context");
225498
+ if (!proof && !replay || !directlyOwnedBodyParagraph(bytes, entry.byteStart, entry.byteEnd) || /<(?:[\w.-]+:)?\w+Change\b/.test(properties))
225110
225499
  return rejected14(txId, "unsupported-context");
225111
225500
  const targetProperties = step2.targetParagraphPropertiesXml === undefined ? properties : rewriteCanonicalWordprocessingPrefix(step2.targetParagraphPropertiesXml, wrapper.wordprocessingPrefix);
225112
225501
  if (!equivalentParagraphProperties(properties, targetProperties))
@@ -225119,7 +225508,9 @@ function runInlineControlRemovalCommit(input, storyId, step2, changeMode, option
225119
225508
  return rejected14(txId, "no-author-configured");
225120
225509
  const namespaceBindings = new Map(Object.entries(namespaces));
225121
225510
  namespaceBindings.set(wrapper.wordprocessingPrefix, W_NS4);
225122
- const pieces = [];
225511
+ const pieces = replay ? inlineComparePieces(replay) ?? [] : [];
225512
+ if (replay && !pieces.length)
225513
+ return rejected14(txId, "unsupported-context");
225123
225514
  const appendGap = (old, next) => {
225124
225515
  let head = 0;
225125
225516
  while (head < old.length && head < next.length && old[head] === next[head])
@@ -225136,7 +225527,7 @@ function runInlineControlRemovalCommit(input, storyId, step2, changeMode, option
225136
225527
  };
225137
225528
  let oldCursor = 0;
225138
225529
  let nextCursor = 0;
225139
- for (let index = 0;index < proof.afterRoots.length; index++) {
225530
+ for (let index = 0;proof && !replay && index < proof.afterRoots.length; index++) {
225140
225531
  const root = proof.afterRoots[index];
225141
225532
  if (!/<(?:[\w.-]+:)?sdt\b/.test(root))
225142
225533
  continue;
@@ -225148,12 +225539,33 @@ function runInlineControlRemovalCommit(input, storyId, step2, changeMode, option
225148
225539
  oldCursor = oldIndex + 1;
225149
225540
  nextCursor = index + 1;
225150
225541
  }
225151
- appendGap(proof.beforeRoots.slice(oldCursor), proof.afterRoots.slice(nextCursor));
225542
+ if (proof && !replay)
225543
+ appendGap(proof.beforeRoots.slice(oldCursor), proof.afterRoots.slice(nextCursor));
225152
225544
  const stamp = { revisionId: 0, author: serializedSessionAuthor(sourceAuthor), date: input.txTimestamp };
225153
225545
  const prepare = (revisionIds) => {
225154
225546
  let xml = "";
225155
225547
  const wordIds = [];
225156
225548
  for (const piece of pieces) {
225549
+ if (piece.beforeXml === piece.afterXml) {
225550
+ xml += piece.beforeXml;
225551
+ continue;
225552
+ }
225553
+ if (piece.ordinary && !/<(?:[\w.-]+:)?sdt\b/.test(piece.beforeXml + piece.afterXml)) {
225554
+ const result2 = renderWordLevelTrackedRunSegment(piece.beforeXml, piece.afterXml, () => {
225555
+ const delId = revisionIds?.[wordIds.length] ?? wordIds.length;
225556
+ wordIds.push(String(delId));
225557
+ const insId = revisionIds?.[wordIds.length] ?? wordIds.length;
225558
+ wordIds.push(String(insId));
225559
+ return {
225560
+ del: { rsid: delId.toString(16).padStart(8, "0"), wId: delId, author: stamp.author, date: stamp.date },
225561
+ ins: { rsid: delId.toString(16).padStart(8, "0"), wId: insId, author: stamp.author, date: stamp.date }
225562
+ };
225563
+ }, wrapper.wordprocessingPrefix);
225564
+ if (result2) {
225565
+ xml += result2.xml;
225566
+ continue;
225567
+ }
225568
+ }
225157
225569
  const result = prepareTrackedSdtXmlReplacement({
225158
225570
  beforeXml: piece.beforeXml,
225159
225571
  afterXml: piece.afterXml,
@@ -236511,7 +236923,7 @@ function resolveFragmentApplyTarget(state, op) {
236511
236923
  const index2 = paragraphIndexForPasteStory(state, target.storyId);
236512
236924
  if (!index2)
236513
236925
  return { ok: false, reason: "unsupported-context" };
236514
- const structuralFailure = validateRangeCollapseStructure(bytes, index2.entries, anchor.entry, anchor.entry, op.changeMode ?? "direct", undefined, undefined, { allowWholeParagraphReplacement: true });
236926
+ const structuralFailure = validateRangeCollapseStructure(bytes, index2.entries, anchor.entry, anchor.entry, op.changeMode ?? "direct", undefined, undefined, { allowWholeParagraphReplacement: true, inlineRange: { startOffset: 0, endOffset: anchor.entry.textLength, deleteFullBlocks: true } });
236515
236927
  if (structuralFailure)
236516
236928
  return { ok: false, reason: structuralFailure };
236517
236929
  const integrityFailure = validateFragmentRangeIntegrity({
@@ -236587,7 +236999,7 @@ function resolveFragmentApplyTarget(state, op) {
236587
236999
  const crossParagraph = startAnchor.ordinal !== endAnchor.ordinal;
236588
237000
  const replacesContent = crossParagraph || target.charOffset !== explicitEnd.charOffset;
236589
237001
  if (crossParagraph) {
236590
- const structuralFailure = validateRangeCollapseStructure(residentBytes, index.entries, startAnchor.entry, endAnchor.entry, op.changeMode ?? "direct", target.collapsedObjectBoundary, explicitEnd.objectBoundary, { allowTrackedSdtContent: true });
237002
+ const structuralFailure = validateRangeCollapseStructure(residentBytes, index.entries, startAnchor.entry, endAnchor.entry, op.changeMode ?? "direct", target.collapsedObjectBoundary, explicitEnd.objectBoundary, { allowTrackedSdtContent: true, inlineRange: { startOffset: target.charOffset, endOffset: explicitEnd.charOffset } });
236591
237003
  if (structuralFailure)
236592
237004
  return { ok: false, reason: structuralFailure };
236593
237005
  } else if (replacesContent && residentBytes) {
@@ -255970,6 +256382,7 @@ var init_sd_document_session_internal = __esm(() => {
255970
256382
  init_section_property_references();
255971
256383
  init_bibliography_style();
255972
256384
  init_bibliography_review_resources();
256385
+ init_compile_compare_apply();
255973
256386
  });
255974
256387
 
255975
256388
  // ../../../v2/document-api-v2-adapter/dist/address/story-identity.js
@@ -437273,8 +437686,8 @@ function applyPreflightedTrackedTextDecisionsToStory(input) {
437273
437686
  tx.meta.set(ACTOR_ATTRIBUTION_META_KEY, input.actorType);
437274
437687
  scope.meta.set(STORY_META_KEY.lastEditActorType, input.actorType);
437275
437688
  const { decision, doc: doc2, removedCommentCarrierIds, resolved, work } = input.plan;
437276
- for (const { target, span, block } of work) {
437277
- removeTrackedTextCarriers(block, span, decision === "reject" && target.carrierKind === "field-insertion");
437689
+ for (const { span, block } of work) {
437690
+ removeTrackedTextCarriers(block, span, decision === "reject" && span.trackedKind === "ins");
437278
437691
  removeCommentCarriersByIds(block, removedCommentCarrierIds);
437279
437692
  }
437280
437693
  const sorted = [...work].sort((left, right) => {
@@ -437291,12 +437704,12 @@ function applyPreflightedTrackedTextDecisionsToStory(input) {
437291
437704
  if (span.trackedKind === "ins" && decision === "reject") {
437292
437705
  const length3 = span.endOffset - span.startOffset;
437293
437706
  if (length3 > 0) {
437294
- deleteText2({
437295
- doc: doc2,
437296
- blockId: span.blockId,
437707
+ deleteTextAtResolvedTarget({
437708
+ target: resolveStoryTextMutationTarget(doc2, span.blockId),
437297
437709
  offset: span.startOffset,
437298
437710
  length: length3,
437299
- origin: STORY_SURFACE_BACKEND_ORIGIN
437711
+ origin: STORY_SURFACE_BACKEND_ORIGIN,
437712
+ preserveZeroLengthAtStart: true
437300
437713
  });
437301
437714
  }
437302
437715
  }
@@ -437664,8 +438077,19 @@ function validateCompareApplySessionSync(prepared, extension2, options) {
437664
438077
  if (!extension2.storyLocal || prepared.resolvedTargets.stories.length !== 1 || String(prepared.resolvedTargets.stories[0]) !== storyId || ownership.length !== 1 || String(ownership[0]?.storyId) !== storyId || prepared.resolvedTargets.partUris.length !== 1 || String(prepared.resolvedTargets.partUris[0]) !== String(ownership[0]?.partUri)) {
437665
438078
  return { ok: false, message: "compareApply collaboration requires one owned story part" };
437666
438079
  }
437667
- if (source.bodySteps.length === 0 || source.bodySteps.some((step3) => step3.kind !== "edit") || source.storyGroups?.length || source.storyPackageOperations?.length || source.commentSteps?.length || source.packageRewrites?.length || source.relationshipBackedBodyRewrites?.length || source.headerFooterLifecycle || source.headerFooterSlotSteps?.length || source.tableTopologySteps?.length || source.tableBlockSteps?.length || source.tablePropertySteps?.length || source.sectionReplaySteps?.length || source.referenceReplaySteps?.length) {
437668
- return { ok: false, message: "compareApply collaboration supports paragraph text edits only" };
438080
+ if (source.bodySteps.length === 0 || source.bodySteps.some((step3) => {
438081
+ if (step3.kind === "edit")
438082
+ return false;
438083
+ if (step3.kind !== "formatEdit" || step3.targetRunXml === undefined)
438084
+ return true;
438085
+ if (extension2.historyReplay)
438086
+ return false;
438087
+ const bytes = getSDDocumentSessionInternalPartBytes(options.session, String(ownership[0].partUri));
438088
+ const entry = getSDDocumentSessionInternalStoryParagraphIndex(options.session, source.storyId)?.byParaId(step3.blockId);
438089
+ const proof = bytes && entry && inspectInlineControlCompareBodyReplay(bytes, entry.byteStart, entry.byteEnd, step3.targetRunXml);
438090
+ return !proof || proof.inserted.length === 0;
438091
+ }) || source.storyGroups?.length || source.storyPackageOperations?.length || source.commentSteps?.length || source.packageRewrites?.length || source.relationshipBackedBodyRewrites?.length || source.headerFooterLifecycle || source.headerFooterSlotSteps?.length || source.tableTopologySteps?.length || source.tableBlockSteps?.length || source.tablePropertySteps?.length || source.sectionReplaySteps?.length || source.referenceReplaySteps?.length) {
438092
+ return { ok: false, message: "compareApply collaboration supports paragraph text edits and proved inline control wrapping only" };
437669
438093
  }
437670
438094
  const targetBlockIds2 = [...new Set(source.bodySteps.map((step3) => step3.blockId))];
437671
438095
  if (targetBlockIds2.length !== extension2.targetBlockIds.length || targetBlockIds2.some((blockId) => !extension2.targetBlockIds.includes(blockId))) {
@@ -438977,6 +439401,7 @@ function atomOverlapsRange(atomOffset, atomLength, rangeStart, rangeEnd) {
438977
439401
  var STORY_SURFACE_TRACE, TRACKED_CHANGE_DECISIONS_LEDGER_KEY = "trackedChangeDecisions", CLEARED_RUN_ATTRIBUTES;
438978
439402
  var init_story_surface_execution_backend = __esm(() => {
438979
439403
  init_yjs();
439404
+ init_sd_document_session_internal();
438980
439405
  init_scope3();
438981
439406
  init_content_control_groups();
438982
439407
  init_story_surface_session_sync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/mcp",
3
- "version": "1.4.0-next.8",
3
+ "version": "1.4.0-next.9",
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.17.0-next.8",
27
+ "@superdoc/sdk": "2.17.0-next.9",
28
28
  "debug": "^4.4.3",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {
32
- "superdoc": "2.20.0-next.10",
33
- "@superdoc/docx-engine": "0.19.0-next.11",
32
+ "superdoc": "2.20.0-next.11",
33
+ "@superdoc/docx-engine": "0.19.0-next.12",
34
34
  "@superdoc/document-api": "0.1.0-alpha.0",
35
35
  "@types/bun": "^1.3.8",
36
36
  "@types/node": "22.19.2",