@stll/folio-core 0.55.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/ai-edits/apply.js +169 -38
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/ai-edits/types.d.ts +7 -1
  4. package/dist/compare/compare.js +1 -0
  5. package/dist/compare/reproducible-package.d.ts +0 -17
  6. package/dist/compare/reproducible-package.js +12 -18
  7. package/dist/controller/canonicalClipboard.d.ts +23 -0
  8. package/dist/controller/canonicalClipboard.js +476 -0
  9. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  10. package/dist/controller/canonicalClipboardResources.js +382 -0
  11. package/dist/controller/canonicalComposition.d.ts +20 -3
  12. package/dist/controller/canonicalComposition.js +166 -38
  13. package/dist/controller/canonicalInlineProjection.d.ts +113 -0
  14. package/dist/controller/canonicalInlineProjection.js +393 -0
  15. package/dist/controller/canonicalInput.d.ts +13 -4
  16. package/dist/controller/canonicalInput.js +52 -13
  17. package/dist/controller/canonicalInputTimer.d.ts +7 -0
  18. package/dist/controller/canonicalInputTimer.js +11 -0
  19. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  20. package/dist/controller/canonicalPublicOperations.js +556 -0
  21. package/dist/controller/canonicalSession.d.ts +24 -6
  22. package/dist/controller/canonicalSession.js +259 -111
  23. package/dist/controller/canonicalStoryEditor.d.ts +5 -3
  24. package/dist/controller/canonicalStoryEditor.js +7 -2
  25. package/dist/controller/canonicalStructure.js +57 -4
  26. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  27. package/dist/controller/canonicalTocSelection.js +43 -0
  28. package/dist/controller/folioEditor.d.ts +2 -9
  29. package/dist/controller/folioEditor.js +5 -4
  30. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  31. package/dist/controller/headerFooterEditorManager.js +8 -7
  32. package/dist/controller/hiddenEditorApi.d.ts +12 -2
  33. package/dist/controller/hiddenEditorApi.js +4 -0
  34. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  35. package/dist/controller/hiddenEditorManager.js +216 -34
  36. package/dist/controller/noteEditorManager.d.ts +2 -1
  37. package/dist/controller/noteEditorManager.js +8 -7
  38. package/dist/document-operations.d.ts +8 -1
  39. package/dist/document-operations.js +6 -4
  40. package/dist/docx/blockPlainText.d.ts +1 -1
  41. package/dist/docx/blockPlainText.js +11 -3
  42. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  43. package/dist/docx/canonicalResourceSave.js +27 -0
  44. package/dist/docx/canonicalSave.d.ts +29 -0
  45. package/dist/docx/canonicalSave.js +101 -0
  46. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  47. package/dist/docx/canonicalStoryRepack.js +38 -5
  48. package/dist/docx/documentParser.d.ts +1 -13
  49. package/dist/docx/documentParser.js +1 -47
  50. package/dist/docx/ensureParaIds.js +10 -4
  51. package/dist/docx/footnoteParser.d.ts +1 -11
  52. package/dist/docx/footnoteParser.js +1 -16
  53. package/dist/docx/headerFooterParser.d.ts +1 -9
  54. package/dist/docx/headerFooterParser.js +1 -12
  55. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  56. package/dist/docx/headerFooterVerbatim.js +81 -3
  57. package/dist/docx/imageParser.d.ts +13 -1
  58. package/dist/docx/imageParser.js +20 -11
  59. package/dist/docx/metadataPrivacy.js +6 -1
  60. package/dist/docx/packageParts.js +9 -3
  61. package/dist/docx/paragraphPropertySource.js +2 -1
  62. package/dist/docx/parser.js +2 -1
  63. package/dist/docx/removeHeaderFooterParts.js +17 -6
  64. package/dist/docx/rezip.d.ts +49 -5
  65. package/dist/docx/rezip.js +745 -229
  66. package/dist/docx/saveDiagnostics.d.ts +11 -0
  67. package/dist/docx/selectiveSave.d.ts +2 -1
  68. package/dist/docx/selectiveSave.js +32 -3
  69. package/dist/docx/selectiveXmlPatch.js +7 -5
  70. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  71. package/dist/docx/serializer/documentSerializer.js +29 -10
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  73. package/dist/docx/storyBlockReplay.d.ts +21 -1
  74. package/dist/docx/storyBlockReplay.js +113 -9
  75. package/dist/docx/storyPlainText.d.ts +20 -0
  76. package/dist/docx/storyPlainText.js +23 -0
  77. package/dist/docx/structuralXmlPatch.js +10 -22
  78. package/dist/docx/styleParser.d.ts +5 -1
  79. package/dist/docx/styleParser.js +1 -1
  80. package/dist/docx/tableParser.d.ts +1 -8
  81. package/dist/docx/tableParser.js +1 -28
  82. package/dist/docx/textBoxParser.d.ts +1 -5
  83. package/dist/docx/textBoxParser.js +1 -20
  84. package/dist/docx/unzip.js +6 -1
  85. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  86. package/dist/internal/acceptedBlockProjection.js +45 -0
  87. package/dist/managers/DocumentLoaderManager.js +2 -1
  88. package/dist/markdown/escape.d.ts +3 -1
  89. package/dist/markdown/escape.js +9 -2
  90. package/dist/markdown/renderBlock.js +5 -120
  91. package/dist/markdown/renderParagraph.js +4 -2
  92. package/dist/markdown/renderRuns.d.ts +7 -1
  93. package/dist/markdown/renderRuns.js +32 -13
  94. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  95. package/dist/prosemirror/canonicalCommands.js +11 -4
  96. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  97. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  98. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  99. package/dist/prosemirror/clipboardIntent.js +9 -0
  100. package/dist/prosemirror/commands/pageBreak.js +2 -2
  101. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  102. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  103. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  104. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  105. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  106. package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
  107. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
  108. package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
  109. package/dist/prosemirror/insertOperations.js +2 -1
  110. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  111. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  112. package/dist/prosemirror/plugins/suggestionMode.js +7 -5
  113. package/dist/types/canonicalCapabilities.d.ts +215 -0
  114. package/dist/types/canonicalCapabilities.js +212 -0
  115. package/dist/types/canonicalSave.d.ts +11 -0
  116. package/dist/types/canonicalSave.js +0 -0
  117. package/dist/types/docxSerialization.d.ts +14 -0
  118. package/dist/types/docxSerialization.js +7 -0
  119. package/package.json +4 -4
