@stll/folio-core 0.3.1 → 0.5.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 (120) hide show
  1. package/dist/ai-edits/apply.d.ts +3 -9
  2. package/dist/ai-edits/apply.js +94 -14
  3. package/dist/ai-edits/blockRange.d.ts +33 -0
  4. package/dist/ai-edits/blockRange.js +58 -0
  5. package/dist/ai-edits/headless.d.ts +35 -5
  6. package/dist/ai-edits/headless.js +92 -22
  7. package/dist/ai-edits/index.d.ts +5 -3
  8. package/dist/ai-edits/index.js +3 -2
  9. package/dist/ai-edits/snapshot.d.ts +14 -2
  10. package/dist/ai-edits/snapshot.js +12 -1
  11. package/dist/ai-edits/types.d.ts +42 -3
  12. package/dist/compat/eigenpal.d.ts +3 -2
  13. package/dist/compat/eigenpal.js +6 -5
  14. package/dist/controller/layoutPipeline.d.ts +1 -0
  15. package/dist/controller/layoutPipeline.js +35 -115
  16. package/dist/controller/layoutScheduler.d.ts +2 -1
  17. package/dist/controller/layoutScheduler.js +6 -0
  18. package/dist/document-operations.d.ts +122 -0
  19. package/dist/document-operations.js +575 -0
  20. package/dist/docx/blockContentParser.js +17 -7
  21. package/dist/docx/encryption/agileDecryption.d.ts +6 -0
  22. package/dist/docx/encryption/agileDecryption.js +209 -0
  23. package/dist/docx/encryption/compoundFile.d.ts +20 -0
  24. package/dist/docx/encryption/compoundFile.js +251 -0
  25. package/dist/docx/encryption/containerFormat.d.ts +15 -0
  26. package/dist/docx/encryption/containerFormat.js +40 -0
  27. package/dist/docx/encryption/cryptoBytes.d.ts +11 -0
  28. package/dist/docx/encryption/cryptoBytes.js +55 -0
  29. package/dist/docx/encryption/encryptionInfo.d.ts +32 -0
  30. package/dist/docx/encryption/encryptionInfo.js +128 -0
  31. package/dist/docx/encryption/errors.d.ts +18 -0
  32. package/dist/docx/encryption/errors.js +13 -0
  33. package/dist/docx/encryption/index.d.ts +4 -0
  34. package/dist/docx/encryption/index.js +4 -0
  35. package/dist/docx/encryption/openEncryptedDocx.d.ts +28 -0
  36. package/dist/docx/encryption/openEncryptedDocx.js +65 -0
  37. package/dist/docx/footnoteParser.d.ts +1 -17
  38. package/dist/docx/footnoteParser.js +1 -27
  39. package/dist/docx/groupDrawingParser.d.ts +8 -0
  40. package/dist/docx/groupDrawingParser.js +131 -0
  41. package/dist/docx/index.d.ts +3 -1
  42. package/dist/docx/index.js +3 -1
  43. package/dist/docx/numberingParser.d.ts +3 -1
  44. package/dist/docx/numberingParser.js +8 -2
  45. package/dist/docx/paragraphParser.js +9 -3
  46. package/dist/docx/parser.d.ts +4 -5
  47. package/dist/docx/parser.js +8 -2
  48. package/dist/docx/runParser.js +8 -1
  49. package/dist/docx/selectiveXmlPatch.js +4 -10
  50. package/dist/docx/serializer/numberingSerializer.js +3 -1
  51. package/dist/docx/settingsParser.d.ts +6 -3
  52. package/dist/docx/settingsParser.js +2 -0
  53. package/dist/docx/unzip.d.ts +7 -5
  54. package/dist/docx/unzip.js +8 -2
  55. package/dist/docx/xmlParser.js +11 -4
  56. package/dist/index.d.ts +3 -2
  57. package/dist/index.js +6 -5
  58. package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
  59. package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
  60. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +6 -8
  61. package/dist/layout-bridge/convert/headerFooterLayout.js +21 -13
  62. package/dist/layout-bridge/convert/toFlowBlocks.js +58 -19
  63. package/dist/layout-engine/index.d.ts +6 -5
  64. package/dist/layout-engine/index.js +116 -27
  65. package/dist/layout-engine/keep-together.d.ts +13 -8
  66. package/dist/layout-engine/keep-together.js +40 -25
  67. package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
  68. package/dist/layout-engine/layoutInstrumentation.js +10 -1
  69. package/dist/layout-engine/measure/measureBlocks.js +43 -19
  70. package/dist/layout-engine/measure/measureParagraph.js +91 -14
  71. package/dist/layout-engine/paginator.d.ts +2 -1
  72. package/dist/layout-engine/paginator.js +9 -3
  73. package/dist/layout-engine/textBoxGroup.d.ts +7 -0
  74. package/dist/layout-engine/textBoxGroup.js +8 -0
  75. package/dist/layout-engine/types.d.ts +33 -9
  76. package/dist/layout-engine/types.js +20 -2
  77. package/dist/layout-painter/renderPage.d.ts +15 -1
  78. package/dist/layout-painter/renderPage.js +29 -12
  79. package/dist/layout-painter/renderParagraph.js +44 -9
  80. package/dist/layout-painter/renderTable.js +27 -7
  81. package/dist/managers/DocumentLoaderManager.d.ts +3 -1
  82. package/dist/managers/DocumentLoaderManager.js +13 -5
  83. package/dist/managers/TableSelectionManager.js +1 -1
  84. package/dist/model.js +1 -1
  85. package/dist/paged-layout/pageText.d.ts +14 -0
  86. package/dist/paged-layout/pageText.js +21 -0
  87. package/dist/prosemirror/attrs/index.js +3 -0
  88. package/dist/prosemirror/commands/comments.js +75 -5
  89. package/dist/prosemirror/commands/hyperlink.js +4 -2
  90. package/dist/prosemirror/commands/image.js +2 -1
  91. package/dist/prosemirror/commands/index.js +1 -1
  92. package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
  93. package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
  94. package/dist/prosemirror/conversion/fromProseDoc.js +21 -5
  95. package/dist/prosemirror/conversion/toProseDoc.js +43 -15
  96. package/dist/prosemirror/extensions/core/ParagraphExtension.js +3 -0
  97. package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
  98. package/dist/prosemirror/extensions/nodes/TableExtension.js +12 -11
  99. package/dist/prosemirror/findReplaceSelection.js +2 -1
  100. package/dist/prosemirror/index.js +1 -1
  101. package/dist/prosemirror/insertOperations.js +1 -1
  102. package/dist/prosemirror/schema/nodes.d.ts +14 -7
  103. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  104. package/dist/prosemirror/utils/tabCalculator.js +4 -2
  105. package/dist/redline.d.ts +20 -0
  106. package/dist/redline.js +127 -0
  107. package/dist/server.d.ts +7 -3
  108. package/dist/server.js +6 -2
  109. package/dist/symbols.d.ts +35 -0
  110. package/dist/symbols.js +512 -0
  111. package/dist/utils/clipboard.js +3 -3
  112. package/dist/utils/fontLoader.js +3 -0
  113. package/dist/utils/fontResolver.js +65 -1
  114. package/dist/utils/zoom.d.ts +21 -0
  115. package/dist/utils/zoom.js +21 -0
  116. package/dist/version-comparison.d.ts +92 -0
  117. package/dist/version-comparison.js +436 -0
  118. package/package.json +7 -3
  119. package/dist/paged-layout/headerFooterMargins.d.ts +0 -73
  120. package/dist/paged-layout/headerFooterMargins.js +0 -120
