@docx-editor.dev/core 2.29.0 → 2.31.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 (107) hide show
  1. package/dist/automation.cjs +1 -1
  2. package/dist/automation.d.ts +22 -26
  3. package/dist/automation.js +1 -1
  4. package/dist/binding.cjs +1 -1
  5. package/dist/binding.d.ts +47 -40
  6. package/dist/binding.js +1 -1
  7. package/dist/bookmark-names-DDuAra_V.d.ts +37 -0
  8. package/dist/{chrome-print-CAs51cpq.d.ts → chrome-print-BWvaVEhD.d.ts} +577 -243
  9. package/dist/chunk-243W4WW2.cjs +2 -0
  10. package/dist/{chunk-G4HOFIKJ.js → chunk-2E2RPEKX.js} +1 -1
  11. package/dist/chunk-4S24UOPG.js +30 -0
  12. package/dist/chunk-5JPIEN54.cjs +3 -0
  13. package/dist/{chunk-7WQ7AAHW.cjs → chunk-5SO5QDMV.cjs} +1 -1
  14. package/dist/{chunk-UHHYFIDT.cjs → chunk-6IDOVU6L.cjs} +1 -1
  15. package/dist/chunk-DBOOTED4.cjs +30 -0
  16. package/dist/chunk-EOTAXOJY.cjs +5 -0
  17. package/dist/chunk-FDAKJ2SJ.cjs +30 -0
  18. package/dist/chunk-FNNOJNGY.js +3 -0
  19. package/dist/chunk-FT33HRZP.js +5 -0
  20. package/dist/{chunk-W35WTWAO.cjs → chunk-HC6YNKUI.cjs} +1 -1
  21. package/dist/chunk-HZ66UCIS.js +2 -0
  22. package/dist/{chunk-MM5QBMTE.cjs → chunk-IBRBNT37.cjs} +1 -1
  23. package/dist/chunk-IVIN7X43.js +30 -0
  24. package/dist/chunk-LZY6B6IV.js +4 -0
  25. package/dist/chunk-MOYYRVFV.cjs +2 -0
  26. package/dist/{chunk-OMCG63ZF.js → chunk-N6B3BXCL.js} +1 -1
  27. package/dist/chunk-Q5K2GGNG.js +2 -0
  28. package/dist/chunk-R2XINWVE.js +7 -0
  29. package/dist/chunk-SUEXVBPR.js +1 -0
  30. package/dist/chunk-T2QQHG5C.js +18 -0
  31. package/dist/{chunk-LSZ6KPKK.js → chunk-VWIJKH4A.js} +1 -1
  32. package/dist/chunk-WO4V6PNS.cjs +18 -0
  33. package/dist/chunk-XMJKFURD.cjs +4 -0
  34. package/dist/{chunk-BY2ACZZ6.cjs → chunk-XSTGE3NI.cjs} +1 -1
  35. package/dist/chunk-YQWMI6J5.cjs +7 -0
  36. package/dist/{chunk-RCLZXP3B.js → chunk-ZCJS7D7S.js} +1 -1
  37. package/dist/collaboration/replication.cjs +1 -1
  38. package/dist/collaboration/replication.d.ts +4 -5
  39. package/dist/collaboration/replication.js +1 -1
  40. package/dist/collaboration.d.ts +3 -4
  41. package/dist/contracts/document.d.ts +3 -3
  42. package/dist/contracts/editor.d.ts +8 -9
  43. package/dist/contracts/interaction.d.ts +2 -2
  44. package/dist/contracts/modules.d.ts +12 -13
  45. package/dist/contracts/types.d.ts +2 -2
  46. package/dist/{custom-node-writes-Bn6uciLb.d.ts → custom-node-writes-TeUvMP13.d.ts} +2 -2
  47. package/dist/{diagnostic-context-B8M79cfK.d.ts → diagnostic-context-DJ0P1uZE.d.ts} +3 -2
  48. package/dist/{diagnostic-hints-BeVjfNOv.d.ts → diagnostic-hints-C9JlLz1L.d.ts} +12 -5
  49. package/dist/{document-protection-CUb55pl_.d.ts → document-protection-C8x3P2J6.d.ts} +1 -1
  50. package/dist/{editor-CZQZmTIU.d.ts → editor-B2ShlO23.d.ts} +368 -52
  51. package/dist/{editor-scope-7o9xweiJ.d.ts → editor-scope-C7ke1KJG.d.ts} +57 -4
  52. package/dist/editor.cjs +1 -1
  53. package/dist/editor.css +5 -3
  54. package/dist/editor.d.ts +606 -34
  55. package/dist/editor.js +1 -1
  56. package/dist/export.cjs +2 -2
  57. package/dist/export.d.ts +17 -15
  58. package/dist/export.js +2 -2
  59. package/dist/{font-resolver-lSBLirLR.d.ts → font-resolver-CWh40DYp.d.ts} +1 -1
  60. package/dist/font-shaping-policy-BvpkrfxM.d.ts +7 -0
  61. package/dist/{headless-document-view-pp3q7wIh.d.ts → headless-document-view-qOpaCF82.d.ts} +2 -2
  62. package/dist/index.cjs +1 -1
  63. package/dist/index.d.ts +24 -24
  64. package/dist/index.js +1 -1
  65. package/dist/{layout-shaping-BD-dF_rn.d.ts → layout-shaping-CWS3j0Xr.d.ts} +3 -2
  66. package/dist/layout.cjs +1 -1
  67. package/dist/layout.d.ts +112 -44
  68. package/dist/layout.js +1 -1
  69. package/dist/{note-properties-qQUjO1GQ.d.ts → note-properties-QsKh24sM.d.ts} +1 -1
  70. package/dist/{omml-equation-BUbU0utM.d.ts → omml-equation-BsLAa2lX.d.ts} +38 -8
  71. package/dist/output.cjs +1 -1
  72. package/dist/output.d.ts +20 -10
  73. package/dist/output.js +1 -1
  74. package/dist/{protocol-DYzzz2ej.d.ts → protocol-COZJUKUY.d.ts} +94 -2
  75. package/dist/{review-paragraph-order-CN_0l2Cb.d.ts → review-paragraph-order-C_QbCKcH.d.ts} +1 -1
  76. package/dist/{semantic-cell-selection-eYag3P47.d.ts → semantic-cell-selection-Cub9FDa8.d.ts} +2 -2
  77. package/dist/{semantic-interaction-kR_QB845.d.ts → semantic-interaction-dApRIYpt.d.ts} +1 -1
  78. package/dist/{semantic-paint-ZayFF3tD.d.ts → semantic-paint-DLzA8sjP.d.ts} +3 -3
  79. package/dist/{semantic-record-queries-Bsgth6lv.d.ts → semantic-record-queries-3yp9o11g.d.ts} +1 -1
  80. package/dist/{semantic-records-DQmumPgL.d.ts → semantic-records-B-akgRch.d.ts} +207 -139
  81. package/dist/store.cjs +1 -1
  82. package/dist/store.d.ts +49 -136
  83. package/dist/store.js +1 -1
  84. package/dist/{tree-package-store-CovjNARF.d.ts → tree-package-store-B8l8DX3Z.d.ts} +612 -428
  85. package/dist/{tree-session-contract-CZVAGl51.d.ts → tree-session-contract-DeLRqohz.d.ts} +28 -13
  86. package/dist/{types-barrel-DuC5QBNC.d.ts → types-barrel-CkF2JbU4.d.ts} +1146 -745
  87. package/package.json +2 -2
  88. package/dist/chunk-236O4YQW.js +0 -2
  89. package/dist/chunk-6JPL6ZAS.cjs +0 -5
  90. package/dist/chunk-6ZQP4HQ7.js +0 -3
  91. package/dist/chunk-BCXH4BXB.js +0 -5
  92. package/dist/chunk-CAJEXXVG.cjs +0 -4
  93. package/dist/chunk-CPWLFPWC.js +0 -30
  94. package/dist/chunk-D7ZMDMPX.cjs +0 -3
  95. package/dist/chunk-DOHF6FA7.cjs +0 -30
  96. package/dist/chunk-DVX32P2P.js +0 -27
  97. package/dist/chunk-E4LOMDNL.js +0 -1
  98. package/dist/chunk-EKLM7NWB.cjs +0 -7
  99. package/dist/chunk-G3K63G7P.cjs +0 -27
  100. package/dist/chunk-JLVBDYVQ.cjs +0 -2
  101. package/dist/chunk-LQX36Z5P.js +0 -7
  102. package/dist/chunk-OSFOJTHU.cjs +0 -18
  103. package/dist/chunk-T6K2QYNZ.cjs +0 -2
  104. package/dist/chunk-TRGAVV2Y.js +0 -4
  105. package/dist/chunk-U4JZVSOE.js +0 -2
  106. package/dist/chunk-YQ5G6LC7.js +0 -18
  107. package/dist/image-resources-tXLQShoC.d.ts +0 -211
@@ -1,16 +1,4 @@
1
- import { S as SupportedImageMime, c as ImageDecodePort } from './image-resources-tXLQShoC.js';
2
- import { Q as OoxmlPackage, ax as RevisionAttributionInput, ay as DrawingTreeDocOp, O as OoxmlPart, N as OoxmlNode, ak as OoxmlElement, az as OoxmlParagraphNode, aA as OoxmlNoteNode, aB as OoxmlContinuationSeparatorNode, aC as OoxmlEndnotesNode, aD as OoxmlFootnotesNode, aE as OoxmlNoteRefNode, aF as OoxmlNoteReferenceNode, aG as OoxmlSeparatorNode, aH as ImpactClass, ag as TreeDocOp, av as TreeOpRejection } from './types-barrel-DuC5QBNC.js';
3
-
4
- /**
5
- * How saved field results are addressed.
6
- *
7
- * - `atomic`: every closed field is one offset unit.
8
- * - `editable`: the results of CREATEDATE, DATE, FILENAME, FILLIN, HYPERLINK, MERGEFIELD,
9
- * PRINTDATE, QUOTE, SAVEDATE, SEQ, and TIME fields are text when their results are unnested.
10
- * Simple fields inside complex field results are not supported in this mode. Other nested
11
- * results, other field types, and legacy FORMTEXT inputs keep their existing addressing.
12
- */
13
- type FieldResultsMode = 'atomic' | 'editable';
1
+ import { Q as OoxmlPackage, af as SupportedImageMime, O as OoxmlPart, N as OoxmlNode, ap as OoxmlElement, aH as PackageWatermarkWrite, aI as OoxmlInvariantIssue, aE as ImageDecodePort, aG as RevisionAttributionInput, aJ as DrawingTreeDocOp, aK as ImpactClass, ao as TreeDocOp, aC as TreeOpRejection, aL as OoxmlParagraphNode, aM as OoxmlNoteNode, aN as OoxmlContinuationSeparatorNode, aO as OoxmlEndnotesNode, aP as OoxmlFootnotesNode, aQ as OoxmlNoteRefNode, aR as OoxmlNoteReferenceNode, aS as OoxmlSeparatorNode } from './types-barrel-CkF2JbU4.js';
14
2
 
15
3
  /** A freshly allocated drawing property id, or the reason one could not be minted. */
16
4
  type DrawingPropertyIdResult = {
@@ -74,52 +62,6 @@ interface ExternalImageFetchPort {
74
62
  }>>;
75
63
  }
76
64
 
77
- interface InsertImageInput {
78
- readonly paragraphId: string;
79
- readonly offset: number;
80
- readonly bytes: Uint8Array;
81
- readonly mime: SupportedImageMime;
82
- readonly widthPoints: number;
83
- readonly heightPoints: number;
84
- readonly decodePort: ImageDecodePort;
85
- readonly expectedPackageRevision: number;
86
- readonly commitGuard?: () => boolean;
87
- readonly title?: string;
88
- readonly description?: string;
89
- readonly hyperlink?: string;
90
- /** Present in suggesting mode: the inserted drawing's run goes into a `w:ins`. */
91
- readonly revision?: RevisionAttributionInput;
92
- /**
93
- * Collaboration actor for the ids this insert mints — `wp:docPr/@id`, the image
94
- * relationship, and a hyperlink relationship when one is asked for.
95
- *
96
- * EXPLICIT, and not the ambient `runWithTransactionActor` binding, because this entry is
97
- * async: the mint runs after `await validateEmbeddedImageForCommit` and the ambient actor
98
- * is synchronous-only. A wrap around the call would compile, read as correct, and be gone
99
- * by the time the id is taken. Omitted (solo) keeps Word's dense `highest + 1` sequence.
100
- */
101
- readonly actorId?: string;
102
- }
103
- interface ReplaceImageOptions {
104
- readonly expectedPackageRevision: number;
105
- readonly commitGuard?: () => boolean;
106
- /** Collaboration actor for the replacement's minted ids. See {@link InsertImageInput.actorId}. */
107
- readonly actorId?: string;
108
- }
109
- type ImageIntentResult = (Extract<PackageTransactResult, {
110
- ok: true;
111
- }> & {
112
- readonly drawingNodeId?: string;
113
- readonly mediaPartName?: string;
114
- }) | Extract<PackageTransactResult, {
115
- ok: false;
116
- }>;
117
- interface ApplyImagePropertiesInput {
118
- readonly drawingNodeId: string;
119
- readonly ops: readonly DrawingTreeDocOp[];
120
- readonly hyperlink: string | null;
121
- }
122
-
123
65
  /** `w:headerReference w:type` vocabulary (ECMA-376 §17.10.5): default, first page, even pages. */
124
66
  type HeaderFooterVariant = 'default' | 'first' | 'even';
125
67
  /** Header vs footer region kind for chrome and lifecycle ops. */
@@ -233,6 +175,13 @@ type HeaderFooterLifecycleOp = {
233
175
  */
234
176
  readonly op: 'setDocumentProtection';
235
177
  readonly enforce: boolean;
178
+ } | {
179
+ /**
180
+ * Replace the document watermark in every header a section shows; null removes it.
181
+ * It may create a section's default header, so it rides this lane's package undo unit.
182
+ */
183
+ readonly op: 'setWatermark';
184
+ readonly watermark: PackageWatermarkWrite | null;
236
185
  };
237
186
  /** Why a header/footer lifecycle op was refused. */
238
187
  type HeaderFooterLifecycleRejection = 'invalidArgs' | 'tree-invariant';
@@ -258,338 +207,198 @@ declare function isHeaderFooterLifecycleOp(op: {
258
207
  */
259
208
  declare function applyHeaderFooterLifecycleOp(pkg: OoxmlPackage, op: HeaderFooterLifecycleOp): HeaderFooterLifecycleResult;
260
209
 
261
- /** UTF-16 placeholder for one atomic note unit in `paragraphTextOf` / segments. */
262
- declare const NOTE_ATOM_CHAR = "\uFFFC";
263
- /** Signed 32-bit Word-compatible note id range (positive allocation only). */
264
- declare const NOTE_ID_MIN = 1;
265
- /** Largest note id Word accepts — signed 32-bit. */
266
- declare const NOTE_ID_MAX = 2147483647;
267
- /** Reserved separator / continuation ids — never allocated for normal notes. */
268
- declare const NOTE_SEPARATOR_ID = -1;
269
- /** Reserved id of the continuation-separator entry. Never allocated to a real note. */
270
- declare const NOTE_CONTINUATION_SEPARATOR_ID = 0;
271
- /** Which notes part a note lives in. Decides both placement and numbering rules. */
272
- type NoteKind = 'footnote' | 'endnote';
273
- /** Authored `ST_FtnEdn` including explicit `normal`. Absent means Word default (normal). */
274
210
  /**
275
- * What a note entry IS.
211
+ * How saved field results are addressed.
276
212
  *
277
- * Only `normal` is a note a reader sees. The others are the furniture Word stores in the same
278
- * part — the rules and notices drawn around the note area — reached by reserved ids.
213
+ * - `atomic`: every closed field is one offset unit.
214
+ * - `editable`: the results of CREATEDATE, DATE, FILENAME, FILLIN, HYPERLINK, MERGEFIELD,
215
+ * PRINTDATE, QUOTE, SAVEDATE, SEQ, and TIME fields are text when their results are unnested.
216
+ * Simple fields inside complex field results are not supported in this mode. Other nested
217
+ * results, other field types, and legacy FORMTEXT inputs keep their existing addressing.
279
218
  */
280
- type NoteType = 'normal' | 'separator' | 'continuationSeparator' | 'continuationNotice';
281
- /** Parse a signed decimal note id; reject non-integers and out-of-int32 values. */
282
- declare function parseNoteId(raw: string | undefined): number | null;
219
+ type FieldResultsMode = 'atomic' | 'editable';
220
+
221
+ /** An edited part, or the invariant violations that rejected the edit. */
222
+ type OoxmlEditResult = {
223
+ readonly ok: true;
224
+ readonly part: OoxmlPart;
225
+ } | {
226
+ readonly ok: false;
227
+ readonly issues: readonly OoxmlInvariantIssue[];
228
+ };
283
229
  /**
284
- * Canonical `EditorScope { kind: 'note'; id }` encoding.
230
+ * An opt-in relaxation of `ST_Lock` for hosts that protect WHAT a control says, not how it looks.
285
231
  *
286
- * Footnote id `1` and endnote id `1` coexist, so the kind is part of the string:
287
- * `footnote:2` / `endnote:-1`. Do not invent a parallel scope union arm.
288
- */
289
- declare function formatNoteScopeId(noteKind: NoteKind, noteId: number): string;
290
- /**
291
- * Parse {@link formatNoteScopeId}. Returns `null` for malformed / out-of-range ids.
292
- */
293
- declare function parseNoteScopeId(id: string): {
294
- readonly noteKind: NoteKind;
295
- readonly noteId: number;
296
- } | null;
297
- /** Whether a node is a `w:footnotes` part root. */
298
- declare function isFootnotesNode(node: OoxmlNode): node is OoxmlFootnotesNode;
299
- /** Whether a node is a `w:endnotes` part root. */
300
- declare function isEndnotesNode(node: OoxmlNode): node is OoxmlEndnotesNode;
301
- /** Whether a node is a `w:footnote` or `w:endnote` body. */
302
- declare function isNoteNode(node: OoxmlNode): node is OoxmlNoteNode;
303
- /** Whether a node is a body-side `w:footnoteReference` / `w:endnoteReference`. */
304
- declare function isNoteReferenceNode(node: OoxmlNode): node is OoxmlNoteReferenceNode;
305
- /** Whether a node is the `w:footnoteRef` / `w:endnoteRef` mark inside a note's own body. */
306
- declare function isNoteRefNode(node: OoxmlNode): node is OoxmlNoteRefNode;
307
- /** Whether a node is the `w:separator` rule drawn above the note area. */
308
- declare function isSeparatorNode(node: OoxmlNode): node is OoxmlSeparatorNode;
309
- /** Whether a node is the `w:continuationSeparator` rule used on continuation pages. */
310
- declare function isContinuationSeparatorNode(node: OoxmlNode): node is OoxmlContinuationSeparatorNode;
311
- /** Typed or generic `w:footnote` / `w:endnote`. */
312
- declare function noteKindOf(node: OoxmlNode): NoteKind | null;
313
- /** Authored `@w:id` on a note or noteReference when parseable. */
314
- declare function noteIdOf(node: OoxmlNode): number | null;
315
- /** Authored `@w:type` (`ST_FtnEdn`) when present and schema-legal; else `undefined`. */
316
- declare function noteTypeOf(node: OoxmlNode): NoteType | undefined;
317
- /** Whether a note is a normal (body) note — absent type and explicit `normal` both count. */
318
- declare function isNormalNote(node: OoxmlNode): boolean;
319
- /** Typed or generic footnote/endnote reference kind. */
320
- declare function noteReferenceKindOf(node: OoxmlNode): NoteKind | null;
321
- /** Typed or generic `w:footnoteRef` / `w:endnoteRef`. */
322
- declare function noteRefKindOf(node: OoxmlNode): NoteKind | null;
323
- /**
324
- * Read `@w:customMarkFollows` on a note reference.
325
- * Returns `undefined` when absent; otherwise the OOXML on/off interpretation.
326
- */
327
- declare function customMarkFollows(node: OoxmlNode): boolean | undefined;
328
- /** Model text contributed by one atomic note unit. */
329
- declare function noteAtomText(): typeof NOTE_ATOM_CHAR;
330
- /**
331
- * True when a run-inner node is a typed note atom that contributes one UTF-16 unit.
332
- * Demoted/malformed known locals stay generic and contribute nothing.
232
+ * ECMA-376 `contentLocked` forbids editing the control's contents and Word reads formatting as
233
+ * part of that, which is the default here. A host that puts protected text in a control (a
234
+ * template placeholder the user must not retype) but lets authors style it sets
235
+ * `allowFormattingInLockedContent`: a run-property write (bold, colour, font, size, highlight …),
236
+ * on runs or on a paragraph mark, then passes the CONTENT half of the lock. The exception never
237
+ * admits a property that hides text (`w:vanish`, `w:specVanish`, `w:webHidden`), which the
238
+ * run-property vocabulary refuses today anyway. Text edits, deletion of the content, paragraph properties,
239
+ * removal of a `sdtLocked` wrapper and `w:dataBinding` refusals are unchanged.
240
+ *
241
+ * @public
333
242
  */
334
- declare function isNoteAtomNode(node: OoxmlNode): boolean;
243
+ interface ContentLockPolicy {
244
+ /** Let run formatting pass a content lock; text and removal stay refused. Default `false`. */
245
+ readonly allowFormattingInLockedContent?: boolean;
246
+ }
335
247
  /**
336
- * One atomic note span inside a paragraph for caret / delete / selection.
248
+ * How an edit primitive validates its result.
337
249
  *
338
- * Each typed note atom is a single-node span (`removeNodeIds` = that node).
250
+ * By default every primitive runs the full-part invariant validation before handing its
251
+ * result back — the safe reading for an isolated call. A TRANSACTION applying many ops pays
252
+ * that full-tree walk once per primitive, which is what made a hundred-paragraph paste
253
+ * quadratic; it defers instead, and runs the same validation ONCE on the final tree before
254
+ * anything is published. Deferring is only sound for a caller that owns a commit boundary:
255
+ * nothing may escape between the unvalidated intermediate and the validated result.
339
256
  */
340
- interface AtomicNoteSpan {
341
- readonly kind: 'noteReference' | 'noteRef' | 'separator' | 'continuationSeparator';
342
- readonly node: OoxmlNode;
343
- readonly runId: string;
344
- readonly removeNodeIds: readonly string[];
257
+ interface EditOptions {
258
+ readonly deferValidation?: boolean;
259
+ /**
260
+ * How a content control's `w:lock` is read for ops that only restyle text. Absent means
261
+ * the default: `contentLocked` / `sdtContentLocked` refuse every write, formatting included.
262
+ */
263
+ readonly contentLockPolicy?: ContentLockPolicy;
264
+ /**
265
+ * A shared minter for `@w:id` on a NEW revision, for the ops of one transaction.
266
+ *
267
+ * `nextRevisionId` walks the whole part to find the highest id in use, and a formatting
268
+ * write emits one op per run — so a Select All + Bold in suggesting mode paid a
269
+ * document-wide walk thousands of times over. The transaction hands its ops one minter
270
+ * instead, and it is DROPPED the moment an op that can import foreign revision ids runs
271
+ * (a paste carries its own), because a stale minter would hand out an id the document had
272
+ * just started using — and two revisions sharing an address are one card in the pane.
273
+ *
274
+ * Absent means "mint your own": every applier falls back to its own walk, which is what a
275
+ * caller outside a transaction gets.
276
+ */
277
+ readonly revisionIds?: () => string;
278
+ /**
279
+ * The transaction's revision-id bookkeeping for the tracked TEXT ops, where `revisionIds`
280
+ * above hands the property ops one shared id for their whole group.
281
+ *
282
+ * Absent means "mint your own and remember nothing", which is what a caller outside a
283
+ * transaction gets: every applier falls back to its own walk of the part.
284
+ */
285
+ readonly trackedRevisionIds?: TransactionRevisionIds;
286
+ /**
287
+ * How the op addresses saved field results (`field-result-mode.ts`). Absent keeps the mode
288
+ * of the enclosing store call, which is `atomic` outside any editable transaction.
289
+ */
290
+ readonly fieldResults?: FieldResultsMode;
345
291
  }
346
292
  /**
347
- * Collect typed note atoms in document order (one segment each).
293
+ * What one transaction knows about the revision ids its tracked text ops write.
348
294
  *
349
- * Walks the same paragraph-inline surface as `segmentsOf` / `walkParagraphInline`:
350
- * hyperlinks and content controls flatten; only direct run children contribute atoms.
351
- * A demoted run-inner SDT husk stays opaque — no phantom addressable hit.
295
+ * TWO questions, and they are the same bookkeeping. `nextRevisionId` walks the whole part, so
296
+ * a replacement — a `deleteText` and an `insertText` in one transaction — walked it twice, and
297
+ * on a five-hundred-page document that doubled the cost of every one. And an insertion aimed
298
+ * at the front edge of a deletion relocates past it only when that deletion is THIS
299
+ * transaction's own: typing over a selection. Backspace, then a keystroke, is two
300
+ * transactions, and there the typed text belongs in FRONT of the struck character, as Word
301
+ * writes it. Adjacency alone cannot tell the two apart; the ids can.
352
302
  */
353
- declare function atomicNoteSpansOf(paragraph: OoxmlParagraphNode): readonly AtomicNoteSpan[];
354
- /** Cap on notes scanned when allocating / indexing a notes part. */
355
- declare const MAX_NOTES_PER_PART = 10000;
303
+ interface TransactionRevisionIds {
304
+ /** A fresh `@w:id`, from this transaction's one walk of the part. */
305
+ mint(): string;
306
+ /**
307
+ * Record a wrapper this transaction wrote, by its FULL `CT_TrackChange` identity — id,
308
+ * author and date, as `revisionKey` spells it. Not by the id alone: that number comes out
309
+ * of the file, nothing makes it unique, and a strike joining `w:id="3"` in one paragraph
310
+ * would otherwise claim an unrelated `w:id="3"` elsewhere as this edit's own.
311
+ */
312
+ wrote(key: string): void;
313
+ /** Whether this transaction wrote a wrapper with that identity. */
314
+ wroteUnder(key: string): boolean;
315
+ }
316
+ /** Every node id currently present in the part. */
317
+ declare function collectNodeIds(part: OoxmlPart): Set<string>;
356
318
  /**
357
- * Allocate the next positive signed 32-bit note id for a notes-part root.
319
+ * Mint ids for nodes an edit introduces.
358
320
  *
359
- * Solo: `max(existing)+1`, the dense sequence Word writes. `null` on exhaustion, so the
360
- * caller refuses rather than wrapping onto an id a note already holds.
321
+ * Deterministic and collision-checked against the whole part: a structural-path id from the
322
+ * original parse (`/word/document.xml#0.1.2`) and a minted one (`/word/document.xml#new:3`)
323
+ * can never coincide, and the counter skips anything already taken so repeated edits in one
324
+ * session stay unique.
361
325
  *
362
- * With a collaboration actor bound — explicit argument, or the open store transaction — the
363
- * id is the next unused value in that actor's stripe. Two peers each adding their first
364
- * footnote from one snapshot otherwise both mint `w:id="1"`: the merge keeps both bodies
365
- * under one id, both `w:footnoteReference` marks resolve to the first, and one author's note
366
- * becomes unreachable.
326
+ * Allocators share reservations across edited roots and restored roots. Preview edits,
327
+ * detached nodes, and undo branches must not reuse an identity that collaboration retains.
367
328
  *
368
- * The reserved ids stay reserved either way. `0` (continuation separator) is seeded as used,
369
- * so a residue of 0 advances one class instead of taking Word's furniture, and a stripe walk
370
- * only ever climbs from a non-negative residue, so `-1` (separator) is unreachable.
371
- */
372
- declare function allocateNoteId(notesRoot: OoxmlNode, actorId?: string): number | null;
373
- /** Collect note bodies from a footnotes/endnotes root, bounded. */
374
- declare function notesOf(root: OoxmlNode): readonly OoxmlNoteNode[];
375
- /** Find a typed note by id inside a notes-part root. */
376
- declare function findNoteById(root: OoxmlNode, noteId: number): OoxmlNoteNode | undefined;
377
-
378
- /** Cap on nodes visited while scanning for note references across stories. */
379
- declare const MAX_NOTE_REFERENCE_SCAN = 20000;
380
- /**
381
- * Cap on XML parts walked in one package-wide note-reference scan (N/N+1 gate).
382
- * Soft targets are not allowed: exceeding this marks the shared budget truncated.
329
+ * Checks the part's node index directly rather than copying every id into a fresh set: the
330
+ * copy was O(document) per op, and an allocator is created for every op.
383
331
  */
384
- declare const MAX_NOTE_REFERENCE_PARTS = 256;
332
+ declare function createNodeIdAllocator(part: OoxmlPart, family?: 'new' | 'paste'): () => string;
333
+ /** Whether a node id exists in the part. */
334
+ declare function hasNode(part: OoxmlPart, nodeId: string): boolean;
335
+ /** Read a node back out of a part by id. */
336
+ declare function findNode(part: OoxmlPart, nodeId: string): OoxmlNode | null;
337
+ /** The element that holds a node, or null for the root and for unknown ids. */
338
+ declare function parentNodeOf(part: OoxmlPart, nodeId: string): OoxmlElement | null;
339
+ /** Replace one node's children wholesale. */
340
+ declare function replaceChildren(part: OoxmlPart, nodeId: string, children: readonly OoxmlNode[], options?: EditOptions): OoxmlEditResult;
341
+ /** Insert children into a node at `index` (clamped to the child list). */
342
+ declare function insertChildren(part: OoxmlPart, nodeId: string, index: number, children: readonly OoxmlNode[], options?: EditOptions): OoxmlEditResult;
343
+ /** Replace one node with another, keeping its position among its siblings. */
344
+ declare function replaceNode(part: OoxmlPart, nodeId: string, replacement: OoxmlNode, options?: EditOptions): OoxmlEditResult;
345
+ /** Remove a node and its subtree. */
346
+ declare function removeNode(part: OoxmlPart, nodeId: string, options?: EditOptions): OoxmlEditResult;
385
347
  /**
386
- * A load-time note problem worth reporting.
348
+ * Apply several primitives as ONE atomic step.
387
349
  *
388
- * `dangling-note-reference` is a citation pointing at no note; `note-reference-scan-truncated`
389
- * says the scan hit its budget, so absence of further diagnostics is not proof of correctness.
390
- */
391
- type NoteDiagnosticCode = 'dangling-note-reference' | 'note-reference-scan-truncated';
392
- /**
393
- * Load-time note diagnostics. Array API preserved; truncation is signaled as a typed
394
- * entry rather than by throwing or rejecting the package.
350
+ * Each edit runs against the result of the previous one, and the whole sequence is
351
+ * validated once at the end. If any step fails, the ORIGINAL part is what the caller keeps
352
+ * — there is no partially-edited intermediate to publish. This is the shape a multi-`DocOp`
353
+ * store transaction needs.
395
354
  */
396
- type NoteDiagnostic = {
397
- readonly code: 'dangling-note-reference';
398
- readonly noteKind: NoteKind;
399
- readonly noteId: number;
400
- /** Paragraph / container node id when known. */
401
- readonly sourceNodeId?: string;
402
- } | {
403
- /** Hard visited/part budget or soft hit cap stopped the scan before full coverage. */
404
- readonly code: 'note-reference-scan-truncated';
405
- };
406
- /** One note reference found in a story, with where it sits. */
407
- interface NoteReferenceHit {
408
- readonly noteKind: NoteKind;
409
- readonly noteId: number;
410
- readonly nodeId: string;
355
+ declare function applyEdits(part: OoxmlPart, edits: readonly ((current: OoxmlPart) => OoxmlEditResult)[], options?: EditOptions): OoxmlEditResult;
356
+
357
+ interface InsertImageInput {
411
358
  readonly paragraphId: string;
412
- /** Canonical UTF-16 atom offset within {@link paragraphId} (U+FFFC model). */
413
- readonly atomOffset: number;
414
- readonly customMarkFollows: boolean;
415
- /** Canonical part name that owns this reference. */
416
- readonly partName: string;
359
+ readonly offset: number;
360
+ readonly bytes: Uint8Array;
361
+ readonly mime: SupportedImageMime;
362
+ readonly widthPoints: number;
363
+ readonly heightPoints: number;
364
+ readonly decodePort: ImageDecodePort;
365
+ readonly expectedPackageRevision: number;
366
+ readonly commitGuard?: () => boolean;
367
+ readonly title?: string;
368
+ readonly description?: string;
369
+ readonly hyperlink?: string;
370
+ /** Present in suggesting mode: the inserted drawing's run goes into a `w:ins`. */
371
+ readonly revision?: RevisionAttributionInput;
372
+ /**
373
+ * Collaboration actor for the ids this insert mints — `wp:docPr/@id`, the image
374
+ * relationship, and a hyperlink relationship when one is asked for.
375
+ *
376
+ * EXPLICIT, and not the ambient `runWithTransactionActor` binding, because this entry is
377
+ * async: the mint runs after `await validateEmbeddedImageForCommit` and the ambient actor
378
+ * is synchronous-only. A wrap around the call would compile, read as correct, and be gone
379
+ * by the time the id is taken. Omitted (solo) keeps Word's dense `highest + 1` sequence.
380
+ */
381
+ readonly actorId?: string;
417
382
  }
418
- /** Mutable visited-node + part budget shared across parts / package snapshots. */
419
- interface NoteReferenceScanBudget {
420
- visited: number;
421
- readonly maxVisited: number;
422
- parts: number;
423
- readonly maxParts: number;
424
- /** Set when a walk stops before finishing because a cap was hit. */
425
- truncated: boolean;
426
- }
427
- /** A bounded budget for scanning note references, so a crafted document cannot stall a load. */
428
- declare function createNoteReferenceScanBudget(maxVisited?: number, maxParts?: number): NoteReferenceScanBudget;
429
- /**
430
- * Resolve the footnotes or endnotes part via safe Internal document relationships.
431
- *
432
- * Unusable matching relationships (External, unsafe target, missing part, wrong root)
433
- * are skipped — never fetched, never accepted — so a decoy first match cannot hide a
434
- * later usable Internal notes part (same continue-past-bad pattern as settingsPartOf).
435
- */
436
- declare function resolveNotesPart(pkg: OoxmlPackage, noteKind: NoteKind): OoxmlPart | null;
437
- /** Walk a part without applying a layout projection. */
438
- declare function collectNoteReferences(part: OoxmlPart, options?: {
439
- readonly maxHits?: number;
440
- readonly budget?: NoteReferenceScanBudget;
441
- }): readonly NoteReferenceHit[];
442
- /** Collect references across every XML part under one shared part + visited-node budget. */
443
- declare function collectPackageNoteReferences(pkg: OoxmlPackage, options?: {
444
- readonly budget?: NoteReferenceScanBudget;
445
- /** Soft hit cap for diagnostics. Omit / Infinity for mutation scans. */
446
- readonly maxHits?: number;
447
- }): readonly NoteReferenceHit[];
448
- /**
449
- * Load diagnostics for dangling note references. Fail-open: never throws or mutates;
450
- * returns diagnostics for callers to surface. Does not invent missing note bodies.
451
- *
452
- * When the hard visited/part budget truncates or the soft hit cap binds, appends a single
453
- * `note-reference-scan-truncated` entry so incomplete coverage is visible without breaking
454
- * consumers that filter on `dangling-note-reference`.
455
- */
456
- declare function diagnoseNoteReferences(pkg: OoxmlPackage): readonly NoteDiagnostic[];
457
- /** Whether a notes-part root contains a note with the given id (any type). */
458
- declare function notesPartHasId(part: OoxmlPart, noteId: number): boolean;
459
- /** List normal (body) note ids in document order, bounded. */
460
- declare function normalNoteIds(part: OoxmlPart): readonly number[];
461
-
462
- /** How far a note lifecycle op reaches — which parts a caller must expect to have changed. */
463
- type NoteLifecycleImpact = 'flow-structural' | 'global';
464
- /**
465
- * A note lifecycle mutation: insert, delete, convert, or set properties.
466
- *
467
- * Package-level rather than story-level, because every one of these touches the main document,
468
- * the notes part, the document relationships AND `[Content_Types].xml` together.
469
- */
470
- type NoteLifecycleOp = {
471
- readonly op: 'insertNote';
472
- readonly noteKind: NoteKind;
473
- readonly paragraphId: string;
474
- readonly offset: number;
475
- /**
476
- * Write the citing reference as a TRACKED insertion: the reference run goes inside a
477
- * `w:ins` carrying this attribution. The note body stays plain — rejecting the
478
- * reference removes it, and the orphaned body is swept by the same cascade an
479
- * untracked reference deletion triggers.
480
- */
481
- readonly revision?: RevisionAttributionInput;
482
- } | {
483
- readonly op: 'deleteNote';
484
- readonly noteKind: NoteKind;
485
- readonly noteId: number;
486
- } | {
487
- readonly op: 'convertNote';
488
- readonly fromKind: NoteKind;
489
- readonly noteId: number;
490
- } | {
491
- /**
492
- * Convert every normal note of `fromKind` in document order. One atomic package
493
- * transaction / undo unit with the same validation as repeated `convertNote`.
494
- */
495
- readonly op: 'convertAllNotes';
496
- readonly fromKind: NoteKind;
497
- } | {
498
- readonly op: 'setNoteProperties';
499
- readonly scope: 'document' | 'section';
500
- readonly sectionIndex?: number;
501
- readonly footnote?: {
502
- readonly numFmt?: string;
503
- readonly numRestart?: string;
504
- readonly position?: string;
505
- readonly numStart?: number;
506
- };
507
- readonly endnote?: {
508
- readonly numFmt?: string;
509
- readonly numRestart?: string;
510
- readonly position?: string;
511
- readonly numStart?: number;
512
- };
513
- };
514
- /** Why a note lifecycle op was refused. */
515
- type NoteLifecycleRejection = 'invalidArgs' | 'invalid-property-value' | 'tree-invariant';
516
- /**
517
- * A new package, or a typed rejection.
518
- *
519
- * Application is PURE and all-or-nothing: there are no partial writes, so a refused op leaves the
520
- * caller's package untouched rather than half-migrated across four parts.
521
- */
522
- type NoteLifecycleResult = {
523
- readonly ok: true;
524
- readonly package: OoxmlPackage;
525
- readonly impact: NoteLifecycleImpact;
526
- readonly noteId?: number;
527
- readonly noteKind?: NoteKind;
528
- readonly createdPartName?: string;
529
- } | {
530
- readonly ok: false;
531
- readonly reason: NoteLifecycleRejection;
532
- readonly detail?: string;
533
- };
534
- /** Whether an op is a note lifecycle op rather than a story-level one. */
535
- declare function isNoteLifecycleOp(op: {
536
- readonly op: string;
537
- }): op is NoteLifecycleOp;
538
- /** Attribution and limits applied to one note lifecycle op. */
539
- interface NoteLifecycleOptions {
540
- /**
541
- * Shared part + visited-node budget for reference scans. When omitted a fresh default
542
- * budget is used. Truncation rejects the op with the original package unchanged.
543
- */
544
- readonly scanBudget?: NoteReferenceScanBudget;
383
+ interface ReplaceImageOptions {
384
+ readonly expectedPackageRevision: number;
385
+ readonly commitGuard?: () => boolean;
386
+ /** Collaboration actor for the replacement's minted ids. See {@link InsertImageInput.actorId}. */
387
+ readonly actorId?: string;
545
388
  }
546
- /**
547
- * Apply one note lifecycle op atomically. Rejected ops leave the input package untouched.
548
- */
549
- declare function applyNoteLifecycleOp(pkg: OoxmlPackage, op: NoteLifecycleOp, options?: NoteLifecycleOptions): NoteLifecycleResult;
550
- /**
551
- * How deleting text cascades into the notes it referenced.
552
- *
553
- * A note's body and the citation reaching it are one thing to a reader, so removing the reference
554
- * must remove the body too or the notes part keeps an entry nothing points at.
555
- */
556
- interface CascadeDeletedNoteReferencesOptions {
557
- /**
558
- * Independent full budgets per snapshot. When omitted each snapshot gets its own
559
- * default budget — never share one counter across before/after walks.
560
- */
561
- readonly beforeBudget?: NoteReferenceScanBudget;
562
- readonly afterBudget?: NoteReferenceScanBudget;
389
+ type ImageIntentResult = (Extract<PackageTransactResult, {
390
+ ok: true;
391
+ }> & {
392
+ readonly drawingNodeId?: string;
393
+ readonly mediaPartName?: string;
394
+ }) | Extract<PackageTransactResult, {
395
+ ok: false;
396
+ }>;
397
+ interface ApplyImagePropertiesInput {
398
+ readonly drawingNodeId: string;
399
+ readonly ops: readonly DrawingTreeDocOp[];
400
+ readonly hyperlink: string | null;
563
401
  }
564
- /**
565
- * Remove note bodies for references that disappeared between two package snapshots.
566
- * Used when `deleteText` or `deleteBlock` removes a `noteReference` atom so body+ref stay
567
- * one undo unit.
568
- *
569
- * Each snapshot gets an independent full visited/part budget. If either scan truncates
570
- * the cascade fails closed so a hostile package cannot skip body deletion silently —
571
- * without accidentally halving capacity by charging both walks to one counter.
572
- */
573
- declare function cascadeDeletedNoteReferences(before: OoxmlPackage, after: OoxmlPackage, options?: CascadeDeletedNoteReferencesOptions): OoxmlPackage | null;
574
-
575
- /**
576
- * The identity of one continuous user gesture, for history grouping.
577
- *
578
- * A live color picker, a font-size stepper or a spacing slider applies every intermediate
579
- * value so the document updates under the pointer, and each of those writes is a
580
- * transaction in its own right — it must render, and a collaborator must receive it. What
581
- * they are NOT is separate undo steps. The caller mints one token when the gesture starts
582
- * (`Symbol('color-drag')`) and passes it with every write until the gesture ends.
583
- *
584
- * A symbol rather than a string, so two unrelated callers cannot collide by naming their
585
- * gesture the same way, and so a token cannot outlive its gesture by accident through
586
- * serialization.
587
- */
588
- type HistoryGroup = symbol;
589
- type HistoryCaptureKind = 'started' | 'extended' | 'split';
590
- type HistoryCaptureReason = 'package-unit' | 'composition';
591
- declare function observeHistoryGroup<T>(group: HistoryGroup, report: (kind: HistoryCaptureKind, reason?: HistoryCaptureReason) => void, run: () => T): T;
592
- declare function reportHistoryGroup(group: HistoryGroup | undefined, kind: HistoryCaptureKind, reason?: HistoryCaptureReason): void;
593
402
 
594
403
  /** How one transaction behaves: its story scope, its attribution, and its selection marks. */
595
404
  interface TransactOptions {
@@ -646,6 +455,11 @@ interface TreeDocumentStoreOptions {
646
455
  * document can gain or lose protection while its stories stay open.
647
456
  */
648
457
  readonly settingsPart?: () => OoxmlPart | null | undefined;
458
+ /**
459
+ * Opt-in relaxation of `w:lock` for run-property writes. See {@link ContentLockPolicy};
460
+ * absent, `contentLocked` and `sdtContentLocked` refuse formatting like any other write.
461
+ */
462
+ readonly contentLockPolicy?: ContentLockPolicy;
649
463
  }
650
464
 
651
465
  /** A selection the caller wants restored when an entry is undone or redone. */
@@ -746,6 +560,10 @@ interface TransactionContext {
746
560
  * The edit is a pure function of the working package, so a rejected transaction discards it
747
561
  * with everything else. Returning the SAME package is a no-op, not a failure — a primitive
748
562
  * that finds nothing to do says so that way.
563
+ *
564
+ * Engine package edits refuse by throwing the internal `PackageEditRefusal`
565
+ * (`package-edit-refusal.ts`): the transaction is refused with its reason, `package-changed`
566
+ * by default, and nothing is applied. Any other throw escapes `transact` as a crash.
749
567
  */
750
568
  applyPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): boolean;
751
569
  /** The selection to restore when this entry is undone. */
@@ -809,10 +627,13 @@ declare class TreeDocumentStore {
809
627
  private commitCounter;
810
628
  private readonly undoStack;
811
629
  private readonly redoStack;
630
+ private pushCount;
812
631
  private readonly subscribers;
813
632
  private readonly historyLimit;
814
633
  /** See {@link TreeDocumentStoreOptions.settingsPart}. */
815
634
  private readonly settingsPartOverride;
635
+ /** See {@link TreeDocumentStoreOptions.contentLockPolicy}. */
636
+ private readonly contentLockPolicy;
816
637
  /** Package-aware story tag applied to publishes (including undo/redo). */
817
638
  private storyRef;
818
639
  /** Open composition, if any. While set, transactions extend one entry (task 5.5). */
@@ -838,6 +659,12 @@ declare class TreeDocumentStore {
838
659
  get canRedo(): boolean;
839
660
  /** Retained entries — the unit `undo()` reverses, so tests can assert grouping. */
840
661
  get historyDepth(): number;
662
+ /**
663
+ * How many entries this store has ever pushed. Monotonic, unlike `historyDepth`, which
664
+ * stands still at the history limit because the oldest entry is shifted out.
665
+ * @internal
666
+ */
667
+ get historyPushCount(): number;
841
668
  get compositionActive(): boolean;
842
669
  /**
843
670
  * Replace the package OUTSIDE the transaction and history lanes.
@@ -909,73 +736,412 @@ declare class TreeDocumentStore {
909
736
  selectionForUndo(): SelectionMark | null;
910
737
  /** The selection to restore for the entry `redo()` would reapply next. */
911
738
  selectionForRedo(): SelectionMark | null;
912
- private pushUndo;
913
- private publish;
914
- }
915
-
916
- /** Decoded fragment payloads above this cap are refused before any package work. */
917
- declare const MAX_FRAGMENT_DECODED_BYTES: number;
918
- interface FragmentPasteInput {
919
- readonly paragraphId: string;
920
- readonly offset: number;
921
- /** The fragment package zip (from the clipboard codec or the HTML projection). */
922
- readonly fragmentBytes: Uint8Array;
923
- readonly lastMarkCovered: boolean;
924
- /** Ops that clear the current selection first (the surface's deleteSelectionPlan). */
925
- readonly priorOps?: readonly TreeDocOp[];
926
739
  /**
927
- * Collaboration actor for the ids this paste mints.
928
- *
929
- * A paste freshens three package-wide namespaces — bookmark `@w:id`, revision `@w:id` and
930
- * `wp:docPr/@id` — and the merge reads the actor off the open transaction. Without one, two
931
- * replicas pasting the same fragment from one snapshot mint the same numbers, and only the
932
- * merged document shows it. Omitted (solo) keeps Word's dense sequences.
740
+ * Remove the newest undo entry and answer its selections, for the package coordinator
741
+ * when a package history group absorbs the story pointer that entry belonged to.
742
+ * @internal
933
743
  */
934
- readonly actorId?: string;
935
- }
936
- type FragmentPasteResult = (Extract<PackageTransactResult, {
937
- ok: true;
938
- }> & {
939
- readonly blockCount: number;
940
- }) | Extract<PackageTransactResult, {
941
- ok: false;
942
- }>;
943
- /**
944
- * Read the fragment through the bounded file-open trust boundary, merge its resources,
945
- * and land its blocks — all inside one transaction. Any refusal leaves the document
946
- * exactly as it was; the caller (the paste router) degrades to the next flavour.
947
- */
948
- declare function applyFragmentPaste(store: TreePackageStore, scope: StoryScope, input: FragmentPasteInput): FragmentPasteResult;
949
-
950
- /** Attribution stamped on one remote canonical publication. */
951
- interface RemotePackageAttribution {
952
- readonly origin: string;
953
- readonly actorId?: string;
954
- readonly operationId?: string;
744
+ discardNewestUndoEntry(): Pick<HistoryEntry, 'selectionBefore' | 'selectionAfter'> | null;
745
+ private pushUndo;
746
+ private publish;
955
747
  }
956
748
 
957
- type NoteCascadeFn = (before: OoxmlPackage, after: OoxmlPackage) => OoxmlPackage | null;
958
749
  /**
959
- * Editable story target.
750
+ * The identity of one continuous user gesture, for history grouping.
960
751
  *
961
- * Body and headerFooter mirror `EditorScope`. Notes use one lazy store per notes part
962
- * (`notesPart`) — not one store per note — resolved through safe document relationships.
963
- */
964
- type StoryScope = {
965
- readonly kind: 'body';
966
- } | {
967
- readonly kind: 'headerFooter';
968
- readonly rId: string;
969
- } | {
970
- readonly kind: 'notesPart';
971
- readonly noteKind: NoteKind;
972
- };
973
- /**
974
- * Why a story scope could not be resolved to a part.
752
+ * A live color picker, a font-size stepper or a spacing slider applies every intermediate
753
+ * value so the document updates under the pointer, and each of those writes is a
754
+ * transaction in its own right — it must render, and a collaborator must receive it. What
755
+ * they are NOT is separate undo steps. The caller mints one token when the gesture starts
756
+ * (`Symbol('color-drag')`) and passes it with every write until the gesture ends.
975
757
  *
976
- * Several of these are FILE-hostile shapes rather than caller mistakes:
977
- * `external-relationship` and `bad-relationship-target` are how a crafted document tries to
978
- * point a story at something outside the package, and both are refused rather than followed.
758
+ * A symbol rather than a string, so two unrelated callers cannot collide by naming their
759
+ * gesture the same way, and so a token cannot outlive its gesture by accident through
760
+ * serialization.
761
+ */
762
+ type HistoryGroup = symbol;
763
+ type HistoryCaptureKind = 'started' | 'extended' | 'split';
764
+ type HistoryCaptureReason = 'package-unit' | 'composition';
765
+ declare function observeHistoryGroup<T>(group: HistoryGroup, report: (kind: HistoryCaptureKind, reason?: HistoryCaptureReason) => void, run: () => T): T;
766
+ declare function reportHistoryGroup(group: HistoryGroup | undefined, kind: HistoryCaptureKind, reason?: HistoryCaptureReason): void;
767
+
768
+ /** UTF-16 placeholder for one atomic note unit in `paragraphTextOf` / segments. */
769
+ declare const NOTE_ATOM_CHAR = "\uFFFC";
770
+ /** Signed 32-bit Word-compatible note id range (positive allocation only). */
771
+ declare const NOTE_ID_MIN = 1;
772
+ /** Largest note id Word accepts — signed 32-bit. */
773
+ declare const NOTE_ID_MAX = 2147483647;
774
+ /** Reserved separator / continuation ids — never allocated for normal notes. */
775
+ declare const NOTE_SEPARATOR_ID = -1;
776
+ /** Reserved id of the continuation-separator entry. Never allocated to a real note. */
777
+ declare const NOTE_CONTINUATION_SEPARATOR_ID = 0;
778
+ /** Which notes part a note lives in. Decides both placement and numbering rules. */
779
+ type NoteKind = 'footnote' | 'endnote';
780
+ /** Authored `ST_FtnEdn` including explicit `normal`. Absent means Word default (normal). */
781
+ /**
782
+ * What a note entry IS.
783
+ *
784
+ * Only `normal` is a note a reader sees. The others are the furniture Word stores in the same
785
+ * part — the rules and notices drawn around the note area — reached by reserved ids.
786
+ */
787
+ type NoteType = 'normal' | 'separator' | 'continuationSeparator' | 'continuationNotice';
788
+ /** Parse a signed decimal note id; reject non-integers and out-of-int32 values. */
789
+ declare function parseNoteId(raw: string | undefined): number | null;
790
+ /**
791
+ * Canonical `EditorScope { kind: 'note'; id }` encoding.
792
+ *
793
+ * Footnote id `1` and endnote id `1` coexist, so the kind is part of the string:
794
+ * `footnote:2` / `endnote:-1`. Do not invent a parallel scope union arm.
795
+ */
796
+ declare function formatNoteScopeId(noteKind: NoteKind, noteId: number): string;
797
+ /**
798
+ * Parse {@link formatNoteScopeId}. Returns `null` for malformed / out-of-range ids.
799
+ */
800
+ declare function parseNoteScopeId(id: string): {
801
+ readonly noteKind: NoteKind;
802
+ readonly noteId: number;
803
+ } | null;
804
+ /** Whether a node is a `w:footnotes` part root. */
805
+ declare function isFootnotesNode(node: OoxmlNode): node is OoxmlFootnotesNode;
806
+ /** Whether a node is a `w:endnotes` part root. */
807
+ declare function isEndnotesNode(node: OoxmlNode): node is OoxmlEndnotesNode;
808
+ /** Whether a node is a `w:footnote` or `w:endnote` body. */
809
+ declare function isNoteNode(node: OoxmlNode): node is OoxmlNoteNode;
810
+ /** Whether a node is a body-side `w:footnoteReference` / `w:endnoteReference`. */
811
+ declare function isNoteReferenceNode(node: OoxmlNode): node is OoxmlNoteReferenceNode;
812
+ /** Whether a node is the `w:footnoteRef` / `w:endnoteRef` mark inside a note's own body. */
813
+ declare function isNoteRefNode(node: OoxmlNode): node is OoxmlNoteRefNode;
814
+ /** Whether a node is the `w:separator` rule drawn above the note area. */
815
+ declare function isSeparatorNode(node: OoxmlNode): node is OoxmlSeparatorNode;
816
+ /** Whether a node is the `w:continuationSeparator` rule used on continuation pages. */
817
+ declare function isContinuationSeparatorNode(node: OoxmlNode): node is OoxmlContinuationSeparatorNode;
818
+ /** Typed or generic `w:footnote` / `w:endnote`. */
819
+ declare function noteKindOf(node: OoxmlNode): NoteKind | null;
820
+ /** Authored `@w:id` on a note or noteReference when parseable. */
821
+ declare function noteIdOf(node: OoxmlNode): number | null;
822
+ /** Authored `@w:type` (`ST_FtnEdn`) when present and schema-legal; else `undefined`. */
823
+ declare function noteTypeOf(node: OoxmlNode): NoteType | undefined;
824
+ /** Whether a note is a normal (body) note — absent type and explicit `normal` both count. */
825
+ declare function isNormalNote(node: OoxmlNode): boolean;
826
+ /** Typed or generic footnote/endnote reference kind. */
827
+ declare function noteReferenceKindOf(node: OoxmlNode): NoteKind | null;
828
+ /** Typed or generic `w:footnoteRef` / `w:endnoteRef`. */
829
+ declare function noteRefKindOf(node: OoxmlNode): NoteKind | null;
830
+ /**
831
+ * Read `@w:customMarkFollows` on a note reference.
832
+ * Returns `undefined` when absent; otherwise the OOXML on/off interpretation.
833
+ */
834
+ declare function customMarkFollows(node: OoxmlNode): boolean | undefined;
835
+ /** Model text contributed by one atomic note unit. */
836
+ declare function noteAtomText(): typeof NOTE_ATOM_CHAR;
837
+ /**
838
+ * True when a run-inner node is a typed note atom that contributes one UTF-16 unit.
839
+ * Demoted/malformed known locals stay generic and contribute nothing.
840
+ */
841
+ declare function isNoteAtomNode(node: OoxmlNode): boolean;
842
+ /**
843
+ * One atomic note span inside a paragraph for caret / delete / selection.
844
+ *
845
+ * Each typed note atom is a single-node span (`removeNodeIds` = that node).
846
+ */
847
+ interface AtomicNoteSpan {
848
+ readonly kind: 'noteReference' | 'noteRef' | 'separator' | 'continuationSeparator';
849
+ readonly node: OoxmlNode;
850
+ readonly runId: string;
851
+ readonly removeNodeIds: readonly string[];
852
+ }
853
+ /**
854
+ * Collect typed note atoms in document order (one segment each).
855
+ *
856
+ * Walks the same paragraph-inline surface as `segmentsOf` / `walkParagraphInline`:
857
+ * hyperlinks and content controls flatten; only direct run children contribute atoms.
858
+ * A demoted run-inner SDT husk stays opaque — no phantom addressable hit.
859
+ */
860
+ declare function atomicNoteSpansOf(paragraph: OoxmlParagraphNode): readonly AtomicNoteSpan[];
861
+ /** Cap on notes scanned when allocating / indexing a notes part. */
862
+ declare const MAX_NOTES_PER_PART = 10000;
863
+ /**
864
+ * Allocate the next positive signed 32-bit note id for a notes-part root.
865
+ *
866
+ * Solo: `max(existing)+1`, the dense sequence Word writes. `null` on exhaustion, so the
867
+ * caller refuses rather than wrapping onto an id a note already holds.
868
+ *
869
+ * With a collaboration actor bound — explicit argument, or the open store transaction — the
870
+ * id is the next unused value in that actor's stripe. Two peers each adding their first
871
+ * footnote from one snapshot otherwise both mint `w:id="1"`: the merge keeps both bodies
872
+ * under one id, both `w:footnoteReference` marks resolve to the first, and one author's note
873
+ * becomes unreachable.
874
+ *
875
+ * The reserved ids stay reserved either way. `0` (continuation separator) is seeded as used,
876
+ * so a residue of 0 advances one class instead of taking Word's furniture, and a stripe walk
877
+ * only ever climbs from a non-negative residue, so `-1` (separator) is unreachable.
878
+ */
879
+ declare function allocateNoteId(notesRoot: OoxmlNode, actorId?: string): number | null;
880
+ /** Collect note bodies from a footnotes/endnotes root, bounded. */
881
+ declare function notesOf(root: OoxmlNode): readonly OoxmlNoteNode[];
882
+ /** Find a typed note by id inside a notes-part root. */
883
+ declare function findNoteById(root: OoxmlNode, noteId: number): OoxmlNoteNode | undefined;
884
+
885
+ /** Cap on nodes visited while scanning for note references across stories. */
886
+ declare const MAX_NOTE_REFERENCE_SCAN = 20000;
887
+ /**
888
+ * Cap on XML parts walked in one package-wide note-reference scan (N/N+1 gate).
889
+ * Soft targets are not allowed: exceeding this marks the shared budget truncated.
890
+ */
891
+ declare const MAX_NOTE_REFERENCE_PARTS = 256;
892
+ /**
893
+ * A load-time note problem worth reporting.
894
+ *
895
+ * `dangling-note-reference` is a citation pointing at no note; `note-reference-scan-truncated`
896
+ * says the scan hit its budget, so absence of further diagnostics is not proof of correctness.
897
+ */
898
+ type NoteDiagnosticCode = 'dangling-note-reference' | 'note-reference-scan-truncated';
899
+ /**
900
+ * Load-time note diagnostics. Array API preserved; truncation is signaled as a typed
901
+ * entry rather than by throwing or rejecting the package.
902
+ */
903
+ type NoteDiagnostic = {
904
+ readonly code: 'dangling-note-reference';
905
+ readonly noteKind: NoteKind;
906
+ readonly noteId: number;
907
+ /** Paragraph / container node id when known. */
908
+ readonly sourceNodeId?: string;
909
+ } | {
910
+ /** Hard visited/part budget or soft hit cap stopped the scan before full coverage. */
911
+ readonly code: 'note-reference-scan-truncated';
912
+ };
913
+ /** One note reference found in a story, with where it sits. */
914
+ interface NoteReferenceHit {
915
+ readonly noteKind: NoteKind;
916
+ readonly noteId: number;
917
+ readonly nodeId: string;
918
+ readonly paragraphId: string;
919
+ /** Canonical UTF-16 atom offset within {@link paragraphId} (U+FFFC model). */
920
+ readonly atomOffset: number;
921
+ readonly customMarkFollows: boolean;
922
+ /** Canonical part name that owns this reference. */
923
+ readonly partName: string;
924
+ }
925
+ /** Mutable visited-node + part budget shared across parts / package snapshots. */
926
+ interface NoteReferenceScanBudget {
927
+ visited: number;
928
+ readonly maxVisited: number;
929
+ parts: number;
930
+ readonly maxParts: number;
931
+ /** Set when a walk stops before finishing because a cap was hit. */
932
+ truncated: boolean;
933
+ }
934
+ /** A bounded budget for scanning note references, so a crafted document cannot stall a load. */
935
+ declare function createNoteReferenceScanBudget(maxVisited?: number, maxParts?: number): NoteReferenceScanBudget;
936
+ /**
937
+ * Resolve the footnotes or endnotes part via safe Internal document relationships.
938
+ *
939
+ * Unusable matching relationships (External, unsafe target, missing part, wrong root)
940
+ * are skipped — never fetched, never accepted — so a decoy first match cannot hide a
941
+ * later usable Internal notes part (same continue-past-bad pattern as settingsPartOf).
942
+ */
943
+ declare function resolveNotesPart(pkg: OoxmlPackage, noteKind: NoteKind): OoxmlPart | null;
944
+ /** Walk a part without applying a layout projection. */
945
+ declare function collectNoteReferences(part: OoxmlPart, options?: {
946
+ readonly maxHits?: number;
947
+ readonly budget?: NoteReferenceScanBudget;
948
+ }): readonly NoteReferenceHit[];
949
+ /** Collect references across every XML part under one shared part + visited-node budget. */
950
+ declare function collectPackageNoteReferences(pkg: OoxmlPackage, options?: {
951
+ readonly budget?: NoteReferenceScanBudget;
952
+ /** Soft hit cap for diagnostics. Omit / Infinity for mutation scans. */
953
+ readonly maxHits?: number;
954
+ }): readonly NoteReferenceHit[];
955
+ /**
956
+ * Load diagnostics for dangling note references. Fail-open: never throws or mutates;
957
+ * returns diagnostics for callers to surface. Does not invent missing note bodies.
958
+ *
959
+ * When the hard visited/part budget truncates or the soft hit cap binds, appends a single
960
+ * `note-reference-scan-truncated` entry so incomplete coverage is visible without breaking
961
+ * consumers that filter on `dangling-note-reference`.
962
+ */
963
+ declare function diagnoseNoteReferences(pkg: OoxmlPackage): readonly NoteDiagnostic[];
964
+ /** Whether a notes-part root contains a note with the given id (any type). */
965
+ declare function notesPartHasId(part: OoxmlPart, noteId: number): boolean;
966
+ /** List normal (body) note ids in document order, bounded. */
967
+ declare function normalNoteIds(part: OoxmlPart): readonly number[];
968
+
969
+ /** How far a note lifecycle op reaches — which parts a caller must expect to have changed. */
970
+ type NoteLifecycleImpact = 'flow-structural' | 'global';
971
+ /**
972
+ * A note lifecycle mutation: insert, delete, convert, or set properties.
973
+ *
974
+ * Package-level rather than story-level, because every one of these touches the main document,
975
+ * the notes part, the document relationships AND `[Content_Types].xml` together.
976
+ */
977
+ type NoteLifecycleOp = {
978
+ readonly op: 'insertNote';
979
+ readonly noteKind: NoteKind;
980
+ readonly paragraphId: string;
981
+ readonly offset: number;
982
+ /**
983
+ * Write the citing reference as a TRACKED insertion: the reference run goes inside a
984
+ * `w:ins` carrying this attribution. The note body stays plain — rejecting the
985
+ * reference removes it, and the orphaned body is swept by the same cascade an
986
+ * untracked reference deletion triggers.
987
+ */
988
+ readonly revision?: RevisionAttributionInput;
989
+ } | {
990
+ readonly op: 'deleteNote';
991
+ readonly noteKind: NoteKind;
992
+ readonly noteId: number;
993
+ } | {
994
+ readonly op: 'convertNote';
995
+ readonly fromKind: NoteKind;
996
+ readonly noteId: number;
997
+ } | {
998
+ /**
999
+ * Convert every normal note of `fromKind` in document order. One atomic package
1000
+ * transaction / undo unit with the same validation as repeated `convertNote`.
1001
+ */
1002
+ readonly op: 'convertAllNotes';
1003
+ readonly fromKind: NoteKind;
1004
+ } | {
1005
+ readonly op: 'setNoteProperties';
1006
+ readonly scope: 'document' | 'section';
1007
+ readonly sectionIndex?: number;
1008
+ readonly footnote?: {
1009
+ readonly numFmt?: string;
1010
+ readonly numRestart?: string;
1011
+ readonly position?: string;
1012
+ readonly numStart?: number;
1013
+ };
1014
+ readonly endnote?: {
1015
+ readonly numFmt?: string;
1016
+ readonly numRestart?: string;
1017
+ readonly position?: string;
1018
+ readonly numStart?: number;
1019
+ };
1020
+ };
1021
+ /** Why a note lifecycle op was refused. */
1022
+ type NoteLifecycleRejection = 'invalidArgs' | 'invalid-property-value' | 'tree-invariant';
1023
+ /**
1024
+ * A new package, or a typed rejection.
1025
+ *
1026
+ * Application is PURE and all-or-nothing: there are no partial writes, so a refused op leaves the
1027
+ * caller's package untouched rather than half-migrated across four parts.
1028
+ */
1029
+ type NoteLifecycleResult = {
1030
+ readonly ok: true;
1031
+ readonly package: OoxmlPackage;
1032
+ readonly impact: NoteLifecycleImpact;
1033
+ readonly noteId?: number;
1034
+ readonly noteKind?: NoteKind;
1035
+ readonly createdPartName?: string;
1036
+ } | {
1037
+ readonly ok: false;
1038
+ readonly reason: NoteLifecycleRejection;
1039
+ readonly detail?: string;
1040
+ };
1041
+ /** Whether an op is a note lifecycle op rather than a story-level one. */
1042
+ declare function isNoteLifecycleOp(op: {
1043
+ readonly op: string;
1044
+ }): op is NoteLifecycleOp;
1045
+ /** Attribution and limits applied to one note lifecycle op. */
1046
+ interface NoteLifecycleOptions {
1047
+ /**
1048
+ * Shared part + visited-node budget for reference scans. When omitted a fresh default
1049
+ * budget is used. Truncation rejects the op with the original package unchanged.
1050
+ */
1051
+ readonly scanBudget?: NoteReferenceScanBudget;
1052
+ }
1053
+ /**
1054
+ * Apply one note lifecycle op atomically. Rejected ops leave the input package untouched.
1055
+ */
1056
+ declare function applyNoteLifecycleOp(pkg: OoxmlPackage, op: NoteLifecycleOp, options?: NoteLifecycleOptions): NoteLifecycleResult;
1057
+ /**
1058
+ * How deleting text cascades into the notes it referenced.
1059
+ *
1060
+ * A note's body and the citation reaching it are one thing to a reader, so removing the reference
1061
+ * must remove the body too or the notes part keeps an entry nothing points at.
1062
+ */
1063
+ interface CascadeDeletedNoteReferencesOptions {
1064
+ /**
1065
+ * Independent full budgets per snapshot. When omitted each snapshot gets its own
1066
+ * default budget — never share one counter across before/after walks.
1067
+ */
1068
+ readonly beforeBudget?: NoteReferenceScanBudget;
1069
+ readonly afterBudget?: NoteReferenceScanBudget;
1070
+ }
1071
+ /**
1072
+ * Remove note bodies for references that disappeared between two package snapshots.
1073
+ * Used when `deleteText` or `deleteBlock` removes a `noteReference` atom so body+ref stay
1074
+ * one undo unit.
1075
+ *
1076
+ * Each snapshot gets an independent full visited/part budget. If either scan truncates
1077
+ * the cascade fails closed so a hostile package cannot skip body deletion silently —
1078
+ * without accidentally halving capacity by charging both walks to one counter.
1079
+ */
1080
+ declare function cascadeDeletedNoteReferences(before: OoxmlPackage, after: OoxmlPackage, options?: CascadeDeletedNoteReferencesOptions): OoxmlPackage | null;
1081
+
1082
+ /** Decoded fragment payloads above this cap are refused before any package work. */
1083
+ declare const MAX_FRAGMENT_DECODED_BYTES: number;
1084
+ interface FragmentPasteInput {
1085
+ readonly paragraphId: string;
1086
+ readonly offset: number;
1087
+ /** The fragment package zip (from the clipboard codec or the HTML projection). */
1088
+ readonly fragmentBytes: Uint8Array;
1089
+ readonly lastMarkCovered: boolean;
1090
+ /** Ops that clear the current selection first (the surface's deleteSelectionPlan). */
1091
+ readonly priorOps?: readonly TreeDocOp[];
1092
+ /**
1093
+ * Collaboration actor for the ids this paste mints.
1094
+ *
1095
+ * A paste freshens three package-wide namespaces — bookmark `@w:id`, revision `@w:id` and
1096
+ * `wp:docPr/@id` — and the merge reads the actor off the open transaction. Without one, two
1097
+ * replicas pasting the same fragment from one snapshot mint the same numbers, and only the
1098
+ * merged document shows it. Omitted (solo) keeps Word's dense sequences.
1099
+ */
1100
+ readonly actorId?: string;
1101
+ }
1102
+ type FragmentPasteResult = (Extract<PackageTransactResult, {
1103
+ ok: true;
1104
+ }> & {
1105
+ readonly blockCount: number;
1106
+ }) | Extract<PackageTransactResult, {
1107
+ ok: false;
1108
+ }>;
1109
+ /**
1110
+ * Read the fragment through the bounded file-open trust boundary, merge its resources,
1111
+ * and land its blocks — all inside one transaction. Any refusal leaves the document
1112
+ * exactly as it was; the caller (the paste router) degrades to the next flavour.
1113
+ */
1114
+ declare function applyFragmentPaste(store: TreePackageStore, scope: StoryScope, input: FragmentPasteInput): FragmentPasteResult;
1115
+
1116
+ /** Attribution stamped on one remote canonical publication. */
1117
+ interface RemotePackageAttribution {
1118
+ readonly origin: string;
1119
+ readonly actorId?: string;
1120
+ readonly operationId?: string;
1121
+ }
1122
+
1123
+ type NoteCascadeFn = (before: OoxmlPackage, after: OoxmlPackage) => OoxmlPackage | null;
1124
+ /**
1125
+ * Editable story target.
1126
+ *
1127
+ * Body and headerFooter mirror `EditorScope`. Notes use one lazy store per notes part
1128
+ * (`notesPart`) — not one store per note — resolved through safe document relationships.
1129
+ */
1130
+ type StoryScope = {
1131
+ readonly kind: 'body';
1132
+ } | {
1133
+ readonly kind: 'headerFooter';
1134
+ readonly rId: string;
1135
+ } | {
1136
+ readonly kind: 'notesPart';
1137
+ readonly noteKind: NoteKind;
1138
+ };
1139
+ /**
1140
+ * Why a story scope could not be resolved to a part.
1141
+ *
1142
+ * Several of these are FILE-hostile shapes rather than caller mistakes:
1143
+ * `external-relationship` and `bad-relationship-target` are how a crafted document tries to
1144
+ * point a story at something outside the package, and both are refused rather than followed.
979
1145
  */
980
1146
  type StoryTargetRejection = 'unknown-scope' | 'dangling-relationship' | 'wrong-relationship-type' | 'external-relationship' | 'bad-relationship-target' | 'missing-part' | 'not-a-story-part' | 'too-many-story-stores';
981
1147
  /** A story scope resolved to a part, or the typed reason it could not be. */
@@ -1002,6 +1168,8 @@ declare const DEFAULT_MAX_EDITABLE_STORY_PARTS = 64;
1002
1168
  /** How a package store is constructed: limits, history depth, and review contributions. */
1003
1169
  interface TreePackageStoreOptions {
1004
1170
  readonly historyLimit?: number;
1171
+ /** Opt-in relaxation of `w:lock` for run-property writes, for the body and every story store. */
1172
+ readonly contentLockPolicy?: ContentLockPolicy;
1005
1173
  /** Bound on opened story stores; defaults to {@link DEFAULT_MAX_EDITABLE_STORY_PARTS}. */
1006
1174
  readonly maxEditableStoryParts?: number;
1007
1175
  /**
@@ -1025,8 +1193,10 @@ declare class TreePackageStore {
1025
1193
  private readonly rIdToPartName;
1026
1194
  private readonly undoOrder;
1027
1195
  private readonly redoOrder;
1196
+ private readonly historyGroups;
1028
1197
  private readonly subscribers;
1029
1198
  private readonly historyLimit;
1199
+ private readonly contentLockPolicy;
1030
1200
  private readonly maxEditableStoryParts;
1031
1201
  private readonly cascadeNoteReferences;
1032
1202
  private lastChange;
@@ -1051,6 +1221,14 @@ declare class TreePackageStore {
1051
1221
  * a matching tuple proves the snapshot cannot differ; nothing invalidates explicitly.
1052
1222
  */
1053
1223
  private currentPackageMemo;
1224
+ /**
1225
+ * What the body and every story store open with. `settingsPart` is the SAME live getter for
1226
+ * all of them: a story store is built from a PART, whose synthetic package holds no
1227
+ * `settings.xml`, and a store keeps its own working copy, so a replaced `settings.xml` left one
1228
+ * story reading stale protection. Protection that holds in a header but not the body is worse
1229
+ * than none: it looks enforced. The lock policy reaches every story for the same reason.
1230
+ */
1231
+ private storyStoreOptions;
1054
1232
  constructor(pkg: OoxmlPackage, main: OoxmlPart, options?: TreePackageStoreOptions);
1055
1233
  get packageRevision(): number;
1056
1234
  get canUndo(): boolean;
@@ -1100,6 +1278,8 @@ declare class TreePackageStore {
1100
1278
  */
1101
1279
  applyLifecycleOp(op: HeaderFooterLifecycleOp | NoteLifecycleOp | TreeDocOp, options?: {
1102
1280
  readonly actorId?: string;
1281
+ /** @internal */
1282
+ readonly historyGroup?: HistoryGroup;
1103
1283
  }): PackageTransactResult;
1104
1284
  private commitLifecycleOp;
1105
1285
  undo(): TreeModelChange | null;
@@ -1170,6 +1350,8 @@ declare class TreePackageStore {
1170
1350
  * started from to this instead, the same promotion the note cascade does.
1171
1351
  */
1172
1352
  adoptPackageUnit(before: OoxmlPackage): void;
1353
+ /** {@link adoptPackageUnit} as a frame of a history group. @internal */
1354
+ adoptGroupedPackageUnit(before: OoxmlPackage, historyGroup: HistoryGroup | undefined): void;
1173
1355
  /** Install a full package snapshot (public seam for post-fetch cleanup). */
1174
1356
  installPackageSnapshot(snapshot: OoxmlPackage): void;
1175
1357
  /**
@@ -1190,6 +1372,8 @@ declare class TreePackageStore {
1190
1372
  private openHeaderFooterStore;
1191
1373
  private storyRefForPart;
1192
1374
  private syncPackageFromStore;
1375
+ /** Close every open history group: the story stores' and the coordinator's. */
1376
+ private closeHistoryGroups;
1193
1377
  private pushUndoPointer;
1194
1378
  /** Keep history-reachable stories and prune unreachable shell hyperlink owners. */
1195
1379
  private evictUnreachableStories;
@@ -1197,5 +1381,5 @@ declare class TreePackageStore {
1197
1381
  private publishSynthetic;
1198
1382
  }
1199
1383
 
1200
- export { NOTE_ID_MAX as B, DEFAULT_MAX_EDITABLE_STORY_PARTS as D, NOTE_ID_MIN as E, NOTE_SEPARATOR_ID as G, MAX_FRAGMENT_DECODED_BYTES as M, TreePackageStore as T, allocateDrawingPropertyId as a4, allocateNoteId as a5, applyFragmentPaste as a6, applyHeaderFooterLifecycleOp as a7, applyNoteLifecycleOp as a8, atomicNoteSpansOf as a9, noteTypeOf as aA, notesOf as aB, notesPartHasId as aC, observeHistoryGroup as aD, parseNoteId as aE, parseNoteScopeId as aF, resolveHeaderFooterParts as aG, resolveHeaderFooterPartsBySection as aH, resolveHeaderFooterResolutionBySection as aI, resolveNotesPart as aJ, withBinaryPart as aK, withEmbeddedImage as aL, withoutUnreferencedImagePart as aM, cascadeDeletedNoteReferences as aa, collectNoteReferences as ab, collectPackageNoteReferences as ac, collectSectionPropertyNodes as ad, createNoteReferenceScanBudget as ae, customMarkFollows as af, diagnoseNoteReferences as ag, findNoteById as ah, formatNoteScopeId as ai, isContinuationSeparatorNode as aj, isEndnotesNode as ak, isFootnotesNode as al, isHeaderFooterLifecycleOp as am, isNormalNote as an, isNoteAtomNode as ao, isNoteLifecycleOp as ap, isNoteNode as aq, isNoteRefNode as ar, isNoteReferenceNode as as, isSeparatorNode as at, normalNoteIds as au, noteAtomText as av, noteIdOf as aw, noteKindOf as ax, noteRefKindOf as ay, noteReferenceKindOf as az, TreeDocumentStore as f, reportHistoryGroup as r, MAX_NOTES_PER_PART as v, MAX_NOTE_REFERENCE_PARTS as w, MAX_NOTE_REFERENCE_SCAN as x, NOTE_ATOM_CHAR as y, NOTE_CONTINUATION_SEPARATOR_ID as z };
1201
- export type { TreeDocumentStoreOptions as $, ApplyImagePropertiesInput as A, CascadeDeletedNoteReferencesOptions as C, FieldResultsMode as F, HeaderFooterParts as H, InsertImageInput as I, NoteDiagnostic as J, NoteDiagnosticCode as K, NoteLifecycleImpact as L, NoteKind as N, NoteLifecycleOp as O, NoteLifecycleOptions as P, NoteLifecycleRejection as Q, ReplaceImageOptions as R, StoryScope as S, NoteLifecycleResult as U, NoteReferenceHit as V, NoteReferenceScanBudget as W, NoteType as X, PackageTransactResult as Y, RemotePackageAttribution as Z, StoryResolveResult as _, StoryTargetRejection as a, TreePackageStoreOptions as a0, TreeStoryRef as a1, TransactResult as a2, TransactionContext as a3, HeaderFooterSectionResolution as b, HeaderFooterVariant as c, HistoryGroup as d, TreeModelChange as e, SelectionMark as g, TransactOptions as h, ImageIntentResult as i, FragmentPasteInput as j, FragmentPasteResult as k, AtomicNoteSpan as l, DrawingPropertyIdResult as m, HeaderFooterKind as n, HeaderFooterLifecycleImpact as o, HeaderFooterLifecycleOp as p, HeaderFooterLifecycleRejection as q, HeaderFooterLifecycleResult as s, HeaderFooterSlotMeta as t, HistoryCaptureKind as u };
1384
+ export { NOTE_CONTINUATION_SEPARATOR_ID as B, DEFAULT_MAX_EDITABLE_STORY_PARTS as D, NOTE_ID_MAX as G, NOTE_ID_MIN as J, NOTE_SEPARATOR_ID as K, MAX_FRAGMENT_DECODED_BYTES as M, TreePackageStore as T, allocateDrawingPropertyId as a8, allocateNoteId as a9, isNoteNode as aA, isNoteRefNode as aB, isNoteReferenceNode as aC, isSeparatorNode as aD, normalNoteIds as aE, noteAtomText as aF, noteIdOf as aG, noteKindOf as aH, noteRefKindOf as aI, noteReferenceKindOf as aJ, noteTypeOf as aK, notesOf as aL, notesPartHasId as aM, observeHistoryGroup as aN, parentNodeOf as aO, parseNoteId as aP, parseNoteScopeId as aQ, removeNode as aR, replaceChildren as aS, replaceNode as aT, resolveHeaderFooterParts as aU, resolveHeaderFooterPartsBySection as aV, resolveHeaderFooterResolutionBySection as aW, resolveNotesPart as aX, withBinaryPart as aY, withEmbeddedImage as aZ, withoutUnreferencedImagePart as a_, applyEdits as aa, applyFragmentPaste as ab, applyHeaderFooterLifecycleOp as ac, applyNoteLifecycleOp as ad, atomicNoteSpansOf as ae, cascadeDeletedNoteReferences as af, collectNodeIds as ag, collectNoteReferences as ah, collectPackageNoteReferences as ai, collectSectionPropertyNodes as aj, createNodeIdAllocator as ak, createNoteReferenceScanBudget as al, customMarkFollows as am, diagnoseNoteReferences as an, findNode as ao, findNoteById as ap, formatNoteScopeId as aq, hasNode as ar, insertChildren as as, isContinuationSeparatorNode as at, isEndnotesNode as au, isFootnotesNode as av, isHeaderFooterLifecycleOp as aw, isNormalNote as ax, isNoteAtomNode as ay, isNoteLifecycleOp as az, TreeDocumentStore as f, reportHistoryGroup as r, MAX_NOTES_PER_PART as w, MAX_NOTE_REFERENCE_PARTS as x, MAX_NOTE_REFERENCE_SCAN as y, NOTE_ATOM_CHAR as z };
1385
+ export type { PackageTransactResult as $, ApplyImagePropertiesInput as A, ContentLockPolicy as C, EditOptions as E, FieldResultsMode as F, HeaderFooterParts as H, InsertImageInput as I, NoteDiagnostic as L, NoteKind as N, NoteDiagnosticCode as O, NoteLifecycleImpact as P, NoteLifecycleOp as Q, ReplaceImageOptions as R, StoryScope as S, NoteLifecycleOptions as U, NoteLifecycleRejection as V, NoteLifecycleResult as W, NoteReferenceHit as X, NoteReferenceScanBudget as Y, NoteType as Z, OoxmlEditResult as _, HeaderFooterSectionResolution as a, RemotePackageAttribution as a0, StoryResolveResult as a1, TransactionRevisionIds as a2, TreeDocumentStoreOptions as a3, TreePackageStoreOptions as a4, TreeStoryRef as a5, TransactResult as a6, TransactionContext as a7, StoryTargetRejection as b, HeaderFooterVariant as c, HistoryGroup as d, TreeModelChange as e, SelectionMark as g, TransactOptions as h, ImageIntentResult as i, FragmentPasteInput as j, FragmentPasteResult as k, AtomicNoteSpan as l, CascadeDeletedNoteReferencesOptions as m, DrawingPropertyIdResult as n, HeaderFooterKind as o, HeaderFooterLifecycleImpact as p, HeaderFooterLifecycleOp as q, HeaderFooterLifecycleRejection as s, HeaderFooterLifecycleResult as t, HeaderFooterSlotMeta as u, HistoryCaptureKind as v };