@@ -17,7 +17,7 @@ import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
17
17
  import { styleResolvedParagraphFormatting } from "../prosemirror/paragraphFormattingProvenance.js";
18
18
  import { directParagraphIndentation, paragraphIndentationAttrPatch, paragraphIndentationEqual, paragraphIndentationFromFormatting, withDirectParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
19
19
  import { joinAtParagraphMark } from "../prosemirror/paragraphMarkJoin.js";
20
- import { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties } from "../prosemirror/paragraphPropertyCarry.js";
20
+ import { carriedParagraphProperties, carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties } from "../prosemirror/paragraphPropertyCarry.js";
21
21
  import { directParagraphSpacing, paragraphSpacingAttrPatch, paragraphSpacingEqual, paragraphSpacingFromFormatting, withDirectParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
22
22
  import { getDocumentNumbering, getDocumentNumberingInstanceIds } from "../prosemirror/plugins/documentNumbering.js";
23
23
  import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
@@ -33,6 +33,7 @@ import { listAttrsFromNumbering, listLevelAttrPatch } from "../prosemirror/style
33
33
  import { removeRowsWithoutCellsAfterBatch } from "../prosemirror/tableGridMutation.js";
34
34
  import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
35
35
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
36
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
36
37
  import { BatchClaims } from "./batch-claims.js";
37
38
  import { stripBlockIdentityAttrs } from "./block-identity.js";
38
39
  import { describeCharacterSplit } from "./character-boundaries.js";
@@ -436,6 +437,10 @@ const preserveRunFormattingAcrossTrackedStyleChange = ({ allocateRevisionInfo, n
436
437
  revisionIds
437
438
  };
438
439
  };
440
+ const appendParagraphPropertyChange = (existing, change) => {
441
+ if (change.info.provenance !== "suggested" && hasSerializableParagraphPropertyChange(existing)) panic("A paragraph-property write must resolve its occupied revision slot first");
442
+ return [...existing ?? [], change];
443
+ };
439
444
  /**
440
445
  * Apply one paragraph-property patch to a live node. A tracked patch stores
441
446
  * the complete previous pPr: a partial snapshot cannot distinguish a property
@@ -482,7 +487,7 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
482
487
  const nextAttrs = {
483
488
  ...node.attrs,
484
489
  ...patch,
485
- ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
490
+ ...change ? { _propertyChanges: appendParagraphPropertyChange(existing, change) } : {}
486
491
  };
487
492
  const bridgeResult = preserveRunFormatting && revisionInfo ? preserveRunFormattingAcrossTrackedStyleChange({
488
493
  allocateRevisionInfo: revisionInfo,
@@ -560,7 +565,7 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
560
565
  const nextAttrs = {
561
566
  ...block.attrs,
562
567
  ...patch,
563
- ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
568
+ ...change ? { _propertyChanges: appendParagraphPropertyChange(existing, change) } : {}
564
569
  };
565
570
  const bridgeResult = preserveRunFormatting && revisionInfo ? preserveRunFormattingAcrossTrackedStyleChange({
566
571
  allocateRevisionInfo: revisionInfo,
@@ -1280,6 +1285,11 @@ const isInsertedPPrMark = (value) => typeof value === "object" && value !== null
1280
1285
  const isSuggestedParagraphInsert = (paragraph) => typeof paragraph.attrs["_suggestedInsert"] === "object" && paragraph.attrs["_suggestedInsert"] !== null;
1281
1286
  /** A paragraph mark that is a pending deletion, or a relocation's source. */
1282
1287
  const isDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && (value.kind === "del" || value.kind === "moveFrom");
1288
+ const pendingParagraphMarkPropertyRefusal = {
1289
+ reason: "pendingParagraphMarkDeletion",
1290
+ canonicalRefusal: { gap: CANONICAL_GAP.publicPendingParagraphMarkProperties },
1291
+ message: "Resolve the pending paragraph mark deletion before editing its properties, or provide mergedParagraphProperties on the merge operation for atomic intent."
1292
+ };
1283
1293
  const hasRevisionMarker = (value) => typeof value === "object" && value !== null && "revisionId" in value;
1284
1294
  const isDeletedCell = (cell) => {
1285
1295
  const marker = cell.attrs["cellMarker"];
@@ -1501,6 +1511,10 @@ const crossesBatchDeletion = ({ doc, from, to, batchRevisionIds }) => {
1501
1511
  }
1502
1512
  return false;
1503
1513
  };
1514
+ const canRetractFinalBreak = ({ predecessor, target }) => {
1515
+ if (target.type !== predecessor.type) return false;
1516
+ return !(JSON.stringify(paragraphPropertiesBeforePendingChanges(target)) !== JSON.stringify(paragraphPropertiesSnapshot(predecessor)) && hasSerializableParagraphPropertyChange(expectParagraphAttrs(predecessor)._propertyChanges));
1517
+ };
1504
1518
  /** Pending retirements, read after the merge has mapped any inserted separator. */
1505
1519
  const pendingRemovedFinalBreakPositions = (tr, deletions) => {
1506
1520
  const positions = /* @__PURE__ */ new Set();
@@ -1512,6 +1526,92 @@ const pendingRemovedFinalBreakPositions = (tr, deletions) => {
1512
1526
  }
1513
1527
  return positions;
1514
1528
  };
1529
+ /** An unoccupied final carrier takes admissible writes from the finished batch. */
1530
+ const finalParagraphCarry = ({ tr, at, target, predecessor, batchRevisionIds, styleResolver, numbering }) => {
1531
+ const { node: previous, position } = predecessor;
1532
+ const beforeBatch = hasSerializableParagraphPropertyChange(expectParagraphAttrs(target)._propertyChanges) ? resolveParagraphChangeAttrs({
1533
+ node: previous,
1534
+ mode: "reject",
1535
+ boundaryCovered: true,
1536
+ revisionSet: batchRevisionIds,
1537
+ styleResolver,
1538
+ numbering
1539
+ }) : null;
1540
+ return {
1541
+ source: beforeBatch ? previous.type.create(beforeBatch, previous.content, previous.marks) : previous,
1542
+ keep: crossesBatchDeletion({
1543
+ doc: tr.doc,
1544
+ from: position + previous.nodeSize,
1545
+ to: at,
1546
+ batchRevisionIds
1547
+ }) ? /* @__PURE__ */ new Set() : propertiesSetInBatch(target, batchRevisionIds)
1548
+ };
1549
+ };
1550
+ /** Deferred retirement still owns a slot before a later explicit formatting merge. */
1551
+ const hasDeferredParagraphPropertyWrite = ({ tr, deletions, targets, mergedPropertyTargets, batchRevisionIds, styleResolver, numbering }) => {
1552
+ for (const deletion of deletions) {
1553
+ const at = tr.mapping.slice(deletion.mappedFrom).map(deletion.position);
1554
+ const target = tr.doc.nodeAt(at);
1555
+ if (!target) continue;
1556
+ const predecessor = finalParagraphPredecessor(tr.doc, at);
1557
+ if (!predecessor) continue;
1558
+ const mark = predecessor.node.attrs["pPrMark"];
1559
+ if (mark == null && mergedPropertyTargets.some(({ position, mappedFrom }) => {
1560
+ const mapped = tr.mapping.slice(mappedFrom).mapResult(position);
1561
+ return !mapped.deleted && mapped.pos === at;
1562
+ })) continue;
1563
+ const writeTarget = mark == null ? target : tr.doc.nodeAt(predecessor.position + predecessor.node.nodeSize);
1564
+ if (!writeTarget || !targets.includes(writeTarget)) continue;
1565
+ if (mark != null && !(isInsertedPPrMark(mark) && canRetractFinalBreak({
1566
+ predecessor: predecessor.node,
1567
+ target: writeTarget
1568
+ }))) continue;
1569
+ const transfer = mark == null ? finalParagraphCarry({
1570
+ tr,
1571
+ at,
1572
+ target,
1573
+ predecessor,
1574
+ batchRevisionIds,
1575
+ styleResolver,
1576
+ numbering
1577
+ }) : {
1578
+ source: predecessor.node,
1579
+ keep: propertiesSetInBatch(writeTarget, batchRevisionIds)
1580
+ };
1581
+ if (JSON.stringify(paragraphPropertiesSnapshot(writeTarget)) !== JSON.stringify(carriedParagraphProperties({
1582
+ ...transfer,
1583
+ target: writeTarget
1584
+ }))) return true;
1585
+ }
1586
+ return false;
1587
+ };
1588
+ /** The paragraph a merge leaves owns its properties, even across pending deleted breaks. */
1589
+ const paragraphPropertyRevisionTargets = ({ item, tr, deletedFinalParagraphs }) => {
1590
+ const position = tr.mapping.map(item.blockFrom, item.operation.type === "replaceBlock" ? -1 : 1);
1591
+ const source = tr.doc.nodeAt(position) ?? item.blockNode;
1592
+ const targets = source.type.name === "paragraph" ? [source] : [];
1593
+ if (item.operation.type !== "mergeBlockWithNext") return targets;
1594
+ const removedBreakPositions = pendingRemovedFinalBreakPositions(tr, deletedFinalParagraphs);
1595
+ for (const deletion of deletedFinalParagraphs) {
1596
+ const at = tr.mapping.slice(deletion.mappedFrom).map(deletion.position);
1597
+ const predecessor = finalParagraphPredecessor(tr.doc, at);
1598
+ if (!predecessor || !isInsertedPPrMark(predecessor.node.attrs["pPrMark"])) continue;
1599
+ const target = tr.doc.nodeAt(predecessor.position + predecessor.node.nodeSize);
1600
+ if (!target) continue;
1601
+ if (canRetractFinalBreak({
1602
+ predecessor: predecessor.node,
1603
+ target
1604
+ })) removedBreakPositions.add(predecessor.position);
1605
+ }
1606
+ const survivorPosition = paragraphLeftAfter({
1607
+ doc: tr.doc,
1608
+ paragraphPos: position,
1609
+ removedBreakPositions
1610
+ });
1611
+ const survivor = survivorPosition === null ? null : tr.doc.nodeAt(survivorPosition);
1612
+ if (survivor?.type.name === "paragraph") targets.push(survivor);
1613
+ return targets;
1614
+ };
1515
1615
  /**
1516
1616
  * Finish the tracked deletion of every paragraph a container ends with.
1517
1617
  *
@@ -1562,21 +1662,21 @@ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appl
1562
1662
  ...revisionExtras
1563
1663
  }
1564
1664
  });
1565
- const beforeBatch = resolveParagraphChangeAttrs({
1566
- node: previous,
1567
- mode: "reject",
1568
- boundaryCovered: true,
1569
- revisionSet: batchRevisionIds,
1570
- styleResolver,
1571
- numbering
1572
- });
1573
1665
  if (!mergedPropertyTargets.some(({ position: target, mappedFrom }) => {
1574
1666
  const mapped = tr.mapping.slice(mappedFrom).mapResult(target);
1575
1667
  return !mapped.deleted && mapped.pos === at;
1576
1668
  })) carryParagraphProperties({
1577
1669
  tr,
1578
1670
  position: at,
1579
- source: beforeBatch ? previous.type.create(beforeBatch, previous.content, previous.marks) : previous,
1671
+ ...finalParagraphCarry({
1672
+ tr,
1673
+ at,
1674
+ target: emptied,
1675
+ predecessor,
1676
+ batchRevisionIds,
1677
+ styleResolver,
1678
+ numbering
1679
+ }),
1580
1680
  styleResolver,
1581
1681
  numbering,
1582
1682
  revision: {
@@ -1584,13 +1684,7 @@ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appl
1584
1684
  author,
1585
1685
  date,
1586
1686
  ...revisionExtras
1587
- },
1588
- keep: crossesBatchDeletion({
1589
- doc: tr.doc,
1590
- from: position + previous.nodeSize,
1591
- to: at,
1592
- batchRevisionIds
1593
- }) ? /* @__PURE__ */ new Set() : propertiesSetInBatch(emptied, batchRevisionIds)
1687
+ }
1594
1688
  });
1595
1689
  resolvedOperationIds.add(operationId);
1596
1690
  addedRevisions.push({
@@ -1601,11 +1695,10 @@ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appl
1601
1695
  }
1602
1696
  if (isInsertedPPrMark(mark)) {
1603
1697
  const following = tr.doc.nodeAt(position + previous.nodeSize);
1604
- if (following?.type.name !== emptied.type.name) continue;
1605
- const previousFormatting = paragraphPropertiesBeforePendingChanges(following);
1606
- const formattingChanges = JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(previous));
1607
- const existing = expectParagraphAttrs(previous)._propertyChanges;
1608
- if (formattingChanges && hasSerializableParagraphPropertyChange(existing)) continue;
1698
+ if (!following || !canRetractFinalBreak({
1699
+ predecessor: previous,
1700
+ target: following
1701
+ })) continue;
1609
1702
  joinAtParagraphMark({
1610
1703
  tr,
1611
1704
  paragraphPos: position,
@@ -2148,6 +2241,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2148
2241
  skipped.push({
2149
2242
  id: operation.id,
2150
2243
  reason: resolution.reason,
2244
+ ...resolution.canonicalRefusal !== void 0 && { canonicalRefusal: resolution.canonicalRefusal },
2151
2245
  ...resolution.message !== void 0 && { message: resolution.message }
2152
2246
  });
2153
2247
  continue;
@@ -2344,9 +2438,22 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2344
2438
  if (!item) panic("The operation execution index exceeded the resolved plan", { executionIndex });
2345
2439
  continueSharedRevisionIds();
2346
2440
  if (mode === "tracked-changes" && writesParagraphPropertyChange(item)) {
2347
- const livePosition = tr.mapping.map(item.blockFrom);
2348
- const liveBlock = tr.doc.nodeAt(livePosition) ?? item.blockNode;
2349
- if (hasSerializableParagraphPropertyChange(liveBlock.type.name === "paragraph" ? expectParagraphAttrs(liveBlock)._propertyChanges : void 0)) {
2441
+ const appliedIds = new Set(applied.map(({ id }) => id));
2442
+ const pendingDeletions = deletedFinalParagraphs.filter(({ operationId }) => appliedIds.has(operationId));
2443
+ const targets = paragraphPropertyRevisionTargets({
2444
+ item,
2445
+ tr,
2446
+ deletedFinalParagraphs: pendingDeletions
2447
+ });
2448
+ if (targets.some((target) => hasSerializableParagraphPropertyChange(expectParagraphAttrs(target)._propertyChanges)) || item.operation.type === "mergeBlockWithNext" && hasDeferredParagraphPropertyWrite({
2449
+ tr,
2450
+ deletions: pendingDeletions,
2451
+ targets,
2452
+ mergedPropertyTargets,
2453
+ batchRevisionIds: new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? [])),
2454
+ styleResolver,
2455
+ numbering
2456
+ })) {
2350
2457
  skipped.push({
2351
2458
  id: item.operation.id,
2352
2459
  reason: "pendingParagraphPropertyChange"
@@ -2580,6 +2687,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2580
2687
  case "replaceBlock": {
2581
2688
  if (!isResolvedReplaceBlockOperation(item)) panic("Resolved replaceBlock operation lost its impact discriminator");
2582
2689
  const { changesStyle, changesText } = REPLACE_BLOCK_IMPACT[item.replaceBlockImpact];
2690
+ if (changesStyle && isDeletedPPrMark(tr.doc.nodeAt(tr.mapping.map(item.blockFrom, -1))?.attrs["pPrMark"])) {
2691
+ skipped.push({
2692
+ id: item.operation.id,
2693
+ ...pendingParagraphMarkPropertyRefusal
2694
+ });
2695
+ continue;
2696
+ }
2583
2697
  const revisionIdDelete = operationRevisionSeed;
2584
2698
  const revisionIdInsert = changesText ? operationRevisionSeed + 1 : operationRevisionSeed;
2585
2699
  const revisionIdBackgroundSeed = changesText ? operationRevisionSeed + 2 : operationRevisionSeed;
@@ -3312,6 +3426,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3312
3426
  continue;
3313
3427
  }
3314
3428
  const blockPosition = mapped.pos;
3429
+ if (isDeletedPPrMark(liveBlock.attrs["pPrMark"])) {
3430
+ skipped.push({
3431
+ id: item.operation.id,
3432
+ ...pendingParagraphMarkPropertyRefusal
3433
+ });
3434
+ continue;
3435
+ }
3315
3436
  const appliedProperties = applyBlockParagraphProperties({
3316
3437
  tr,
3317
3438
  position: blockPosition,
@@ -4889,17 +5010,23 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
4889
5010
  }
4890
5011
  };
4891
5012
  }
4892
- if (operation.type === "setBlockParagraphProperties") return {
4893
- type: "resolved",
4894
- operation: {
4895
- operation,
4896
- from: blockFrom,
4897
- to: blockTo,
4898
- blockFrom,
4899
- blockTo,
4900
- blockNode
4901
- }
4902
- };
5013
+ if (operation.type === "setBlockParagraphProperties") {
5014
+ if (isDeletedPPrMark(blockNode.attrs["pPrMark"])) return {
5015
+ type: "skip",
5016
+ ...pendingParagraphMarkPropertyRefusal
5017
+ };
5018
+ return {
5019
+ type: "resolved",
5020
+ operation: {
5021
+ operation,
5022
+ from: blockFrom,
5023
+ to: blockTo,
5024
+ blockFrom,
5025
+ blockTo,
5026
+ blockNode
5027
+ }
5028
+ };
5029
+ }
4903
5030
  if (operation.type === "splitBlock") {
4904
5031
  const separator = operation.separator ?? "";
4905
5032
  const startOffset = operation.offset;
@@ -4965,6 +5092,10 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
4965
5092
  if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
4966
5093
  const replaceChangesText = operation.type === "replaceBlock" && operation.text !== currentText;
4967
5094
  const replaceChangesStyle = operation.type === "replaceBlock" && operation.styleId !== void 0 && operation.styleId !== (expectParagraphAttrs(blockNode).styleId ?? null);
5095
+ if (replaceChangesStyle && isDeletedPPrMark(blockNode.attrs["pPrMark"])) return {
5096
+ type: "skip",
5097
+ ...pendingParagraphMarkPropertyRefusal
5098
+ };
4968
5099
  if (replaceChangesText && cleanBlock.structuralBoundaries.some((boundary) => boundary.type !== "field" && boundary.type !== "noteReference")) return {
4969
5100
  type: "skip",
4970
5101
  reason: "unsupportedBlock"
@@ -5,8 +5,7 @@ import { importReferencedStyleDefinitions } from "../compare/style-resources.js"
5
5
  import { applyFolioDocumentOperations } from "../document-operations.js";
6
6
  import { applyReplyThreadMarkers } from "../docx/commentReplyMarkers.js";
7
7
  import { rebindDrawingImageRelationship } from "../docx/drawingRelationships.js";
8
- import { getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
9
- import { getHeaderFooterText } from "../docx/headerFooterParser.js";
8
+ import { isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
10
9
  import { createNumberingIdAllocator } from "../docx/numberingIds.js";
11
10
  import { getCachedNumberingMap } from "../docx/numberingParser.js";
12
11
  import { recreateProseNodeWithParagraphPropertySource, transferProseParagraphPropertySource } from "../docx/paragraphPropertySource.js";
@@ -15,6 +14,7 @@ import { createReply } from "../docx/replyToComment.js";
15
14
  import { repackDocx } from "../docx/rezip.js";
16
15
  import { sectionReferenceHistory } from "../docx/sectionReferenceHistory.js";
17
16
  import { attemptSelectiveSave } from "../docx/selectiveSave.js";
17
+ import { getEndnoteText, getFootnoteText, getHeaderFooterText } from "../docx/storyPlainText.js";
18
18
  import { pluginsForHeadlessRevisionResolution } from "../internal/headlessRevisionResolutionGuard.js";
19
19
  import { withTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
20
20
  import { captureSectionReferenceInventory, resolvedSectionReferenceLosses, withSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { CanonicalGap } from "../types/canonicalCapabilities.js";
2
3
  import { FolioContentBlock, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentListReference, FolioContentParagraphIndentation, FolioContentParagraphKind, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
3
4
  import { ListKind } from "../docx/listNumberingInstances.js";
4
5
  //#region src/ai-edits/types.d.ts
@@ -656,7 +657,9 @@ type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" |
656
657
  * deletion is accepted. Reject that deletion first, or insert a new block
657
658
  * next to it.
658
659
  */
659
- "pendingDeletion";
660
+ "pendingDeletion" |
661
+ /** Resolve the removed paragraph mark before editing its properties separately. */
662
+ "pendingParagraphMarkDeletion";
660
663
  type FolioAIEditAppliedOperation = {
661
664
  id: string;
662
665
  commentId?: number;
@@ -689,6 +692,9 @@ type FolioAIEditAppliedOperation = {
689
692
  type FolioAIEditSkippedOperation = {
690
693
  id: string;
691
694
  reason: FolioAIEditSkipReason;
695
+ canonicalRefusal?: {
696
+ gap: CanonicalGap;
697
+ };
692
698
  /**
693
699
  * What exactly was wrong when the reason alone does not say: the values a
694
700
  * `payloadDoesNotFit` skip could not place, the earlier operation an
@@ -486,6 +486,7 @@ const COMPARE_SKIP_DISPOSITION = {
486
486
  staleRange: "fatal",
487
487
  emptyOperation: "unwritable",
488
488
  pendingParagraphPropertyChange: "unwritable",
489
+ pendingParagraphMarkDeletion: "unwritable",
489
490
  pendingRunPropertyChange: "unwritable",
490
491
  noopOperation: "unwritable",
491
492
  documentVersionMismatch: "fatal",
@@ -1,21 +1,4 @@
1
1
  //#region src/compare/reproducible-package.d.ts
2
- /**
3
- * The last two clocks in the compare path are outside the document body.
4
- *
5
- * Every part the serializer rewrites is stored with JSZip's default entry
6
- * date, which is `new Date()`. The XML is identical between two runs, but the
7
- * DOS timestamp fields in the local headers are not, and they only agree when
8
- * both runs land in the same two-second bucket — so the packages match most of
9
- * the time and differ occasionally, which is worse than differing always.
10
- *
11
- * The save also stamps `dcterms:modified` in `docProps/core.xml` from the wall
12
- * clock, which is the same failure one part deeper: two runs over identical
13
- * inputs differ in that part alone.
14
- *
15
- * Restamping both from the comparison's own timestamp removes them. It also
16
- * states the truth about the package: a generated redline is dated by the
17
- * comparison that produced it, not by the second it happened to be written.
18
- */
19
2
  declare const withFixedPackageDates: (buffer: ArrayBuffer, date: Date) => Promise<ArrayBuffer>;
20
3
  //#endregion
21
4
  export { withFixedPackageDates };
@@ -1,21 +1,10 @@
1
+ import { writeZipPart } from "@stll/docx-core/zip";
1
2
  import JSZip from "jszip";
2
3
  //#region src/compare/reproducible-package.ts
3
4
  /**
4
- * The last two clocks in the compare path are outside the document body.
5
- *
6
- * Every part the serializer rewrites is stored with JSZip's default entry
7
- * date, which is `new Date()`. The XML is identical between two runs, but the
8
- * DOS timestamp fields in the local headers are not, and they only agree when
9
- * both runs land in the same two-second bucket — so the packages match most of
10
- * the time and differ occasionally, which is worse than differing always.
11
- *
12
- * The save also stamps `dcterms:modified` in `docProps/core.xml` from the wall
13
- * clock, which is the same failure one part deeper: two runs over identical
14
- * inputs differ in that part alone.
15
- *
16
- * Restamping both from the comparison's own timestamp removes them. It also
17
- * states the truth about the package: a generated redline is dated by the
18
- * comparison that produced it, not by the second it happened to be written.
5
+ * Date comparison output from the comparison's timestamp. Imported entries
6
+ * retain source dates, and saves can stamp `dcterms:modified` from the wall
7
+ * clock; restamping both makes repeated comparisons reproducible.
19
8
  */
20
9
  /** JSZip deflate level `repackDocx` writes DOCX parts at. */
21
10
  const DOCX_COMPRESSION_LEVEL = 6;
@@ -30,9 +19,14 @@ const withFixedModifiedDate = (corePropsXml, date) => corePropsXml.replace(MODIF
30
19
  const withFixedPackageDates = async (buffer, date) => {
31
20
  const zip = await JSZip.loadAsync(buffer);
32
21
  const coreProps = zip.file(CORE_PROPERTIES_PATH);
33
- if (coreProps) zip.file(CORE_PROPERTIES_PATH, withFixedModifiedDate(await coreProps.async("text"), date), {
34
- compression: "DEFLATE",
35
- compressionOptions: { level: DOCX_COMPRESSION_LEVEL }
22
+ if (coreProps) writeZipPart({
23
+ zip,
24
+ path: CORE_PROPERTIES_PATH,
25
+ data: withFixedModifiedDate(await coreProps.async("text"), date),
26
+ options: {
27
+ compression: "DEFLATE",
28
+ compressionOptions: { level: DOCX_COMPRESSION_LEVEL }
29
+ }
36
30
  });
37
31
  zip.forEach((_path, file) => {
38
32
  file.date = date;
@@ -0,0 +1,23 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { CanonicalCommit, CanonicalSession, CanonicalSessionError } from "./canonicalSession.js";
3
+ import { Slice } from "prosemirror-model";
4
+ import { EditorState } from "prosemirror-state";
5
+ import { Result } from "better-result";
6
+ //#region src/controller/canonicalClipboard.d.ts
7
+ /** Clipboard slices are input payloads; their paragraph identities and private captures are not owners. */
8
+ type PrepareCanonicalPasteOptions = {
9
+ session: CanonicalSession;
10
+ state: EditorState;
11
+ slice: Slice;
12
+ plain?: boolean;
13
+ moveTarget?: number;
14
+ pasteTarget?: number;
15
+ sourceDocument?: document_d_exports.Document;
16
+ moveSource?: {
17
+ from: number;
18
+ to: number;
19
+ };
20
+ };
21
+ declare const prepareCanonicalPaste: ({ session, state, slice, plain, moveTarget, pasteTarget, sourceDocument, moveSource }: PrepareCanonicalPasteOptions) => Result<CanonicalCommit, CanonicalSessionError>;
22
+ //#endregion
23
+ export { prepareCanonicalPaste };