@@ -7,6 +7,17 @@ const hashFolioAIBlockText = (text) => {
7
7
  for (const character of text) hash = (hash * 33 + (character.codePointAt(0) ?? 0)) % 2147483647;
8
8
  return `h${hash.toString(36)}`;
9
9
  };
10
+ const createFolioAITextRangeHandle = ({ blockId, text, startOffset, endOffset }) => {
11
+ if (blockId.length === 0 || !Number.isInteger(startOffset) || !Number.isInteger(endOffset) || startOffset < 0 || endOffset <= startOffset || endOffset > text.length) return null;
12
+ return {
13
+ type: "textRange",
14
+ story: "main",
15
+ blockId,
16
+ startOffset,
17
+ endOffset,
18
+ selectedTextHash: hashFolioAIBlockText(text.slice(startOffset, endOffset))
19
+ };
20
+ };
10
21
  const createFolioAIEditSnapshot = (doc) => {
11
22
  const draftBlocks = [];
12
23
  const hashCounts = /* @__PURE__ */ new Map();
@@ -192,4 +203,4 @@ const getColorFromAttrs = (attrs) => {
192
203
  const samePreviewRunStyle = (run, style) => run.bold === style.bold && run.italic === style.italic && run.underline === style.underline && run.strike === style.strike && run.fontFamily === style.fontFamily && run.fontSizePt === style.fontSizePt && run.color === style.color;
193
204
  const isUnstyledPreviewRun = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
194
205
  //#endregion
195
- export { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText };
206
+ export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText };
@@ -45,6 +45,27 @@ type FolioAIEditReviewMeta = {
45
45
  severity?: FolioAIEditSeverity;
46
46
  area?: string;
47
47
  };
48
+ type FolioAIEditPrecondition = {
49
+ blockTextHash: string;
50
+ };
51
+ /**
52
+ * A serializable range over the visible, post-tracked-changes text of one
53
+ * block. Offsets are zero-based UTF-16 boundaries, matching JavaScript string
54
+ * slicing; `selectedTextHash` makes a shifted or changed selection fail stale.
55
+ */
56
+ type FolioAITextRangeHandle = {
57
+ type: "textRange";
58
+ story: "main";
59
+ blockId: string;
60
+ startOffset: number;
61
+ endOffset: number;
62
+ selectedTextHash: string;
63
+ };
64
+ type FolioAIInlineFormatting = {
65
+ bold?: boolean;
66
+ italic?: boolean;
67
+ underline?: boolean;
68
+ };
48
69
  /**
49
70
  * A party in an `insertSignatureTable` op. Mirrors the
50
71
  * `signatureTable` helper in `docx-core/legal-source/compile.ts`:
@@ -56,13 +77,31 @@ type FolioAISignatureParty = {
56
77
  signatory?: string;
57
78
  title?: string;
58
79
  };
59
- type FolioAIEditOperation = FolioAIEditReviewMeta & ({
80
+ type FolioAIEditOperation = FolioAIEditReviewMeta & {
81
+ precondition?: FolioAIEditPrecondition;
82
+ } & ({
60
83
  id: string;
61
84
  type: "replaceInBlock";
62
85
  blockId: string;
63
86
  find: string;
64
87
  replace: string;
65
88
  comment?: FolioAIComment;
89
+ } | {
90
+ id: string;
91
+ type: "replaceRange";
92
+ range: FolioAITextRangeHandle;
93
+ replace: string;
94
+ comment?: FolioAIComment;
95
+ } | {
96
+ id: string;
97
+ type: "commentOnRange";
98
+ range: FolioAITextRangeHandle;
99
+ comment: FolioAIComment;
100
+ } | {
101
+ id: string;
102
+ type: "formatRange";
103
+ range: FolioAITextRangeHandle;
104
+ formatting: FolioAIInlineFormatting;
66
105
  } | {
67
106
  id: string;
68
107
  type: "insertAfterBlock" | "insertBeforeBlock";
@@ -116,7 +155,7 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & ({
116
155
  comment?: FolioAIComment;
117
156
  });
118
157
  type FolioAIEditApplyMode = "direct" | "tracked-changes";
119
- type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" | "missingFind" | "unsupportedBlock" | "emptyOperation"
158
+ type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" | "missingFind" | "unsupportedBlock" | "unsupportedMode" | "atomicBatchRejected" | "preconditionFailed" | "staleRange" | "emptyOperation"
120
159
  /**
121
160
  * The operation would not change the document — find equals
122
161
  * replace, or replaceBlock's `text` matches the live block.
@@ -153,4 +192,4 @@ type FolioAIEditApplyResult = {
153
192
  skipped: FolioAIEditSkippedOperation[];
154
193
  };
155
194
  //#endregion
156
- export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAISignatureParty };
195
+ export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAISignatureParty, FolioAITextRangeHandle };
@@ -1,6 +1,7 @@
1
- import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAISignatureParty } from "../ai-edits/types.js";
1
+ import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAISignatureParty } from "../ai-edits/types.js";
2
2
  import { applyFolioAIEditOperations } from "../ai-edits/apply.js";
3
3
  import { document_d_exports } from "../types/document.js";
4
+ import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationType, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
4
5
  import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "../ai-edits/snapshot.js";
5
6
  import { DeriveBlockIdInput, FolioBlockId, deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "../types/block-id.js";
6
7
  import { WordDiffSegment, diffWordSegments } from "../ai-edits/word-diff.js";
@@ -26,4 +27,4 @@ import { toMarkdown, toMarkdownResult } from "../markdown/index.js";
26
27
  import { EmbeddedFont, EmbeddedFontParts, extractEmbeddedFonts, getEmbeddedFontFaces } from "../fonts/embeddedFonts.js";
27
28
  import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "../utils/fontResolver.js";
28
29
  type Document = document_d_exports.Document;
29
- export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocxCompatibility, type EmbeddedFont, type EmbeddedFontParts, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type ImageMeta, type ImageRef, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applySuggestions, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, isFolioBlockId, isSequentialFolioBlockId, isSuggestionStale, normalizeFolioAIBlockText, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
30
+ export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocxCompatibility, type EmbeddedFont, type EmbeddedFontParts, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationType, type ImageMeta, type ImageRef, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -1,13 +1,14 @@
1
+ import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "../types/block-id.js";
2
+ import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "../ai-edits/snapshot.js";
3
+ import { diffWordSegments } from "../ai-edits/word-diff.js";
4
+ import { applyFolioAIEditOperations } from "../ai-edits/apply.js";
5
+ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
1
6
  import { createEmptyDocument } from "../utils/createDocument.js";
2
7
  import { createDocx } from "../docx/rezip.js";
3
- import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "../types/block-id.js";
4
8
  import { DEFAULT_AI_SUGGESTION_PRESETS } from "../ai-suggestions/types.js";
5
9
  import { buildPositionalText } from "../ai-suggestions/text-positions.js";
6
10
  import { isSuggestionStale, resolveSuggestionAnchor } from "../ai-suggestions/conflict.js";
7
11
  import { applySuggestions } from "../ai-suggestions/apply.js";
8
- import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "../ai-edits/snapshot.js";
9
- import { diffWordSegments } from "../ai-edits/word-diff.js";
10
- import { applyFolioAIEditOperations } from "../ai-edits/apply.js";
11
12
  import { setAISuggestionsMeta, setFocusedSuggestionMeta } from "../prosemirror/plugins/aiSuggestionDecorations.js";
12
13
  import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "../utils/fontResolver.js";
13
14
  import { scrollFolioPositionIntoView } from "../paged-layout/scrollToPmPosition.js";
@@ -21,4 +22,4 @@ import { DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, acceptAutocompleteSuggestion, acc
21
22
  import { fromMarkdown } from "../markdown/fromMarkdown.js";
22
23
  import { toMarkdown, toMarkdownResult } from "../markdown/index.js";
23
24
  import { extractEmbeddedFonts, getEmbeddedFontFaces } from "../fonts/embeddedFonts.js";
24
- export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applySuggestions, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, isFolioBlockId, isSequentialFolioBlockId, isSuggestionStale, normalizeFolioAIBlockText, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
25
+ export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -45,6 +45,7 @@ type LayoutPipelineDeps<THfPMs> = {
45
45
  sectionProperties: document_d_exports.SectionProperties | null | undefined;
46
46
  document: document_d_exports.Document | null;
47
47
  defaultTabStop: number | undefined;
48
+ mirrorMargins: boolean;
48
49
  styles: document_d_exports.StyleDefinitions | null | undefined;
49
50
  layout: Layout | null;
50
51
  hfPMs: THfPMs;
@@ -2,32 +2,24 @@ import "../layout-engine/types.js";
2
2
  import { renderPages } from "../layout-painter/renderPage.js";
3
3
  import { templatePreviewValuesKey } from "../prosemirror/plugins/templatePreviewValues.js";
4
4
  import { toFlowBlocks } from "../layout-bridge/convert/toFlowBlocks.js";
5
- import { recordLayoutComplete, recordLayoutError, recordLayoutPhase } from "../layout-engine/layoutInstrumentation.js";
5
+ import { recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart } from "../layout-engine/layoutInstrumentation.js";
6
6
  import { buildBookmarkPageMap } from "../fields/bookmarkPages.js";
7
7
  import { buildBookmarkText } from "../fields/bookmarkText.js";
8
8
  import { buildHeaderFooterFieldValues, fieldValuesEqual, resolveFieldValues } from "../fields/resolveFieldValues.js";
9
9
  import { buildSectionPageCounts } from "../fields/sectionPageCounts.js";
10
10
  import { buildSeqValues } from "../fields/seqValues.js";
11
11
  import { installCanvasMeasureProvider } from "../layout-engine/measure/measureContainer.js";
12
- import { buildFootnoteContentMap, collectFootnoteRefs } from "../layout-bridge/convert/footnoteLayout.js";
12
+ import { buildFootnoteContentMap, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, remapNoteMarkerText } from "../layout-bridge/convert/footnoteLayout.js";
13
13
  import { applyTemplatePreviewToBlocks } from "../layout-bridge/convert/templatePreviewFlow.js";
14
14
  import { layoutDocument } from "../layout-engine/index.js";
15
15
  import { measureBlocks, measureSingleBlockWithoutFloatingZones } from "../layout-engine/measure/measureBlocks.js";
16
- import { computeFirstPageHeaderFooterMarginExtender, computeHeaderFooterMarginExtender, extendSectionBreakMargins } from "../paged-layout/headerFooterMargins.js";
17
16
  import { tryBuildIncrementalMeasures } from "../paged-layout/incrementalMeasure.js";
18
17
  import { computePerBlockMeasureInputs } from "../paged-layout/sectionBlockWidths.js";
19
- import { twipsToPixels } from "../paged-layout/sectionGeometry.js";
18
+ import { getMargins, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
20
19
  import { getDocumentWatermark } from "../watermark/index.js";
21
20
  //#region src/controller/layoutPipeline.ts
22
- function pageMarginsEqual(left, right) {
23
- return left.top === right.top && left.right === right.right && left.bottom === right.bottom && left.left === right.left && left.header === right.header && left.footer === right.footer;
24
- }
25
- function optionalPageMarginsEqual(left, right) {
26
- if (left === void 0 || right === void 0) return left === right;
27
- return pageMarginsEqual(left, right);
28
- }
29
21
  function runLayoutPipeline(deps, state, options = {}) {
30
- const { contentWidth, columns, pageSize, margins, pageGap, syncCoordinator, headerContent, footerContent, firstPageHeaderContent, firstPageFooterContent, headerContentRId, footerContentRId, firstPageHeaderContentRId, firstPageFooterContentRId, sectionHeaderFooterRefs, theme: _theme, sectionProperties, document, defaultTabStop, styles, layout, hfPMs, painter, pagesContainer, session, renderHfFromContentOrPm, renderHeaderFooterContentByRId, documentFontsAreLoaded, buildFootnoteRenderItems, describeInvalidHighlightMarks, emptyTemplatePreviewEntries: EMPTY_TEMPLATE_PREVIEW_ENTRIES } = deps;
22
+ const { contentWidth, columns, pageSize, margins, pageGap, syncCoordinator, headerContent, footerContent, firstPageHeaderContent, firstPageFooterContent, headerContentRId, footerContentRId, firstPageHeaderContentRId, firstPageFooterContentRId, sectionHeaderFooterRefs, theme: _theme, sectionProperties, document, defaultTabStop, mirrorMargins, styles, layout, hfPMs, painter, pagesContainer, session, renderHfFromContentOrPm, renderHeaderFooterContentByRId, documentFontsAreLoaded, buildFootnoteRenderItems, describeInvalidHighlightMarks, emptyTemplatePreviewEntries: EMPTY_TEMPLATE_PREVIEW_ENTRIES } = deps;
31
23
  let outcome = {};
32
24
  let pendingArtifacts;
33
25
  let pendingTemplatePreview;
@@ -37,6 +29,7 @@ function runLayoutPipeline(deps, state, options = {}) {
37
29
  recordLayoutPhase(reason, phase, performance.now() - startedAt);
38
30
  };
39
31
  const currentEpoch = syncCoordinator.getStateSeq();
32
+ recordLayoutStart(reason);
40
33
  syncCoordinator.onLayoutStart();
41
34
  try {
42
35
  let phaseStartedAt = performance.now();
@@ -61,6 +54,14 @@ function runLayoutPipeline(deps, state, options = {}) {
61
54
  const footnoteRefs = collectFootnoteRefs(newBlocks);
62
55
  const documentFootnotes = document?.package.footnotes;
63
56
  const hasFootnotes = footnoteRefs.length > 0 && documentFootnotes !== void 0;
57
+ const footnoteDisplayNumbers = documentFootnotes ? computeNoteDisplayNumbers(documentFootnotes, footnoteRefs.map((ref) => ref.footnoteId)) : void 0;
58
+ const documentEndnotes = document?.package.endnotes;
59
+ const endnoteDisplayNumbers = documentEndnotes ? computeNoteDisplayNumbers(documentEndnotes, collectEndnoteRefs(newBlocks).map((ref) => ref.endnoteId)) : void 0;
60
+ newBlocks = remapNoteMarkerText(newBlocks, {
61
+ ...footnoteDisplayNumbers ? { footnoteNumbers: footnoteDisplayNumbers } : {},
62
+ ...endnoteDisplayNumbers ? { endnoteNumbers: endnoteDisplayNumbers } : {}
63
+ });
64
+ outcome.blocks = newBlocks;
64
65
  const hfMetricsHeader = {
65
66
  section: "header",
66
67
  pageSize,
@@ -90,63 +91,19 @@ function runLayoutPipeline(deps, state, options = {}) {
90
91
  let firstPageFooterForRender = hasTitlePg ? renderHfFromContentOrPm(firstPageFooterContent, firstPageFooterContentRId, hfPMs, contentWidth, hfMetricsFooter, hfOptions) : void 0;
91
92
  let headerContentByRId = renderHeaderFooterContentByRId(document?.package.headers, hfPMs, contentWidth, hfMetricsHeader, hfOptions);
92
93
  let footerContentByRId = renderHeaderFooterContentByRId(document?.package.footers, hfPMs, contentWidth, hfMetricsFooter, hfOptions);
93
- const hfExtenderContent = {
94
- headerContent: headerContentForRender,
95
- footerContent: footerContentForRender,
96
- firstPageHeaderContent: firstPageHeaderForRender,
97
- firstPageFooterContent: firstPageFooterForRender
98
- };
99
- const hfWarn = (msg) => {
100
- console.warn(`[PagedEditor] ${msg}`);
101
- };
102
- const extendForHfOverflow = computeHeaderFooterMarginExtender({
103
- ...hfExtenderContent,
104
- pageSize,
105
- warn: hfWarn
106
- });
107
- const extendForFirstPage = computeFirstPageHeaderFooterMarginExtender({
108
- ...hfExtenderContent,
109
- pageSize,
110
- warn: hfWarn
111
- });
112
- let effectiveMargins = extendForHfOverflow(margins);
113
- let effectiveFirstPageMargins = hasTitlePg ? extendForFirstPage(margins) : void 0;
114
- const sectionBreaks = newBlocks.filter((block) => block.kind === "sectionBreak");
115
- const originalSectionBreakMargins = /* @__PURE__ */ new Map();
116
- for (const sectionBreak of sectionBreaks) originalSectionBreakMargins.set(sectionBreak.id, sectionBreak.margins ? { ...sectionBreak.margins } : void 0);
117
- const restoreSectionBreakMargins = () => {
118
- for (const sectionBreak of sectionBreaks) {
119
- const originalMargins = originalSectionBreakMargins.get(sectionBreak.id);
120
- if (originalMargins === void 0) {
121
- delete sectionBreak.margins;
122
- continue;
123
- }
124
- sectionBreak.margins = { ...originalMargins };
125
- }
126
- };
127
- const applySectionBreakMargins = (content, bodyMargins) => {
128
- restoreSectionBreakMargins();
129
- extendSectionBreakMargins(sectionBreaks, {
130
- content,
131
- bodyPageSize: pageSize,
132
- bodyMargins,
133
- warn: hfWarn
134
- });
135
- };
136
- applySectionBreakMargins(hfExtenderContent, effectiveMargins);
137
94
  recordPhaseDuration("header-footer", phaseStartedAt);
138
95
  phaseStartedAt = performance.now();
139
- let bodyLayoutConfig = {
96
+ const bodyLayoutConfig = {
140
97
  pageSize,
141
- margins: effectiveMargins
98
+ margins
142
99
  };
143
100
  if (columns !== void 0) bodyLayoutConfig.columns = columns;
144
- let blockMeasureInputs = computePerBlockMeasureInputs({
101
+ const blockMeasureInputs = computePerBlockMeasureInputs({
145
102
  blocks: newBlocks,
146
103
  bodyConfig: bodyLayoutConfig,
147
104
  finalConfig: bodyLayoutConfig
148
105
  });
149
- let blockWidths = blockMeasureInputs.widths;
106
+ const blockWidths = blockMeasureInputs.widths;
150
107
  const previousArtifacts = session.artifacts;
151
108
  const incrementalResult = options.dirtyRange && !options.forceFull && previousArtifacts ? tryBuildIncrementalMeasures({
152
109
  previousBlocks: previousArtifacts.blocks,
@@ -173,19 +130,31 @@ function runLayoutPipeline(deps, state, options = {}) {
173
130
  let pageFootnoteMap = /* @__PURE__ */ new Map();
174
131
  let footnoteContentMap = /* @__PURE__ */ new Map();
175
132
  const bodyBreakType = sectionProperties?.sectionStart;
176
- const buildLayoutOpts = (nextMargins, nextFirstPageMargins) => {
133
+ const buildLayoutOpts = () => {
177
134
  const nextLayoutOpts = {
178
135
  pageSize,
179
- margins: nextMargins,
180
- pageGap
136
+ margins,
137
+ pageGap,
138
+ mirrorMargins
181
139
  };
182
- if (nextFirstPageMargins !== void 0) nextLayoutOpts.firstPageMargins = nextFirstPageMargins;
140
+ if (hasTitlePg && firstPageHeaderForRender) {
141
+ const headerBottom = (margins.header ?? 0) + (firstPageHeaderForRender.marginPushBottom ?? firstPageHeaderForRender.height);
142
+ if (headerBottom > margins.top) nextLayoutOpts.firstPageMargins = {
143
+ ...margins,
144
+ top: headerBottom
145
+ };
146
+ }
147
+ const finalSection = document?.package.document.sections?.at(-1);
148
+ if (finalSection) {
149
+ nextLayoutOpts.finalPageSize = getPageSize(finalSection.properties);
150
+ nextLayoutOpts.finalMargins = getMargins(finalSection.properties);
151
+ }
183
152
  if (columns !== void 0) nextLayoutOpts.columns = columns;
184
153
  if (bodyBreakType !== void 0) nextLayoutOpts.bodyBreakType = bodyBreakType;
185
154
  if (sectionHeaderFooterRefs !== void 0) nextLayoutOpts.sectionHeaderFooterRefs = sectionHeaderFooterRefs;
186
155
  return nextLayoutOpts;
187
156
  };
188
- const layoutOpts = buildLayoutOpts(effectiveMargins, effectiveFirstPageMargins);
157
+ const layoutOpts = buildLayoutOpts();
189
158
  let layoutOptsUsed = layoutOpts;
190
159
  if (hasFootnotes) {
191
160
  footnoteContentMap = buildFootnoteContentMap(documentFootnotes, footnoteRefs, contentWidth, (() => {
@@ -282,57 +251,8 @@ function runLayoutPipeline(deps, state, options = {}) {
282
251
  firstPageFooterForRender = hasTitlePg ? renderHfFromContentOrPm(firstPageFooterContent, firstPageFooterContentRId, hfPMs, contentWidth, hfMetricsFooter, finalHfOptions) : void 0;
283
252
  headerContentByRId = renderHeaderFooterContentByRId(document?.package.headers, hfPMs, contentWidth, hfMetricsHeader, finalHfOptions);
284
253
  footerContentByRId = renderHeaderFooterContentByRId(document?.package.footers, hfPMs, contentWidth, hfMetricsFooter, finalHfOptions);
285
- return {
286
- headerContent: headerContentForRender,
287
- footerContent: footerContentForRender,
288
- firstPageHeaderContent: firstPageHeaderForRender,
289
- firstPageFooterContent: firstPageFooterForRender
290
- };
291
254
  };
292
- const MAX_HEADER_FOOTER_STABILIZATION_PASSES = 3;
293
- for (let pass = 0; pass < MAX_HEADER_FOOTER_STABILIZATION_PASSES; pass++) {
294
- const finalHfExtenderContent = rebuildHeaderFooterForLayout();
295
- const finalEffectiveMargins = computeHeaderFooterMarginExtender({
296
- ...finalHfExtenderContent,
297
- pageSize,
298
- warn: hfWarn
299
- })(margins);
300
- const finalEffectiveFirstPageMargins = hasTitlePg ? computeFirstPageHeaderFooterMarginExtender({
301
- ...finalHfExtenderContent,
302
- pageSize,
303
- warn: hfWarn
304
- })(margins) : void 0;
305
- if (!pageMarginsEqual(effectiveMargins, finalEffectiveMargins) || !optionalPageMarginsEqual(effectiveFirstPageMargins, finalEffectiveFirstPageMargins)) {
306
- effectiveMargins = finalEffectiveMargins;
307
- effectiveFirstPageMargins = finalEffectiveFirstPageMargins;
308
- applySectionBreakMargins(finalHfExtenderContent, effectiveMargins);
309
- bodyLayoutConfig = {
310
- pageSize,
311
- margins: effectiveMargins
312
- };
313
- if (columns !== void 0) bodyLayoutConfig.columns = columns;
314
- blockMeasureInputs = computePerBlockMeasureInputs({
315
- blocks: newBlocks,
316
- bodyConfig: bodyLayoutConfig,
317
- finalConfig: bodyLayoutConfig
318
- });
319
- blockWidths = blockMeasureInputs.widths;
320
- newMeasures = measureBlocks(newBlocks, blockWidths, blockMeasureInputs.marginTops, {
321
- pageHeight: blockMeasureInputs.pageHeights,
322
- marginBottom: blockMeasureInputs.marginBottoms
323
- });
324
- pendingArtifacts = {
325
- blocks: newBlocks,
326
- blockWidths,
327
- measures: newMeasures
328
- };
329
- outcome.measures = newMeasures;
330
- relayoutWithCurrentMeasures(buildLayoutOpts(effectiveMargins, effectiveFirstPageMargins));
331
- stabilizeFieldWidths();
332
- continue;
333
- }
334
- break;
335
- }
255
+ rebuildHeaderFooterForLayout();
336
256
  outcome.layout = newLayout;
337
257
  recordPhaseDuration("layout-document", phaseStartedAt);
338
258
  if (pagesContainer && painter) {
@@ -18,7 +18,8 @@ type SchedulerClock = {
18
18
  declare const browserClock: SchedulerClock;
19
19
  type LayoutSchedulerConfig<TState> = {
20
20
  runLayout: RunLayout<TState>; /** Quiet-window debounce before an interactive layout pass. */
21
- debounceMs: number; /** Hard cap on latency from the first edit in a burst. */
21
+ debounceMs: number; /** Run the first edit after a max-delay-sized idle period on the next frame. */
22
+ leadingFrame?: boolean; /** Hard cap on latency from the first edit in a burst. */
22
23
  maxDelayMs: number;
23
24
  /**
24
25
  * Timing source. Pass `browserClock` in the browser; a host injects its own
@@ -9,6 +9,7 @@ const browserClock = {
9
9
  };
10
10
  const createLayoutScheduler = (config) => {
11
11
  const clock = config.clock;
12
+ let lastRunAt = null;
12
13
  let pending = null;
13
14
  const flushPending = () => {
14
15
  if (!pending || pending.rafId !== null) return;
@@ -17,6 +18,7 @@ const createLayoutScheduler = (config) => {
17
18
  const latest = pending;
18
19
  pending = null;
19
20
  if (!latest) return;
21
+ lastRunAt = clock.now();
20
22
  const options = { reason: "transaction" };
21
23
  if (latest.dirtyRange) options.dirtyRange = latest.dirtyRange;
22
24
  config.runLayout(latest.state, options);
@@ -45,6 +47,10 @@ const createLayoutScheduler = (config) => {
45
47
  timerId: null
46
48
  };
47
49
  pending = next;
50
+ if (config.leadingFrame && (lastRunAt === null || clock.now() - lastRunAt >= config.maxDelayMs)) {
51
+ flushPending();
52
+ return;
53
+ }
48
54
  armTimer(next);
49
55
  },
50
56
  dispose() {
@@ -0,0 +1,122 @@
1
+ import { FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAITextRangeHandle } from "./ai-edits/types.js";
2
+ import { FolioAIEditView } from "./ai-edits/apply.js";
3
+
4
+ //#region src/document-operations.d.ts
5
+ declare const FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION: 1;
6
+ declare const FOLIO_DOCUMENT_OPERATION_TYPES: readonly ["replaceInBlock", "replaceRange", "commentOnRange", "formatRange", "insertAfterBlock", "insertBeforeBlock", "replaceBlock", "deleteBlock", "commentOnBlock", "insertSignatureTable"];
7
+ declare const FOLIO_DOCUMENT_OPERATION_MODES: readonly ["direct", "tracked-changes"];
8
+ declare const FOLIO_DOCUMENT_OPERATION_STORIES: readonly ["main"];
9
+ declare const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS: readonly ["blockTextHash"];
10
+ declare const FOLIO_DOCUMENT_OPERATION_BATCH_MODES: readonly ["best-effort", "atomic"];
11
+ type FolioDocumentOperation = FolioAIEditOperation;
12
+ type FolioDocumentOperationMode = FolioAIEditApplyMode;
13
+ type FolioDocumentOperationPrecondition = FolioAIEditPrecondition;
14
+ type FolioDocumentOperationType = FolioDocumentOperation["type"];
15
+ declare const FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE: Readonly<{
16
+ readonly replaceInBlock: readonly ["direct", "tracked-changes"];
17
+ readonly replaceRange: readonly ["direct", "tracked-changes"];
18
+ readonly commentOnRange: readonly ["direct", "tracked-changes"];
19
+ readonly formatRange: readonly ["direct"];
20
+ readonly insertAfterBlock: readonly ["direct", "tracked-changes"];
21
+ readonly insertBeforeBlock: readonly ["direct", "tracked-changes"];
22
+ readonly replaceBlock: readonly ["direct", "tracked-changes"];
23
+ readonly deleteBlock: readonly ["direct", "tracked-changes"];
24
+ readonly commentOnBlock: readonly ["direct", "tracked-changes"];
25
+ readonly insertSignatureTable: readonly ["direct"];
26
+ }>;
27
+ type FolioDocumentOperationCapabilities = {
28
+ readonly version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
29
+ readonly operationTypes: typeof FOLIO_DOCUMENT_OPERATION_TYPES;
30
+ readonly modes: typeof FOLIO_DOCUMENT_OPERATION_MODES;
31
+ readonly modesByOperationType: typeof FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE;
32
+ readonly batchModes: typeof FOLIO_DOCUMENT_OPERATION_BATCH_MODES;
33
+ readonly dryRun: true;
34
+ readonly preconditions: typeof FOLIO_DOCUMENT_OPERATION_PRECONDITIONS;
35
+ readonly stories: typeof FOLIO_DOCUMENT_OPERATION_STORIES;
36
+ };
37
+ declare const getFolioDocumentOperationCapabilities: () => FolioDocumentOperationCapabilities;
38
+ declare const isFolioDocumentOperationModeSupported: (operationType: FolioDocumentOperationType, mode: FolioDocumentOperationMode) => boolean;
39
+ declare const isSupportedFolioDocumentOperationVersion: (value: unknown) => value is typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
40
+ declare const UnsupportedFolioDocumentOperationVersionError_base: import("better-result").TaggedErrorClass<"UnsupportedFolioDocumentOperationVersionError", {
41
+ message: string;
42
+ receivedVersion: unknown;
43
+ }>;
44
+ declare class UnsupportedFolioDocumentOperationVersionError extends UnsupportedFolioDocumentOperationVersionError_base {}
45
+ declare const InvalidFolioDocumentOperationBatchError_base: import("better-result").TaggedErrorClass<"InvalidFolioDocumentOperationBatchError", {
46
+ message: string;
47
+ path: string;
48
+ reason: string;
49
+ }>;
50
+ declare class InvalidFolioDocumentOperationBatchError extends InvalidFolioDocumentOperationBatchError_base {}
51
+ declare const assertSupportedFolioDocumentOperationVersion: (value: unknown) => typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
52
+ type FolioDocumentOperationBatch = {
53
+ readonly version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
54
+ operations: FolioDocumentOperation[];
55
+ mode?: FolioDocumentOperationMode;
56
+ atomic?: boolean;
57
+ dryRun?: boolean;
58
+ };
59
+ declare const parseFolioDocumentOperationBatch: (value: unknown) => FolioDocumentOperationBatch;
60
+ type FolioDocumentOperationStatus = "committed" | "previewed" | "rejected";
61
+ type FolioDocumentOperationRecovery = "refreshDocument" | "narrowMatch" | "changeMode" | "changeTarget" | "removeOperation" | "inspectBatch";
62
+ type FolioDocumentOperationIssue = {
63
+ operationId: string;
64
+ operationIndex: number;
65
+ path: `$.operations[${number}]`;
66
+ code: FolioAIEditSkippedOperation["reason"];
67
+ retryable: boolean;
68
+ recovery: FolioDocumentOperationRecovery;
69
+ };
70
+ /** One typed target affected by a successfully applied document operation. */
71
+ type FolioDocumentOperationAffectedTarget = {
72
+ type: "block";
73
+ story: "main";
74
+ blockId: string;
75
+ effect: "updated" | "deleted" | "commented";
76
+ } | {
77
+ type: "textRange";
78
+ range: FolioAITextRangeHandle;
79
+ effect: "formatted" | "commented";
80
+ } | {
81
+ type: "insertion";
82
+ story: "main";
83
+ anchorBlockId: string;
84
+ position: "before" | "after";
85
+ content: "block" | "signatureTable";
86
+ } | {
87
+ type: "comment";
88
+ commentId: number;
89
+ };
90
+ /** Input-ordered effect receipt for one successfully applied operation. */
91
+ type FolioDocumentOperationReceipt = {
92
+ operationId: string;
93
+ operationIndex: number;
94
+ affected: FolioDocumentOperationAffectedTarget[];
95
+ };
96
+ type FolioDocumentOperationResult = {
97
+ version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
98
+ status: FolioDocumentOperationStatus;
99
+ applied: FolioAIEditAppliedOperation[];
100
+ skipped: FolioAIEditSkippedOperation[];
101
+ issues: FolioDocumentOperationIssue[]; /** Successful effects in input-operation order; skipped operations are omitted. */
102
+ receipts: FolioDocumentOperationReceipt[];
103
+ };
104
+ declare const getFolioDocumentOperationIssues: (operations: readonly FolioDocumentOperation[], skipped: readonly FolioAIEditSkippedOperation[]) => FolioDocumentOperationIssue[];
105
+ /** Build deterministic affected-target receipts from operations and their applied entries. */
106
+ declare const getFolioDocumentOperationReceipts: (operations: readonly FolioDocumentOperation[], applied: readonly FolioAIEditAppliedOperation[]) => FolioDocumentOperationReceipt[];
107
+ type ApplyFolioDocumentOperationsOptions = {
108
+ view: FolioAIEditView;
109
+ snapshot: FolioAIEditSnapshot;
110
+ batch: FolioDocumentOperationBatch;
111
+ author?: string;
112
+ createCommentId?: (text: string) => number;
113
+ };
114
+ declare const applyFolioDocumentOperations: ({
115
+ view,
116
+ snapshot,
117
+ batch,
118
+ author,
119
+ createCommentId
120
+ }: ApplyFolioDocumentOperationsOptions) => FolioDocumentOperationResult;
121
+ //#endregion
122
+ export { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationType, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };