@stll/folio-core 0.8.0 → 0.10.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 (168) 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 +73 -6
  4. package/dist/ai-edits/headless.js +299 -60
  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 +13 -1
  14. package/dist/document-operations.d.ts +20 -4
  15. package/dist/document-operations.js +36 -15
  16. package/dist/document-stories.d.ts +10 -0
  17. package/dist/document-stories.js +27 -0
  18. package/dist/docx/blockContentParser.js +2 -2
  19. package/dist/docx/commentParser.js +1 -1
  20. package/dist/docx/compatibility.d.ts +3 -7
  21. package/dist/docx/compatibility.js +0 -11
  22. package/dist/docx/corePropertiesParser.d.ts +8 -0
  23. package/dist/docx/corePropertiesParser.js +53 -0
  24. package/dist/docx/encryption/agileDecryption.js +1 -1
  25. package/dist/docx/encryption/encryptionInfo.js +1 -1
  26. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  27. package/dist/docx/fontTableParser.d.ts +6 -0
  28. package/dist/docx/fontTableParser.js +72 -0
  29. package/dist/docx/groupDrawingParser.js +7 -2
  30. package/dist/docx/headerFooterParser.js +1 -1
  31. package/dist/docx/index.d.ts +2 -1
  32. package/dist/docx/index.js +2 -1
  33. package/dist/docx/metadataPrivacy.d.ts +40 -0
  34. package/dist/docx/metadataPrivacy.js +131 -0
  35. package/dist/docx/normalizeBaseDirection.js +1 -1
  36. package/dist/docx/numberingParser.js +1 -0
  37. package/dist/docx/paragraphParser.js +12 -1
  38. package/dist/docx/parser.d.ts +1 -1
  39. package/dist/docx/parser.js +16 -10
  40. package/dist/docx/rezip.d.ts +5 -4
  41. package/dist/docx/rezip.js +76 -13
  42. package/dist/docx/runConsolidator.js +4 -0
  43. package/dist/docx/runParser.js +19 -1
  44. package/dist/docx/selectiveSave.js +3 -3
  45. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  46. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  47. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  48. package/dist/docx/serializer/paragraphSerializer.js +4 -0
  49. package/dist/docx/serializer/runSerializer.js +7 -0
  50. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  51. package/dist/docx/serializer/settingsSerializer.js +16 -0
  52. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  53. package/dist/docx/serializer/stylesSerializer.js +56 -0
  54. package/dist/docx/serializer/tableSerializer.js +25 -26
  55. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  56. package/dist/docx/serializer/themeSerializer.js +36 -0
  57. package/dist/docx/settingsParser.js +46 -0
  58. package/dist/docx/styleParser.js +26 -1
  59. package/dist/docx/tableParser.js +11 -1
  60. package/dist/docx/vmlImageParser.js +1 -0
  61. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  62. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  63. package/dist/index.d.ts +6 -4
  64. package/dist/index.js +5 -3
  65. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  66. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  67. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  68. package/dist/layout-bridge/convert/headerFooterLayout.js +6 -1
  69. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  70. package/dist/layout-bridge/convert/toFlowBlocks.js +52 -14
  71. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  72. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  73. package/dist/layout-engine/index.js +36 -28
  74. package/dist/layout-engine/measure/cache.js +6 -3
  75. package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
  76. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
  77. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  78. package/dist/layout-engine/measure/index.d.ts +2 -1
  79. package/dist/layout-engine/measure/index.js +2 -1
  80. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  81. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  82. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  83. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  84. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  85. package/dist/layout-engine/measure/listMarkerWidth.js +12 -6
  86. package/dist/layout-engine/measure/measureBlocks.d.ts +1 -2
  87. package/dist/layout-engine/measure/measureBlocks.js +6 -36
  88. package/dist/layout-engine/measure/measureContainer.js +32 -9
  89. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  90. package/dist/layout-engine/measure/measureParagraph.js +332 -73
  91. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  92. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  93. package/dist/layout-engine/measure/measureWorker.js +8 -19
  94. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  95. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  96. package/dist/layout-engine/measure/tableCellFloating.js +51 -37
  97. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  98. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  99. package/dist/layout-engine/measure/tableCellGrid.js +1 -1
  100. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  101. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  102. package/dist/layout-engine/renderedBreakReconciliation.d.ts +6 -1
  103. package/dist/layout-engine/renderedBreakReconciliation.js +34 -11
  104. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  105. package/dist/layout-engine/tableRowBreak.js +62 -29
  106. package/dist/layout-engine/types.d.ts +43 -8
  107. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  108. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  109. package/dist/layout-painter/borderStroke.d.ts +23 -0
  110. package/dist/layout-painter/borderStroke.js +39 -0
  111. package/dist/layout-painter/renderImage.d.ts +5 -4
  112. package/dist/layout-painter/renderImage.js +18 -4
  113. package/dist/layout-painter/renderPage.d.ts +2 -27
  114. package/dist/layout-painter/renderPage.js +13 -99
  115. package/dist/layout-painter/renderParagraph.js +41 -23
  116. package/dist/layout-painter/renderTable.d.ts +2 -0
  117. package/dist/layout-painter/renderTable.js +223 -43
  118. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  119. package/dist/managers/DocumentLoaderManager.js +2 -2
  120. package/dist/prosemirror/attrs/index.d.ts +4 -2
  121. package/dist/prosemirror/attrs/index.js +22 -2
  122. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  123. package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
  124. package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
  125. package/dist/prosemirror/conversion/fromProseDoc.js +34 -18
  126. package/dist/prosemirror/conversion/toProseDoc.js +81 -77
  127. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  128. package/dist/prosemirror/extensions/core/ParagraphExtension.js +7 -2
  129. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  130. package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
  131. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  132. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  133. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  134. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  135. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  136. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  137. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  138. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  139. package/dist/prosemirror/extensions/nodes/ImageExtension.js +3 -1
  140. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  141. package/dist/prosemirror/schema/index.d.ts +2 -2
  142. package/dist/prosemirror/schema/marks.d.ts +6 -1
  143. package/dist/prosemirror/schema/nodes.d.ts +15 -12
  144. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  145. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  146. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  147. package/dist/prosemirror/validation.js +4 -1
  148. package/dist/redline.d.ts +26 -11
  149. package/dist/redline.js +107 -64
  150. package/dist/server.d.ts +9 -5
  151. package/dist/server.js +8 -4
  152. package/dist/style-sets/extract.d.ts +35 -0
  153. package/dist/style-sets/extract.js +123 -0
  154. package/dist/style-sets/stellaStyle.d.ts +13 -0
  155. package/dist/style-sets/stellaStyle.js +516 -0
  156. package/dist/style-sets/types.d.ts +31 -0
  157. package/dist/style-sets/types.js +5 -0
  158. package/dist/utils/createDocument.d.ts +12 -2
  159. package/dist/utils/createDocument.js +42 -31
  160. package/dist/utils/fontResolver.js +6 -6
  161. package/dist/utils/formatToStyle.js +1 -1
  162. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  163. package/dist/utils/paragraphFormattingMerge.js +14 -4
  164. package/dist/version-comparison.d.ts +65 -11
  165. package/dist/version-comparison.js +187 -29
  166. package/package.json +5 -1
  167. package/dist/docx/capabilities.d.ts +0 -41
  168. package/dist/docx/capabilities.js +0 -322
@@ -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 { footnoteToProseDoc, 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,7 @@ 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, footer, footnote, and endnote blocks.
38
38
  */
39
39
  /**
40
40
  * Standalone comment-id allocator for the reviewer. The React editor uses
@@ -114,6 +114,43 @@ const ensureDeterministicParaIdsInState = (state) => {
114
114
  tr.setMeta("addToHistory", false);
115
115
  return state.apply(tr);
116
116
  };
117
+ const FOLIO_REVIEWED_VIEWS = Object.freeze([
118
+ "original",
119
+ "current-markup",
120
+ "final"
121
+ ]);
122
+ const FOLIO_RESOLVED_REVIEWED_VIEWS = Object.freeze(["original", "final"]);
123
+ var UnsupportedFolioReviewedViewError = class extends TaggedError("UnsupportedFolioReviewedViewError")() {};
124
+ var FolioDocumentStoryNotFoundError = class extends TaggedError("FolioDocumentStoryNotFoundError")() {};
125
+ const MAIN_STORY = Object.freeze({ type: "main" });
126
+ const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
127
+ const noteStoryKey = ({ type, noteId }) => `${type}:${noteId}`;
128
+ const secondaryStoryKey = (story) => story.type === "header" || story.type === "footer" ? headerFooterStoryKey(story) : noteStoryKey(story);
129
+ const isFolioReviewedView = (value) => FOLIO_REVIEWED_VIEWS.some((view) => view === value);
130
+ const isFolioResolvedReviewedView = (value) => FOLIO_RESOLVED_REVIEWED_VIEWS.some((view) => view === value);
131
+ const applyCommandToState = (state, command) => {
132
+ let nextState = state;
133
+ command(state, (transaction) => {
134
+ nextState = nextState.apply(transaction);
135
+ });
136
+ return nextState;
137
+ };
138
+ const resolveReviewedState = (state, view) => {
139
+ if (view === "current-markup") return state;
140
+ return applyCommandToState(state, view === "original" ? rejectAllChanges() : acceptAllChanges());
141
+ };
142
+ const formatStoryStateForLLM = (state, annotated) => {
143
+ const snapshot = createFolioAIEditSnapshot(state.doc);
144
+ if (!annotated) return snapshot.blocks.map(formatBlockForLLM).join("\n");
145
+ const startById = /* @__PURE__ */ new Map();
146
+ for (const anchor of Object.values(snapshot.anchors)) startById.set(anchor.id, anchor.from);
147
+ return snapshot.blocks.map((block) => {
148
+ const from = startById.get(block.id);
149
+ const node = from === void 0 ? null : state.doc.nodeAt(from);
150
+ const text = node ? buildAnnotatedBlockText(node) : block.text;
151
+ return formatBlockLine(block, text);
152
+ }).join("\n");
153
+ };
117
154
  /**
118
155
  * One LLM-ready line for a block: `[<blockId>] text`, with an `(h<level>)` tag
119
156
  * for headings and the list marker for list items, so a model can copy the
@@ -162,6 +199,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
162
199
  baseDocument;
163
200
  originalBuffer;
164
201
  state;
202
+ secondaryStoryStates = /* @__PURE__ */ new Map();
165
203
  createdComments = [];
166
204
  documentOperationUndoEntries = [];
167
205
  /**
@@ -206,6 +244,51 @@ var FolioDocxReviewer = class FolioDocxReviewer {
206
244
  snapshot() {
207
245
  return createFolioAIEditSnapshot(this.state.doc);
208
246
  }
247
+ /** Return parsed package metadata without exposing the mutable document model. */
248
+ getDocumentProperties() {
249
+ const properties = this.baseDocument.package.properties;
250
+ if (!properties) return null;
251
+ return Object.freeze({
252
+ ...properties,
253
+ ...properties.created !== void 0 ? { created: new Date(properties.created.getTime()) } : {},
254
+ ...properties.modified !== void 0 ? { modified: new Date(properties.modified.getTime()) } : {}
255
+ });
256
+ }
257
+ /** Snapshot one editable story into stable, operation-ready blocks. */
258
+ snapshotStory(story) {
259
+ const state = this.getEditableStoryState(story);
260
+ return state ? createFolioAIEditSnapshot(state.doc) : null;
261
+ }
262
+ /** Read one story through an immutable reviewed-view projection. */
263
+ readReviewedStory(options = {}) {
264
+ const story = options.story ?? MAIN_STORY;
265
+ const view = options.view ?? "final";
266
+ if (!isFolioReviewedView(view)) throw new UnsupportedFolioReviewedViewError({
267
+ message: "Unsupported reviewed document view.",
268
+ receivedView: view
269
+ });
270
+ const sourceState = this.getEditableStoryState(story);
271
+ if (!sourceState) return null;
272
+ const state = resolveReviewedState(sourceState, view);
273
+ return {
274
+ story,
275
+ view,
276
+ snapshot: createFolioAIEditSnapshot(state.doc),
277
+ text: formatStoryStateForLLM(state, view === "current-markup"),
278
+ changes: getTrackedChangesFromDoc(state.doc)
279
+ };
280
+ }
281
+ /** Resolve one editable story to its original or final state. */
282
+ resolveReviewedStory({ story = MAIN_STORY, view }) {
283
+ if (!isFolioResolvedReviewedView(view)) throw new UnsupportedFolioReviewedViewError({
284
+ message: "Only original and final views can replace editable story state.",
285
+ receivedView: view
286
+ });
287
+ const sourceState = this.getEditableStoryState(story);
288
+ if (!sourceState) return false;
289
+ this.setEditableStoryState(story, resolveReviewedState(sourceState, view));
290
+ return true;
291
+ }
209
292
  /**
210
293
  * Apply operations against the current state. Reuses the live-editor applier
211
294
  * verbatim via a headless `{ state, dispatch }` seam; the resulting state
@@ -213,10 +296,15 @@ var FolioDocxReviewer = class FolioDocxReviewer {
213
296
  */
214
297
  applyOperations(operations, options = {}) {
215
298
  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);
299
+ story: MAIN_STORY,
300
+ batch: {
301
+ version: 1,
302
+ operations,
303
+ mode: options.mode ?? "tracked-changes"
304
+ },
305
+ ...options.snapshot !== void 0 && { snapshot: options.snapshot },
306
+ createUndoEntry: false
307
+ });
220
308
  return {
221
309
  applied,
222
310
  skipped
@@ -229,21 +317,36 @@ var FolioDocxReviewer = class FolioDocxReviewer {
229
317
  * edit semantics.
230
318
  */
231
319
  applyDocumentOperations(batch, options = {}) {
232
- return this.applyDocumentOperationsInternal(batch, options, true);
320
+ return this.applyDocumentOperationsInternal({
321
+ story: MAIN_STORY,
322
+ batch,
323
+ ...options.snapshot !== void 0 && { snapshot: options.snapshot },
324
+ createUndoEntry: true
325
+ });
326
+ }
327
+ /** Apply a versioned operation batch to one editable document story. */
328
+ applyDocumentOperationsToStory({ story, batch, snapshot }) {
329
+ return this.applyDocumentOperationsInternal({
330
+ story,
331
+ batch,
332
+ ...snapshot !== void 0 && { snapshot },
333
+ createUndoEntry: true
334
+ });
233
335
  }
234
- applyDocumentOperationsInternal(batch, options, createUndoEntry) {
235
- const beforeState = this.state;
336
+ applyDocumentOperationsInternal({ story, batch, snapshot, createUndoEntry }) {
337
+ const beforeState = this.requireEditableStoryState(story);
236
338
  const createdCommentsLengthBefore = this.createdComments.length;
237
339
  const view = {
238
- state: this.state,
340
+ state: beforeState,
239
341
  dispatch: (transaction) => {
240
342
  view.state = view.state.apply(transaction);
241
343
  }
242
344
  };
243
345
  const result = applyFolioDocumentOperations({
244
346
  view,
245
- snapshot: options.snapshot ?? this.snapshot(),
347
+ snapshot: snapshot ?? createFolioAIEditSnapshot(beforeState.doc),
246
348
  batch,
349
+ story: story.type === "main" ? "main" : story,
247
350
  author: this.author,
248
351
  createCommentId: (text) => {
249
352
  const comment = createReviewerComment(text, this.author);
@@ -255,11 +358,12 @@ var FolioDocxReviewer = class FolioDocxReviewer {
255
358
  id: `headless-${String(undoHandleCursor++)}`
256
359
  }) }
257
360
  });
258
- this.state = view.state;
361
+ this.setEditableStoryState(story, view.state);
259
362
  if (result.undoHandle !== null) this.documentOperationUndoEntries.push({
260
363
  undoHandle: result.undoHandle,
364
+ story,
261
365
  beforeState,
262
- afterState: this.state,
366
+ afterState: view.state,
263
367
  createdCommentsLengthBefore,
264
368
  createdCommentsLengthAfter: this.createdComments.length
265
369
  });
@@ -284,12 +388,12 @@ var FolioDocxReviewer = class FolioDocxReviewer {
284
388
  undoHandle,
285
389
  reason: "unknownHandle"
286
390
  };
287
- if (this.state !== entry.afterState || this.createdComments.length !== entry.createdCommentsLengthAfter) return {
391
+ if (this.requireEditableStoryState(entry.story) !== entry.afterState || this.createdComments.length !== entry.createdCommentsLengthAfter) return {
288
392
  status: "rejected",
289
393
  undoHandle,
290
394
  reason: "documentChanged"
291
395
  };
292
- this.state = entry.beforeState;
396
+ this.setEditableStoryState(entry.story, entry.beforeState);
293
397
  this.createdComments.length = entry.createdCommentsLengthBefore;
294
398
  this.documentOperationUndoEntries.pop();
295
399
  return {
@@ -317,31 +421,25 @@ var FolioDocxReviewer = class FolioDocxReviewer {
317
421
  * (clean) output flattens tracked changes and is unchanged.
318
422
  */
319
423
  getContentAsText(options = {}) {
320
- if (!options.annotated) return this.getContent().map(formatBlockForLLM).join("\n");
321
- const snapshot = this.snapshot();
322
- const startById = /* @__PURE__ */ new Map();
323
- for (const anchor of Object.values(snapshot.anchors)) startById.set(anchor.id, anchor.from);
324
- return snapshot.blocks.map((block) => {
325
- const from = startById.get(block.id);
326
- const node = from === void 0 ? null : this.state.doc.nodeAt(from);
327
- const text = node ? buildAnnotatedBlockText(node) : block.text;
328
- return formatBlockLine(block, text);
329
- }).join("\n");
424
+ return formatStoryStateForLLM(this.state, options.annotated === true);
330
425
  }
331
426
  /**
332
427
  * Header / footer and footnote / endnote text as labeled, LLM-ready lines,
333
428
  * 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.
429
+ * `[footnote #N] …`, `[endnote #N] …`. Lines reflect in-memory edits. Empty
430
+ * parts and separator notes are omitted.
337
431
  */
338
432
  getNotesAsText() {
339
433
  const pkg = this.baseDocument.package;
340
434
  const lines = [];
341
435
  const pushHeaderFooter = (map, label) => {
342
436
  if (!map) return;
343
- for (const hf of map.values()) {
344
- const text = normalizeFolioAIBlockText(getHeaderFooterText(hf));
437
+ for (const [relationshipId, hf] of map) {
438
+ const handle = {
439
+ type: label,
440
+ relationshipId
441
+ };
442
+ const text = this.getHeaderFooterStoryText(handle, hf);
345
443
  if (text.length > 0) lines.push(`[${label} ${hf.hdrFtrType}] ${text}`);
346
444
  }
347
445
  };
@@ -349,12 +447,20 @@ var FolioDocxReviewer = class FolioDocxReviewer {
349
447
  pushHeaderFooter(pkg.footers, "footer");
350
448
  for (const footnote of pkg.footnotes ?? []) {
351
449
  if (isSeparatorFootnote(footnote)) continue;
352
- const text = normalizeFolioAIBlockText(getFootnoteText(footnote));
450
+ const handle = {
451
+ type: "footnote",
452
+ noteId: footnote.id
453
+ };
454
+ const text = this.getNoteStoryText(handle, footnote);
353
455
  if (text.length > 0) lines.push(`[footnote #${footnote.id}] ${text}`);
354
456
  }
355
457
  for (const endnote of pkg.endnotes ?? []) {
356
458
  if (isSeparatorEndnote(endnote)) continue;
357
- const text = normalizeFolioAIBlockText(getEndnoteText(endnote));
459
+ const handle = {
460
+ type: "endnote",
461
+ noteId: endnote.id
462
+ };
463
+ const text = this.getNoteStoryText(handle, endnote);
358
464
  if (text.length > 0) lines.push(`[endnote #${endnote.id}] ${text}`);
359
465
  }
360
466
  return lines.join("\n");
@@ -366,34 +472,46 @@ var FolioDocxReviewer = class FolioDocxReviewer {
366
472
  handle: { type: "main" },
367
473
  text: this.getContentAsText()
368
474
  }];
369
- for (const [relationshipId, header] of pkg.headers ?? []) stories.push({
370
- handle: {
475
+ for (const [relationshipId, header] of pkg.headers ?? []) {
476
+ const handle = {
371
477
  type: "header",
372
478
  relationshipId
373
- },
374
- text: normalizeFolioAIBlockText(getHeaderFooterText(header))
375
- });
376
- for (const [relationshipId, footer] of pkg.footers ?? []) stories.push({
377
- handle: {
479
+ };
480
+ stories.push({
481
+ handle,
482
+ text: this.getHeaderFooterStoryText(handle, header)
483
+ });
484
+ }
485
+ for (const [relationshipId, footer] of pkg.footers ?? []) {
486
+ const handle = {
378
487
  type: "footer",
379
488
  relationshipId
380
- },
381
- text: normalizeFolioAIBlockText(getHeaderFooterText(footer))
382
- });
383
- for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) stories.push({
384
- handle: {
489
+ };
490
+ stories.push({
491
+ handle,
492
+ text: this.getHeaderFooterStoryText(handle, footer)
493
+ });
494
+ }
495
+ for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) {
496
+ const handle = {
385
497
  type: "footnote",
386
498
  noteId: footnote.id
387
- },
388
- text: normalizeFolioAIBlockText(getFootnoteText(footnote))
389
- });
390
- for (const endnote of pkg.endnotes ?? []) if (!isSeparatorEndnote(endnote)) stories.push({
391
- handle: {
499
+ };
500
+ stories.push({
501
+ handle,
502
+ text: this.getNoteStoryText(handle, footnote)
503
+ });
504
+ }
505
+ for (const endnote of pkg.endnotes ?? []) if (!isSeparatorEndnote(endnote)) {
506
+ const handle = {
392
507
  type: "endnote",
393
508
  noteId: endnote.id
394
- },
395
- text: normalizeFolioAIBlockText(getEndnoteText(endnote))
396
- });
509
+ };
510
+ stories.push({
511
+ handle,
512
+ text: this.getNoteStoryText(handle, endnote)
513
+ });
514
+ }
397
515
  return stories;
398
516
  }
399
517
  /** Read one discovered story; returns null when its handle is no longer present. */
@@ -537,6 +655,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
537
655
  /** The current document model with edits merged back in. */
538
656
  toDocument() {
539
657
  const document = updateDocumentContent(this.baseDocument, this.state.doc);
658
+ this.mergeEditedSecondaryStories(document);
540
659
  if (this.createdComments.length > 0 || this.resolvedOverrides.size > 0) document.package.document.comments = this.withResolvedOverrides([...document.package.document.comments ?? [], ...this.createdComments]);
541
660
  return document;
542
661
  }
@@ -562,16 +681,136 @@ var FolioDocxReviewer = class FolioDocxReviewer {
562
681
  * is the graceful handling — no separate error surface is needed here.
563
682
  */
564
683
  async trySelectiveSave(document) {
684
+ const changedParaIds = new Set(getChangedParagraphIds(this.state));
685
+ let structuralChange = hasStructuralChanges(this.state);
686
+ let untrackedChanges = hasUntrackedChanges(this.state);
687
+ for (const entry of this.secondaryStoryStates.values()) {
688
+ if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
689
+ for (const paraId of getChangedParagraphIds(entry.state)) changedParaIds.add(paraId);
690
+ structuralChange ||= hasStructuralChanges(entry.state);
691
+ untrackedChanges ||= hasUntrackedChanges(entry.state);
692
+ }
565
693
  try {
566
694
  return await attemptSelectiveSave(document, this.originalBuffer, {
567
- changedParaIds: getChangedParagraphIds(this.state),
568
- structuralChange: hasStructuralChanges(this.state),
569
- hasUntrackedChanges: hasUntrackedChanges(this.state)
695
+ changedParaIds,
696
+ structuralChange,
697
+ hasUntrackedChanges: untrackedChanges
570
698
  });
571
699
  } catch {
572
700
  return null;
573
701
  }
574
702
  }
703
+ getEditableStoryState(story) {
704
+ if (story.type === "main") return this.state;
705
+ const key = secondaryStoryKey(story);
706
+ const existing = this.secondaryStoryStates.get(key);
707
+ if (existing) return existing.state;
708
+ const source = story.type === "header" || story.type === "footer" ? this.getHeaderFooterStory(story) : this.getNoteStory(story);
709
+ if (!source) return null;
710
+ const conversionOptions = {
711
+ ...this.baseDocument.package.styles !== void 0 && { styles: this.baseDocument.package.styles },
712
+ ...this.baseDocument.package.theme !== void 0 && { theme: this.baseDocument.package.theme }
713
+ };
714
+ const state = ensureDeterministicParaIdsInState(EditorState.create({
715
+ schema,
716
+ doc: story.type === "header" || story.type === "footer" ? headerFooterToProseDoc(source.content, conversionOptions) : footnoteToProseDoc(source.content, conversionOptions),
717
+ plugins: singletonManager.getPlugins()
718
+ }));
719
+ this.secondaryStoryStates.set(key, {
720
+ handle: story,
721
+ initialState: state,
722
+ state
723
+ });
724
+ return state;
725
+ }
726
+ requireEditableStoryState(story) {
727
+ const state = this.getEditableStoryState(story);
728
+ if (state) return state;
729
+ throw new FolioDocumentStoryNotFoundError({
730
+ message: `Document story ${JSON.stringify(story)} was not found.`,
731
+ story
732
+ });
733
+ }
734
+ setEditableStoryState(story, state) {
735
+ if (story.type === "main") {
736
+ this.state = state;
737
+ return;
738
+ }
739
+ const entry = this.secondaryStoryStates.get(secondaryStoryKey(story));
740
+ if (!entry) throw new FolioDocumentStoryNotFoundError({
741
+ message: `Document story ${JSON.stringify(story)} was not found.`,
742
+ story
743
+ });
744
+ entry.state = state;
745
+ }
746
+ getHeaderFooterStory(story) {
747
+ return (story.type === "header" ? this.baseDocument.package.headers : this.baseDocument.package.footers)?.get(story.relationshipId);
748
+ }
749
+ getNoteStory(story) {
750
+ if (story.type === "footnote") return this.baseDocument.package.footnotes?.find((note) => note.id === story.noteId && !isSeparatorFootnote(note));
751
+ return this.baseDocument.package.endnotes?.find((note) => note.id === story.noteId && !isSeparatorEndnote(note));
752
+ }
753
+ getHeaderFooterStoryText(story, source) {
754
+ const state = this.secondaryStoryStates.get(headerFooterStoryKey(story))?.state;
755
+ return normalizeFolioAIBlockText(state?.doc.textContent ?? getHeaderFooterText(source));
756
+ }
757
+ getNoteStoryText(story, source) {
758
+ const state = this.secondaryStoryStates.get(noteStoryKey(story))?.state;
759
+ const sourceText = source.type === "footnote" ? getFootnoteText(source) : getEndnoteText(source);
760
+ return normalizeFolioAIBlockText(state?.doc.textContent ?? sourceText);
761
+ }
762
+ mergeEditedSecondaryStories(document) {
763
+ let headers;
764
+ let footers;
765
+ let footnotes;
766
+ let endnotes;
767
+ for (const entry of this.secondaryStoryStates.values()) {
768
+ if (entry.state === entry.initialState) continue;
769
+ if (entry.handle.type === "header" || entry.handle.type === "footer") {
770
+ const source = this.getHeaderFooterStory(entry.handle);
771
+ if (!source) continue;
772
+ const edited = {
773
+ ...source,
774
+ content: proseDocToBlocks(entry.state.doc)
775
+ };
776
+ if (entry.handle.type === "header") {
777
+ headers ??= new Map(document.package.headers);
778
+ headers.set(entry.handle.relationshipId, edited);
779
+ continue;
780
+ }
781
+ footers ??= new Map(document.package.footers);
782
+ footers.set(entry.handle.relationshipId, edited);
783
+ continue;
784
+ }
785
+ if (entry.handle.type === "footnote") {
786
+ const { noteId } = entry.handle;
787
+ const source = this.baseDocument.package.footnotes?.find((note) => note.id === noteId && !isSeparatorFootnote(note));
788
+ if (!source) continue;
789
+ const edited = {
790
+ ...source,
791
+ content: proseDocToBlocks(entry.state.doc)
792
+ };
793
+ footnotes ??= [...document.package.footnotes ?? []];
794
+ const index = footnotes.findIndex((note) => note.id === noteId);
795
+ if (index !== -1) footnotes[index] = edited;
796
+ continue;
797
+ }
798
+ const { noteId } = entry.handle;
799
+ const source = this.baseDocument.package.endnotes?.find((note) => note.id === noteId && !isSeparatorEndnote(note));
800
+ if (!source) continue;
801
+ const edited = {
802
+ ...source,
803
+ content: proseDocToBlocks(entry.state.doc)
804
+ };
805
+ endnotes ??= [...document.package.endnotes ?? []];
806
+ const index = endnotes.findIndex((note) => note.id === noteId);
807
+ if (index !== -1) endnotes[index] = edited;
808
+ }
809
+ if (headers) document.package.headers = headers;
810
+ if (footers) document.package.footers = footers;
811
+ if (footnotes) document.package.footnotes = footnotes;
812
+ if (endnotes) document.package.endnotes = endnotes;
813
+ }
575
814
  /**
576
815
  * Count every tracked change an accept-all / reject-all sweep resolves: the
577
816
  * inline insertion / deletion groups {@link getChanges} enumerates, plus the
@@ -654,4 +893,4 @@ const applyFolioAIEditsToBuffer = async (buffer, operations, options = {}) => {
654
893
  };
655
894
  };
656
895
  //#endregion
657
- export { FolioDocxReviewer, applyFolioAIEditsToBuffer };
896
+ export { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, FolioDocxReviewer, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView };
@@ -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 { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } 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, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, 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 FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, 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 { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } 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, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, 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;