@stll/folio-core 0.8.0 → 0.9.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 (152) hide show
  1. package/README.md +39 -0
  2. package/dist/ai-edits/apply.js +18 -4
  3. package/dist/ai-edits/headless.d.ts +31 -4
  4. package/dist/ai-edits/headless.js +148 -35
  5. package/dist/ai-edits/index.d.ts +3 -3
  6. package/dist/ai-edits/index.js +2 -1
  7. package/dist/ai-edits/snapshot.js +40 -3
  8. package/dist/ai-edits/types.d.ts +2 -1
  9. package/dist/compat/eigenpal.d.ts +6 -4
  10. package/dist/compat/eigenpal.js +5 -3
  11. package/dist/controller/latestRequestGate.d.ts +13 -0
  12. package/dist/controller/latestRequestGate.js +16 -0
  13. package/dist/controller/layoutPipeline.js +12 -1
  14. package/dist/document-operations.d.ts +10 -3
  15. package/dist/document-operations.js +29 -14
  16. package/dist/docx/blockContentParser.js +2 -2
  17. package/dist/docx/commentParser.js +1 -1
  18. package/dist/docx/compatibility.d.ts +3 -7
  19. package/dist/docx/compatibility.js +0 -11
  20. package/dist/docx/encryption/agileDecryption.js +1 -1
  21. package/dist/docx/encryption/encryptionInfo.js +1 -1
  22. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  23. package/dist/docx/fontTableParser.d.ts +6 -0
  24. package/dist/docx/fontTableParser.js +72 -0
  25. package/dist/docx/groupDrawingParser.js +7 -2
  26. package/dist/docx/headerFooterParser.js +1 -1
  27. package/dist/docx/normalizeBaseDirection.js +1 -1
  28. package/dist/docx/numberingParser.js +1 -0
  29. package/dist/docx/paragraphParser.js +12 -1
  30. package/dist/docx/parser.d.ts +1 -1
  31. package/dist/docx/parser.js +12 -9
  32. package/dist/docx/rezip.js +70 -5
  33. package/dist/docx/runConsolidator.js +4 -0
  34. package/dist/docx/runParser.js +19 -1
  35. package/dist/docx/selectiveSave.js +3 -3
  36. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  37. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  38. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  39. package/dist/docx/serializer/paragraphSerializer.js +4 -0
  40. package/dist/docx/serializer/runSerializer.js +7 -0
  41. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  42. package/dist/docx/serializer/settingsSerializer.js +16 -0
  43. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  44. package/dist/docx/serializer/stylesSerializer.js +56 -0
  45. package/dist/docx/serializer/tableSerializer.js +25 -26
  46. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  47. package/dist/docx/serializer/themeSerializer.js +36 -0
  48. package/dist/docx/settingsParser.js +46 -0
  49. package/dist/docx/styleParser.js +26 -1
  50. package/dist/docx/tableParser.js +11 -1
  51. package/dist/docx/vmlImageParser.js +1 -0
  52. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  53. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  54. package/dist/index.d.ts +6 -4
  55. package/dist/index.js +5 -3
  56. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  57. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  58. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  59. package/dist/layout-bridge/convert/headerFooterLayout.js +6 -1
  60. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  61. package/dist/layout-bridge/convert/toFlowBlocks.js +51 -14
  62. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  63. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  64. package/dist/layout-engine/index.js +36 -28
  65. package/dist/layout-engine/measure/cache.js +11 -3
  66. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  67. package/dist/layout-engine/measure/index.d.ts +2 -1
  68. package/dist/layout-engine/measure/index.js +2 -1
  69. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  70. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  71. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  72. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  73. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  74. package/dist/layout-engine/measure/listMarkerWidth.js +12 -6
  75. package/dist/layout-engine/measure/measureBlocks.d.ts +1 -2
  76. package/dist/layout-engine/measure/measureBlocks.js +6 -36
  77. package/dist/layout-engine/measure/measureContainer.js +32 -9
  78. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  79. package/dist/layout-engine/measure/measureParagraph.js +335 -72
  80. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  81. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  82. package/dist/layout-engine/measure/measureWorker.js +8 -19
  83. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  84. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  85. package/dist/layout-engine/measure/tableCellFloating.js +37 -29
  86. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  87. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  88. package/dist/layout-engine/measure/tableCellGrid.js +1 -1
  89. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  90. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  91. package/dist/layout-engine/renderedBreakReconciliation.d.ts +6 -1
  92. package/dist/layout-engine/renderedBreakReconciliation.js +34 -11
  93. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  94. package/dist/layout-engine/tableRowBreak.js +62 -29
  95. package/dist/layout-engine/types.d.ts +40 -7
  96. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  97. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  98. package/dist/layout-painter/borderStroke.d.ts +23 -0
  99. package/dist/layout-painter/borderStroke.js +39 -0
  100. package/dist/layout-painter/renderImage.d.ts +5 -4
  101. package/dist/layout-painter/renderImage.js +18 -4
  102. package/dist/layout-painter/renderPage.d.ts +2 -27
  103. package/dist/layout-painter/renderPage.js +13 -99
  104. package/dist/layout-painter/renderParagraph.js +41 -23
  105. package/dist/layout-painter/renderTable.d.ts +2 -0
  106. package/dist/layout-painter/renderTable.js +223 -43
  107. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  108. package/dist/managers/DocumentLoaderManager.js +2 -2
  109. package/dist/prosemirror/attrs/index.d.ts +4 -2
  110. package/dist/prosemirror/attrs/index.js +21 -2
  111. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  112. package/dist/prosemirror/conversion/fromProseDoc.js +33 -18
  113. package/dist/prosemirror/conversion/toProseDoc.js +50 -18
  114. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  115. package/dist/prosemirror/extensions/core/ParagraphExtension.js +7 -2
  116. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  117. package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
  118. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  119. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  120. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  121. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  122. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  123. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  124. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  125. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  126. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  127. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  128. package/dist/prosemirror/schema/index.d.ts +2 -2
  129. package/dist/prosemirror/schema/marks.d.ts +6 -1
  130. package/dist/prosemirror/schema/nodes.d.ts +13 -11
  131. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  132. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  133. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  134. package/dist/prosemirror/validation.js +4 -1
  135. package/dist/redline.js +13 -3
  136. package/dist/server.d.ts +6 -3
  137. package/dist/server.js +5 -2
  138. package/dist/style-sets/extract.d.ts +35 -0
  139. package/dist/style-sets/extract.js +123 -0
  140. package/dist/style-sets/stellaStyle.d.ts +13 -0
  141. package/dist/style-sets/stellaStyle.js +516 -0
  142. package/dist/style-sets/types.d.ts +31 -0
  143. package/dist/style-sets/types.js +5 -0
  144. package/dist/utils/createDocument.d.ts +12 -2
  145. package/dist/utils/createDocument.js +42 -31
  146. package/dist/utils/fontResolver.js +6 -6
  147. package/dist/utils/formatToStyle.js +1 -1
  148. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  149. package/dist/utils/paragraphFormattingMerge.js +14 -4
  150. package/package.json +5 -1
  151. package/dist/docx/capabilities.d.ts +0 -41
  152. package/dist/docx/capabilities.js +0 -322
package/README.md CHANGED
@@ -32,6 +32,45 @@ bun add @stll/folio-core
32
32
  | `@stll/folio-core/server` | DOM-free helpers for server-side use (block-id derivation, document creation, re-zip) |
33
33
  | `@stll/folio-core/*` | the source-mirrored module tree (e.g. `@stll/folio-core/types/document`, `@stll/folio-core/prosemirror/schema`) for adapters that need lower-level building blocks |
34
34
 
35
+ ## New documents and reusable style sets
36
+
37
+ Create a legal-shaped blank document with the built-in preset:
38
+
39
+ ```ts
40
+ import { createDocx, createEmptyDocument, createStellaStyleDocumentPreset } from "@stll/folio-core";
41
+
42
+ const document = createEmptyDocument({
43
+ preset: createStellaStyleDocumentPreset(),
44
+ });
45
+ const docx = await createDocx(document);
46
+ ```
47
+
48
+ Style sets are content-free JSON values. Inspect a source file before presenting
49
+ styles for selection, extract the selected dependency closure, persist the
50
+ result, and load it into any later document:
51
+
52
+ ```ts
53
+ import {
54
+ createEmptyDocument,
55
+ extractDocumentStyleSetFromDocx,
56
+ inspectDocumentStylesFromDocx,
57
+ } from "@stll/folio-core";
58
+
59
+ const catalog = await inspectDocumentStylesFromDocx(sourceDocx);
60
+ const styleSet = await extractDocumentStyleSetFromDocx(sourceDocx, {
61
+ name: "Firm contract styles",
62
+ styleIds: catalog.styles
63
+ .filter(({ role }) => role === "default" || role === "quick")
64
+ .map(({ styleId }) => styleId),
65
+ });
66
+
67
+ const document = createEmptyDocument({ styleSet });
68
+ ```
69
+
70
+ Extraction excludes document content, metadata, relationships, media, comments,
71
+ and revision data. It keeps only the selected styles and the numbering, theme,
72
+ font-table, and settings data required to reproduce their formatting.
73
+
35
74
  ## License
36
75
 
37
76
  [Apache-2.0](./LICENSE)
@@ -678,10 +678,24 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
678
678
  }
679
679
  if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
680
680
  const range = getTextRangeFromCleanBlock(cleanBlock);
681
- if (!range) return {
682
- type: "skip",
683
- reason: "unsupportedBlock"
684
- };
681
+ if (!range) {
682
+ const insertionPoint = cleanBlock.offsets.at(0);
683
+ if (operation.type === "replaceBlock" && currentText.length === 0 && operation.text.length > 0 && insertionPoint !== void 0) return {
684
+ type: "resolved",
685
+ operation: {
686
+ operation,
687
+ from: insertionPoint,
688
+ to: insertionPoint,
689
+ blockFrom,
690
+ blockTo,
691
+ blockNode
692
+ }
693
+ };
694
+ return {
695
+ type: "skip",
696
+ reason: "unsupportedBlock"
697
+ };
698
+ }
685
699
  if (operation.type === "replaceBlock" && operation.text === currentText) return {
686
700
  type: "skip",
687
701
  reason: "noopOperation"
@@ -39,10 +39,22 @@ type FolioDocumentStoryHandle = {
39
39
  type: "footnote" | "endnote";
40
40
  noteId: number;
41
41
  };
42
+ type FolioEditableDocumentStoryHandle = Exclude<FolioDocumentStoryHandle, {
43
+ type: "footnote" | "endnote";
44
+ }>;
42
45
  type FolioDocumentStory = {
43
46
  handle: FolioDocumentStoryHandle;
44
47
  text: string;
45
48
  };
49
+ declare const FolioDocumentStoryNotFoundError_base: import("better-result").TaggedErrorClass<"FolioDocumentStoryNotFoundError", {
50
+ message: string;
51
+ story: FolioEditableDocumentStoryHandle;
52
+ }>;
53
+ declare class FolioDocumentStoryNotFoundError extends FolioDocumentStoryNotFoundError_base {}
54
+ type FolioApplyDocumentOperationsToStoryOptions = FolioApplyDocumentOperationsOptions & {
55
+ story: FolioEditableDocumentStoryHandle;
56
+ batch: FolioDocumentOperationBatch;
57
+ };
46
58
  /** Filter for {@link FolioDocxReviewer.getChanges}. */
47
59
  type FolioReviewChangeFilter = {
48
60
  author?: string;
@@ -97,6 +109,7 @@ declare class FolioDocxReviewer {
97
109
  private readonly baseDocument;
98
110
  private readonly originalBuffer;
99
111
  private state;
112
+ private readonly headerFooterStoryStates;
100
113
  private readonly createdComments;
101
114
  private readonly documentOperationUndoEntries;
102
115
  /**
@@ -116,6 +129,8 @@ declare class FolioDocxReviewer {
116
129
  * the anchor map the apply layer resolves operations against.
117
130
  */
118
131
  snapshot(): FolioAIEditSnapshot;
132
+ /** Snapshot one editable story into stable, operation-ready blocks. */
133
+ snapshotStory(story: FolioEditableDocumentStoryHandle): FolioAIEditSnapshot | null;
119
134
  /**
120
135
  * Apply operations against the current state. Reuses the live-editor applier
121
136
  * verbatim via a headless `{ state, dispatch }` seam; the resulting state
@@ -129,6 +144,12 @@ declare class FolioDocxReviewer {
129
144
  * edit semantics.
130
145
  */
131
146
  applyDocumentOperations(batch: FolioDocumentOperationBatch, options?: FolioApplyDocumentOperationsOptions): FolioDocumentOperationResult;
147
+ /** Apply a versioned operation batch to the main story, a header, or a footer. */
148
+ applyDocumentOperationsToStory({
149
+ story,
150
+ batch,
151
+ snapshot
152
+ }: FolioApplyDocumentOperationsToStoryOptions): FolioDocumentOperationResult;
132
153
  private applyDocumentOperationsInternal;
133
154
  /** Undo the latest unchanged document-operation batch and its created comments. */
134
155
  undoDocumentOperations(undoHandle: FolioDocumentOperationUndoHandle): FolioDocumentOperationUndoResult;
@@ -153,9 +174,9 @@ declare class FolioDocxReviewer {
153
174
  /**
154
175
  * Header / footer and footnote / endnote text as labeled, LLM-ready lines,
155
176
  * one per non-empty part: `[header default] …`, `[footer default] …`,
156
- * `[footnote #N] …`, `[endnote #N] …`. Read-only note bodies are outside
157
- * the reviewer's body-only apply scope, so this reflects the parsed source.
158
- * Empty parts and separator notes are omitted.
177
+ * `[footnote #N] …`, `[endnote #N] …`. Header and footer lines reflect
178
+ * in-memory edits; note bodies reflect the parsed source. Empty parts and
179
+ * separator notes are omitted.
159
180
  */
160
181
  getNotesAsText(): string;
161
182
  /** Discover every readable document story through a typed, serializable handle. */
@@ -239,6 +260,12 @@ declare class FolioDocxReviewer {
239
260
  * is the graceful handling — no separate error surface is needed here.
240
261
  */
241
262
  private trySelectiveSave;
263
+ private getEditableStoryState;
264
+ private requireEditableStoryState;
265
+ private setEditableStoryState;
266
+ private getHeaderFooterStory;
267
+ private getHeaderFooterStoryText;
268
+ private mergeEditedHeaderFooterStories;
242
269
  /**
243
270
  * Count every tracked change an accept-all / reject-all sweep resolves: the
244
271
  * inline insertion / deletion groups {@link getChanges} enumerates, plus the
@@ -283,4 +310,4 @@ type ApplyFolioAIEditsToBufferResult = FolioAIEditApplyResult & {
283
310
  */
284
311
  declare const applyFolioAIEditsToBuffer: (buffer: ArrayBuffer, operations: FolioAIEditOperation[], options?: ApplyFolioAIEditsToBufferOptions) => Promise<ApplyFolioAIEditsToBufferResult>;
285
312
  //#endregion
286
- export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FolioApplyDocumentOperationsOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocxReviewer, FolioDocxReviewerOptions, FolioGetContentAsTextOptions, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, applyFolioAIEditsToBuffer };
313
+ export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, applyFolioAIEditsToBuffer };
@@ -2,18 +2,19 @@ import { buildAnnotatedBlockText } from "./clean-text.js";
2
2
  import { createFolioAIEditSnapshot, normalizeFolioAIBlockText } from "./snapshot.js";
3
3
  import { applyFolioDocumentOperations } from "../document-operations.js";
4
4
  import { getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
5
+ import { getHeaderFooterText } from "../docx/headerFooterParser.js";
6
+ import { parseDocx } from "../docx/parser.js";
5
7
  import { deterministicHexId } from "../utils/hexId.js";
6
8
  import { repackDocx } from "../docx/rezip.js";
7
9
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
8
10
  import { createReply } from "../docx/replyToComment.js";
9
11
  import { attemptSelectiveSave } from "../docx/selectiveSave.js";
10
- import { getHeaderFooterText } from "../docx/headerFooterParser.js";
11
- import { parseDocx } from "../docx/parser.js";
12
12
  import { acceptAIEditRevision, acceptAllChanges, rejectAIEditRevision, rejectAllChanges } from "../prosemirror/commands/comments.js";
13
- import { updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
13
+ import { proseDocToBlocks, updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
14
14
  import { getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges, ignoreTrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
15
15
  import { schema, singletonManager } from "../prosemirror/schema/index.js";
16
- import { toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
16
+ import { headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
17
+ import { TaggedError } from "better-result";
17
18
  import { EditorState } from "prosemirror-state";
18
19
  //#region src/ai-edits/headless.ts
19
20
  /**
@@ -33,8 +34,8 @@ import { EditorState } from "prosemirror-state";
33
34
  * paragraphs in `document.xml` (leaving every untouched part byte-exact), with
34
35
  * a full repack as the fallback for structural edits.
35
36
  *
36
- * Scope: BODY blocks. Footnote / endnote note-body apply is intentionally out
37
- * of scope here and can build on the note serialization work separately.
37
+ * Scope: main, header, and footer blocks. Footnote and endnote bodies build on
38
+ * the note serialization path separately.
38
39
  */
39
40
  /**
40
41
  * Standalone comment-id allocator for the reviewer. The React editor uses
@@ -114,6 +115,9 @@ const ensureDeterministicParaIdsInState = (state) => {
114
115
  tr.setMeta("addToHistory", false);
115
116
  return state.apply(tr);
116
117
  };
118
+ var FolioDocumentStoryNotFoundError = class extends TaggedError("FolioDocumentStoryNotFoundError")() {};
119
+ const MAIN_STORY = Object.freeze({ type: "main" });
120
+ const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
117
121
  /**
118
122
  * One LLM-ready line for a block: `[<blockId>] text`, with an `(h<level>)` tag
119
123
  * for headings and the list marker for list items, so a model can copy the
@@ -162,6 +166,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
162
166
  baseDocument;
163
167
  originalBuffer;
164
168
  state;
169
+ headerFooterStoryStates = /* @__PURE__ */ new Map();
165
170
  createdComments = [];
166
171
  documentOperationUndoEntries = [];
167
172
  /**
@@ -206,6 +211,11 @@ var FolioDocxReviewer = class FolioDocxReviewer {
206
211
  snapshot() {
207
212
  return createFolioAIEditSnapshot(this.state.doc);
208
213
  }
214
+ /** Snapshot one editable story into stable, operation-ready blocks. */
215
+ snapshotStory(story) {
216
+ const state = this.getEditableStoryState(story);
217
+ return state ? createFolioAIEditSnapshot(state.doc) : null;
218
+ }
209
219
  /**
210
220
  * Apply operations against the current state. Reuses the live-editor applier
211
221
  * verbatim via a headless `{ state, dispatch }` seam; the resulting state
@@ -213,10 +223,15 @@ var FolioDocxReviewer = class FolioDocxReviewer {
213
223
  */
214
224
  applyOperations(operations, options = {}) {
215
225
  const { applied, skipped } = this.applyDocumentOperationsInternal({
216
- version: 1,
217
- operations,
218
- mode: options.mode ?? "tracked-changes"
219
- }, { ...options.snapshot !== void 0 && { snapshot: options.snapshot } }, false);
226
+ story: MAIN_STORY,
227
+ batch: {
228
+ version: 1,
229
+ operations,
230
+ mode: options.mode ?? "tracked-changes"
231
+ },
232
+ ...options.snapshot !== void 0 && { snapshot: options.snapshot },
233
+ createUndoEntry: false
234
+ });
220
235
  return {
221
236
  applied,
222
237
  skipped
@@ -229,21 +244,36 @@ var FolioDocxReviewer = class FolioDocxReviewer {
229
244
  * edit semantics.
230
245
  */
231
246
  applyDocumentOperations(batch, options = {}) {
232
- return this.applyDocumentOperationsInternal(batch, options, true);
247
+ return this.applyDocumentOperationsInternal({
248
+ story: MAIN_STORY,
249
+ batch,
250
+ ...options.snapshot !== void 0 && { snapshot: options.snapshot },
251
+ createUndoEntry: true
252
+ });
253
+ }
254
+ /** Apply a versioned operation batch to the main story, a header, or a footer. */
255
+ applyDocumentOperationsToStory({ story, batch, snapshot }) {
256
+ return this.applyDocumentOperationsInternal({
257
+ story,
258
+ batch,
259
+ ...snapshot !== void 0 && { snapshot },
260
+ createUndoEntry: true
261
+ });
233
262
  }
234
- applyDocumentOperationsInternal(batch, options, createUndoEntry) {
235
- const beforeState = this.state;
263
+ applyDocumentOperationsInternal({ story, batch, snapshot, createUndoEntry }) {
264
+ const beforeState = this.requireEditableStoryState(story);
236
265
  const createdCommentsLengthBefore = this.createdComments.length;
237
266
  const view = {
238
- state: this.state,
267
+ state: beforeState,
239
268
  dispatch: (transaction) => {
240
269
  view.state = view.state.apply(transaction);
241
270
  }
242
271
  };
243
272
  const result = applyFolioDocumentOperations({
244
273
  view,
245
- snapshot: options.snapshot ?? this.snapshot(),
274
+ snapshot: snapshot ?? createFolioAIEditSnapshot(beforeState.doc),
246
275
  batch,
276
+ story: story.type === "main" ? "main" : story,
247
277
  author: this.author,
248
278
  createCommentId: (text) => {
249
279
  const comment = createReviewerComment(text, this.author);
@@ -255,11 +285,12 @@ var FolioDocxReviewer = class FolioDocxReviewer {
255
285
  id: `headless-${String(undoHandleCursor++)}`
256
286
  }) }
257
287
  });
258
- this.state = view.state;
288
+ this.setEditableStoryState(story, view.state);
259
289
  if (result.undoHandle !== null) this.documentOperationUndoEntries.push({
260
290
  undoHandle: result.undoHandle,
291
+ story,
261
292
  beforeState,
262
- afterState: this.state,
293
+ afterState: view.state,
263
294
  createdCommentsLengthBefore,
264
295
  createdCommentsLengthAfter: this.createdComments.length
265
296
  });
@@ -284,12 +315,12 @@ var FolioDocxReviewer = class FolioDocxReviewer {
284
315
  undoHandle,
285
316
  reason: "unknownHandle"
286
317
  };
287
- if (this.state !== entry.afterState || this.createdComments.length !== entry.createdCommentsLengthAfter) return {
318
+ if (this.requireEditableStoryState(entry.story) !== entry.afterState || this.createdComments.length !== entry.createdCommentsLengthAfter) return {
288
319
  status: "rejected",
289
320
  undoHandle,
290
321
  reason: "documentChanged"
291
322
  };
292
- this.state = entry.beforeState;
323
+ this.setEditableStoryState(entry.story, entry.beforeState);
293
324
  this.createdComments.length = entry.createdCommentsLengthBefore;
294
325
  this.documentOperationUndoEntries.pop();
295
326
  return {
@@ -331,17 +362,21 @@ var FolioDocxReviewer = class FolioDocxReviewer {
331
362
  /**
332
363
  * Header / footer and footnote / endnote text as labeled, LLM-ready lines,
333
364
  * one per non-empty part: `[header default] …`, `[footer default] …`,
334
- * `[footnote #N] …`, `[endnote #N] …`. Read-only note bodies are outside
335
- * the reviewer's body-only apply scope, so this reflects the parsed source.
336
- * Empty parts and separator notes are omitted.
365
+ * `[footnote #N] …`, `[endnote #N] …`. Header and footer lines reflect
366
+ * in-memory edits; note bodies reflect the parsed source. Empty parts and
367
+ * separator notes are omitted.
337
368
  */
338
369
  getNotesAsText() {
339
370
  const pkg = this.baseDocument.package;
340
371
  const lines = [];
341
372
  const pushHeaderFooter = (map, label) => {
342
373
  if (!map) return;
343
- for (const hf of map.values()) {
344
- const text = normalizeFolioAIBlockText(getHeaderFooterText(hf));
374
+ for (const [relationshipId, hf] of map) {
375
+ const handle = {
376
+ type: label,
377
+ relationshipId
378
+ };
379
+ const text = this.getHeaderFooterStoryText(handle, hf);
345
380
  if (text.length > 0) lines.push(`[${label} ${hf.hdrFtrType}] ${text}`);
346
381
  }
347
382
  };
@@ -366,20 +401,26 @@ var FolioDocxReviewer = class FolioDocxReviewer {
366
401
  handle: { type: "main" },
367
402
  text: this.getContentAsText()
368
403
  }];
369
- for (const [relationshipId, header] of pkg.headers ?? []) stories.push({
370
- handle: {
404
+ for (const [relationshipId, header] of pkg.headers ?? []) {
405
+ const handle = {
371
406
  type: "header",
372
407
  relationshipId
373
- },
374
- text: normalizeFolioAIBlockText(getHeaderFooterText(header))
375
- });
376
- for (const [relationshipId, footer] of pkg.footers ?? []) stories.push({
377
- handle: {
408
+ };
409
+ stories.push({
410
+ handle,
411
+ text: this.getHeaderFooterStoryText(handle, header)
412
+ });
413
+ }
414
+ for (const [relationshipId, footer] of pkg.footers ?? []) {
415
+ const handle = {
378
416
  type: "footer",
379
417
  relationshipId
380
- },
381
- text: normalizeFolioAIBlockText(getHeaderFooterText(footer))
382
- });
418
+ };
419
+ stories.push({
420
+ handle,
421
+ text: this.getHeaderFooterStoryText(handle, footer)
422
+ });
423
+ }
383
424
  for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) stories.push({
384
425
  handle: {
385
426
  type: "footnote",
@@ -537,6 +578,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
537
578
  /** The current document model with edits merged back in. */
538
579
  toDocument() {
539
580
  const document = updateDocumentContent(this.baseDocument, this.state.doc);
581
+ this.mergeEditedHeaderFooterStories(document);
540
582
  if (this.createdComments.length > 0 || this.resolvedOverrides.size > 0) document.package.document.comments = this.withResolvedOverrides([...document.package.document.comments ?? [], ...this.createdComments]);
541
583
  return document;
542
584
  }
@@ -572,6 +614,77 @@ var FolioDocxReviewer = class FolioDocxReviewer {
572
614
  return null;
573
615
  }
574
616
  }
617
+ getEditableStoryState(story) {
618
+ if (story.type === "main") return this.state;
619
+ const key = headerFooterStoryKey(story);
620
+ const existing = this.headerFooterStoryStates.get(key);
621
+ if (existing) return existing.state;
622
+ const source = this.getHeaderFooterStory(story);
623
+ if (!source) return null;
624
+ const state = ensureDeterministicParaIdsInState(EditorState.create({
625
+ schema,
626
+ doc: headerFooterToProseDoc(source.content, {
627
+ ...this.baseDocument.package.styles !== void 0 && { styles: this.baseDocument.package.styles },
628
+ ...this.baseDocument.package.theme !== void 0 && { theme: this.baseDocument.package.theme }
629
+ }),
630
+ plugins: singletonManager.getPlugins()
631
+ }));
632
+ this.headerFooterStoryStates.set(key, {
633
+ handle: story,
634
+ initialState: state,
635
+ state
636
+ });
637
+ return state;
638
+ }
639
+ requireEditableStoryState(story) {
640
+ const state = this.getEditableStoryState(story);
641
+ if (state) return state;
642
+ throw new FolioDocumentStoryNotFoundError({
643
+ message: `Document story ${JSON.stringify(story)} was not found.`,
644
+ story
645
+ });
646
+ }
647
+ setEditableStoryState(story, state) {
648
+ if (story.type === "main") {
649
+ this.state = state;
650
+ return;
651
+ }
652
+ const entry = this.headerFooterStoryStates.get(headerFooterStoryKey(story));
653
+ if (!entry) throw new FolioDocumentStoryNotFoundError({
654
+ message: `Document story ${JSON.stringify(story)} was not found.`,
655
+ story
656
+ });
657
+ entry.state = state;
658
+ }
659
+ getHeaderFooterStory(story) {
660
+ return (story.type === "header" ? this.baseDocument.package.headers : this.baseDocument.package.footers)?.get(story.relationshipId);
661
+ }
662
+ getHeaderFooterStoryText(story, source) {
663
+ const state = this.headerFooterStoryStates.get(headerFooterStoryKey(story))?.state;
664
+ return normalizeFolioAIBlockText(state?.doc.textContent ?? getHeaderFooterText(source));
665
+ }
666
+ mergeEditedHeaderFooterStories(document) {
667
+ let headers;
668
+ let footers;
669
+ for (const entry of this.headerFooterStoryStates.values()) {
670
+ if (entry.state === entry.initialState) continue;
671
+ const source = this.getHeaderFooterStory(entry.handle);
672
+ if (!source) continue;
673
+ const edited = {
674
+ ...source,
675
+ content: proseDocToBlocks(entry.state.doc)
676
+ };
677
+ if (entry.handle.type === "header") {
678
+ headers ??= new Map(document.package.headers);
679
+ headers.set(entry.handle.relationshipId, edited);
680
+ continue;
681
+ }
682
+ footers ??= new Map(document.package.footers);
683
+ footers.set(entry.handle.relationshipId, edited);
684
+ }
685
+ if (headers) document.package.headers = headers;
686
+ if (footers) document.package.footers = footers;
687
+ }
575
688
  /**
576
689
  * Count every tracked change an accept-all / reject-all sweep resolves: the
577
690
  * inline insertion / deletion groups {@link getChanges} enumerates, plus the
@@ -654,4 +767,4 @@ const applyFolioAIEditsToBuffer = async (buffer, operations, options = {}) => {
654
767
  };
655
768
  };
656
769
  //#endregion
657
- export { FolioDocxReviewer, applyFolioAIEditsToBuffer };
770
+ export { FolioDocumentStoryNotFoundError, FolioDocxReviewer, applyFolioAIEditsToBuffer };
@@ -2,11 +2,11 @@ import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreview
2
2
  import { FolioAIEditView, applyFolioAIEditOperations } from "./apply.js";
3
3
  import { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange } from "./blockRange.js";
4
4
  import { buildAnnotatedBlockText } from "./clean-text.js";
5
- 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, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
5
+ 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, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
6
6
  import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
7
- import { FolioDocumentStory, FolioDocumentStoryHandle } from "./headless.js";
7
+ import { FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle } from "./headless.js";
8
8
  import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
9
9
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
10
10
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
11
11
  import { WordDiffSegment, diffWordSegments } from "./word-diff.js";
12
- export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, 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 FolioAIEditView, type FolioAIInlineFormatting, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioCommentAnchor, type FolioDocumentNavigationTarget, 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 FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, type FolioReviewChange, type FolioReviewChangeKind, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
12
+ export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, 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 FolioAIEditView, type FolioAIInlineFormatting, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioReviewChange, type FolioReviewChangeKind, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
@@ -7,4 +7,5 @@ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT
7
7
  import { clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange } from "./blockRange.js";
8
8
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
9
9
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
10
- export { 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, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
10
+ import { FolioDocumentStoryNotFoundError } from "./headless.js";
11
+ export { 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, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
@@ -22,12 +22,28 @@ const createFolioAIEditSnapshot = (doc) => {
22
22
  const draftBlocks = [];
23
23
  const hashCounts = /* @__PURE__ */ new Map();
24
24
  const usedBlockIds = /* @__PURE__ */ new Set();
25
+ const emptyAnchorState = {
26
+ candidate: null,
27
+ textblockCount: 0
28
+ };
25
29
  let blockIndex = 0;
26
- doc.descendants((node, pos) => {
30
+ doc.descendants((node, pos, parent) => {
27
31
  if (!node.isTextblock) return;
28
32
  const { text } = buildCleanBlockText(node, pos);
29
33
  const normalizedText = normalizeFolioAIBlockText(text);
30
- if (normalizedText.length === 0) return;
34
+ if (normalizedText.length === 0) {
35
+ if (parent?.type !== doc.type) return;
36
+ emptyAnchorState.textblockCount++;
37
+ if (emptyAnchorState.candidate === null) {
38
+ const paraIdAttr = node.attrs["paraId"];
39
+ emptyAnchorState.candidate = {
40
+ from: pos,
41
+ to: pos + node.nodeSize,
42
+ paraId: typeof paraIdAttr === "string" && paraIdAttr.length > 0 ? paraIdAttr : null
43
+ };
44
+ }
45
+ return;
46
+ }
31
47
  const textHash = hashFolioAIBlockText(normalizedText);
32
48
  hashCounts.set(textHash, (hashCounts.get(textHash) ?? 0) + 1);
33
49
  blockIndex++;
@@ -72,10 +88,31 @@ const createFolioAIEditSnapshot = (doc) => {
72
88
  hashOccurrenceCount: hashCounts.get(draft.anchor.textHash) ?? 0
73
89
  };
74
90
  }
75
- return {
91
+ const emptyAnchorCandidate = emptyAnchorState.candidate;
92
+ if (blocks.length > 0 || emptyAnchorCandidate === null) return {
76
93
  blocks,
77
94
  anchors
78
95
  };
96
+ const emptyDocumentAnchorId = deriveBlockId({
97
+ paraId: emptyAnchorCandidate.paraId,
98
+ index: 1,
99
+ taken: usedBlockIds
100
+ });
101
+ const normalizedText = "";
102
+ anchors[emptyDocumentAnchorId] = {
103
+ id: emptyDocumentAnchorId,
104
+ from: emptyAnchorCandidate.from,
105
+ to: emptyAnchorCandidate.to,
106
+ text: "",
107
+ normalizedText,
108
+ textHash: hashFolioAIBlockText(normalizedText),
109
+ hashOccurrenceCount: emptyAnchorState.textblockCount
110
+ };
111
+ return {
112
+ blocks,
113
+ anchors,
114
+ emptyDocumentAnchorId
115
+ };
79
116
  };
80
117
  const getBlockKind = (node, headingLevel) => {
81
118
  const listMarker = node.attrs["listMarker"];
@@ -21,7 +21,8 @@ type FolioAIBlock = {
21
21
  };
22
22
  type FolioAIEditSnapshot = {
23
23
  blocks: FolioAIBlock[];
24
- anchors: Record<string, FolioAIBlockAnchor>;
24
+ anchors: Record<string, FolioAIBlockAnchor>; /** Hidden empty paragraph used to anchor insertions when `blocks` is empty. */
25
+ emptyDocumentAnchorId?: string;
25
26
  };
26
27
  type FolioAIBlockAnchor = {
27
28
  id: string;
@@ -1,7 +1,7 @@
1
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, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.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, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
5
5
  import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "../ai-edits/snapshot.js";
6
6
  import { DeriveBlockIdInput, FolioBlockId, deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "../types/block-id.js";
7
7
  import { WordDiffSegment, diffWordSegments } from "../ai-edits/word-diff.js";
@@ -9,10 +9,12 @@ import { AIBarStatus, AIChatMode, AICitation, AICitationSource, AIGenerateInput,
9
9
  import { ApplyResult, applySuggestions } from "../ai-suggestions/apply.js";
10
10
  import { ResolvedAnchor, isSuggestionStale, resolveSuggestionAnchor } from "../ai-suggestions/conflict.js";
11
11
  import { PositionalText, buildPositionalText } from "../ai-suggestions/text-positions.js";
12
+ import { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocumentPreset, DocumentStyleSet } from "../style-sets/types.js";
12
13
  import { CreateEmptyDocumentOptions, createEmptyDocument } from "../utils/createDocument.js";
14
+ import { DocumentStyleCatalog, DocumentStyleCatalogEntry, ExtractDocumentStyleSetOptions, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, inspectDocumentStyles, inspectDocumentStylesFromDocx } from "../style-sets/extract.js";
15
+ import { STELLA_STYLE_SET_NAME, createStellaStyleDocumentPreset, createStellaStyleSet } from "../style-sets/stellaStyle.js";
13
16
  import { createDocx } from "../docx/rezip.js";
14
- import { FOLIO_DOCX_CAPABILITY_HOSTS, FOLIO_DOCX_CAPABILITY_IDS, FOLIO_DOCX_CAPABILITY_MANIFEST, FOLIO_DOCX_CAPABILITY_MANIFEST_VERSION, FOLIO_DOCX_CAPABILITY_OPERATIONS, FOLIO_DOCX_COMPATIBILITY_HOSTS, FOLIO_DOCX_COMPATIBILITY_PROFILES, FOLIO_DOCX_PROFILES, FOLIO_DOCX_SUPPORT_STATES, FolioDocxCapabilityHost, FolioDocxCapabilityId, FolioDocxCapabilityManifest, FolioDocxCapabilityOperation, FolioDocxCompatibilityHost, FolioDocxCompatibilityProfile, FolioDocxFeatureCapability, FolioDocxProfile, FolioDocxSupportState, InvalidFolioDocxCapabilityIdError, getFolioDocxCapability, isFolioDocxCapabilityId } from "../docx/capabilities.js";
15
- import { DocxCompatibility, DocxCompatibilityContext, DocxCompatibilityIssue, DocxCompatibilityLocation, DocxCompatibilityPart, InspectDocxCompatibilityOptions, inspectDocxCompatibility } from "../docx/compatibility.js";
17
+ import { DocxCompatibility, DocxCompatibilityContext, DocxCompatibilityIssue, DocxCompatibilityLocation, DocxCompatibilityPart, FolioDocxCompatibilityHost, FolioDocxCompatibilityProfile, InspectDocxCompatibilityOptions, inspectDocxCompatibility } from "../docx/compatibility.js";
16
18
  import { setAISuggestionsMeta, setFocusedSuggestionMeta } from "../prosemirror/plugins/aiSuggestionDecorations.js";
17
19
  import { scrollFolioPositionIntoView } from "../paged-layout/scrollToPmPosition.js";
18
20
  import { getFolioCaretViewportRect, getFolioSelectionViewportRect } from "../paged-layout/selectionViewportRect.js";
@@ -30,4 +32,4 @@ import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "../utils/fontResol
30
32
  import { DOCX_CONFORMANCE_CLASSES } from "../index.js";
31
33
  type Document = document_d_exports.Document;
32
34
  type DocxConformanceClass = document_d_exports.DocxConformanceClass;
33
- 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, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, 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, FOLIO_DOCX_CAPABILITY_HOSTS, FOLIO_DOCX_CAPABILITY_IDS, FOLIO_DOCX_CAPABILITY_MANIFEST, FOLIO_DOCX_CAPABILITY_MANIFEST_VERSION, FOLIO_DOCX_CAPABILITY_OPERATIONS, FOLIO_DOCX_COMPATIBILITY_HOSTS, FOLIO_DOCX_COMPATIBILITY_PROFILES, FOLIO_DOCX_PROFILES, FOLIO_DOCX_SUPPORT_STATES, 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 FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCapabilityHost, type FolioDocxCapabilityId, type FolioDocxCapabilityManifest, type FolioDocxCapabilityOperation, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioDocxFeatureCapability, type FolioDocxProfile, type FolioDocxSupportState, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, InvalidFolioDocxCapabilityIdError, 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, getFolioDocxCapability, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocxCapabilityId, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
35
+ 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, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, 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 FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, 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, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };