@docx-editor.dev/core 2.30.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 (106) hide show
  1. package/dist/automation.cjs +1 -1
  2. package/dist/automation.d.ts +16 -25
  3. package/dist/automation.js +1 -1
  4. package/dist/binding.cjs +1 -1
  5. package/dist/binding.d.ts +15 -16
  6. package/dist/binding.js +1 -1
  7. package/dist/bookmark-names-DDuAra_V.d.ts +37 -0
  8. package/dist/{chrome-print-BM3Js-6X.d.ts → chrome-print-BWvaVEhD.d.ts} +318 -33
  9. package/dist/chunk-243W4WW2.cjs +2 -0
  10. package/dist/{chunk-RJCQGJR7.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-KJRTNY2Q.cjs → chunk-5SO5QDMV.cjs} +1 -1
  14. package/dist/{chunk-WF7JZ755.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-HYASLE2G.cjs → chunk-HC6YNKUI.cjs} +1 -1
  21. package/dist/chunk-HZ66UCIS.js +2 -0
  22. package/dist/{chunk-5RRAZMBU.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-YZGUBCHP.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-T32MGBJS.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-FLBEJC6X.cjs → chunk-XSTGE3NI.cjs} +1 -1
  35. package/dist/chunk-YQWMI6J5.cjs +7 -0
  36. package/dist/{chunk-WPDD2Y32.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-0v0Kgw1T.d.ts → custom-node-writes-TeUvMP13.d.ts} +2 -2
  47. package/dist/{diagnostic-context-CcAO4q1s.d.ts → diagnostic-context-DJ0P1uZE.d.ts} +3 -2
  48. package/dist/{diagnostic-hints-2Bke6H_w.d.ts → diagnostic-hints-C9JlLz1L.d.ts} +1 -1
  49. package/dist/{document-protection-8eyMWIJ0.d.ts → document-protection-C8x3P2J6.d.ts} +1 -1
  50. package/dist/{editor-DaEJd2so.d.ts → editor-B2ShlO23.d.ts} +292 -50
  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 +602 -31
  55. package/dist/editor.js +1 -1
  56. package/dist/export.cjs +2 -2
  57. package/dist/export.d.ts +13 -14
  58. package/dist/export.js +2 -2
  59. package/dist/{font-resolver-CL9BVKZo.d.ts → font-resolver-CWh40DYp.d.ts} +1 -1
  60. package/dist/{headless-document-view-DZQN8Bnx.d.ts → headless-document-view-qOpaCF82.d.ts} +2 -2
  61. package/dist/index.cjs +1 -1
  62. package/dist/index.d.ts +22 -23
  63. package/dist/index.js +1 -1
  64. package/dist/{layout-shaping-cEnVrhWg.d.ts → layout-shaping-CWS3j0Xr.d.ts} +1 -1
  65. package/dist/layout.cjs +1 -1
  66. package/dist/layout.d.ts +87 -22
  67. package/dist/layout.js +1 -1
  68. package/dist/{note-properties-9FH8zAeG.d.ts → note-properties-QsKh24sM.d.ts} +1 -1
  69. package/dist/{omml-equation-DP2WpUbK.d.ts → omml-equation-BsLAa2lX.d.ts} +27 -9
  70. package/dist/output.cjs +1 -1
  71. package/dist/output.d.ts +20 -10
  72. package/dist/output.js +1 -1
  73. package/dist/{protocol-CZ1uUlPO.d.ts → protocol-COZJUKUY.d.ts} +94 -2
  74. package/dist/{review-paragraph-order-CqVelc5A.d.ts → review-paragraph-order-C_QbCKcH.d.ts} +1 -1
  75. package/dist/{semantic-cell-selection-Dr9yxFZt.d.ts → semantic-cell-selection-Cub9FDa8.d.ts} +2 -2
  76. package/dist/{semantic-interaction-Dc3pf3hr.d.ts → semantic-interaction-dApRIYpt.d.ts} +1 -1
  77. package/dist/{semantic-paint-Bz3FBAoH.d.ts → semantic-paint-DLzA8sjP.d.ts} +3 -3
  78. package/dist/{semantic-record-queries-Bx0pl6kL.d.ts → semantic-record-queries-3yp9o11g.d.ts} +1 -1
  79. package/dist/{semantic-records-CjcF26ZL.d.ts → semantic-records-B-akgRch.d.ts} +130 -87
  80. package/dist/store.cjs +1 -1
  81. package/dist/store.d.ts +48 -22
  82. package/dist/store.js +1 -1
  83. package/dist/{tree-package-store-DKhdEVym.d.ts → tree-package-store-B8l8DX3Z.d.ts} +675 -645
  84. package/dist/{tree-session-contract-CwnTEVLF.d.ts → tree-session-contract-DeLRqohz.d.ts} +26 -13
  85. package/dist/{types-barrel-CA0xB8kH.d.ts → types-barrel-CkF2JbU4.d.ts} +1146 -745
  86. package/package.json +2 -2
  87. package/dist/chunk-2PV5L5NJ.js +0 -5
  88. package/dist/chunk-5OTS2EJ7.js +0 -7
  89. package/dist/chunk-5P4223J4.cjs +0 -27
  90. package/dist/chunk-5SPITS5F.js +0 -18
  91. package/dist/chunk-7ATNKARV.js +0 -2
  92. package/dist/chunk-BGCUTKWE.cjs +0 -5
  93. package/dist/chunk-EBCXSH2W.js +0 -3
  94. package/dist/chunk-ERECKTIR.cjs +0 -2
  95. package/dist/chunk-JC47NRJC.cjs +0 -3
  96. package/dist/chunk-NKPUH3QJ.js +0 -1
  97. package/dist/chunk-PF5NJWS3.js +0 -2
  98. package/dist/chunk-PWTRJSES.cjs +0 -18
  99. package/dist/chunk-QFKGI242.cjs +0 -30
  100. package/dist/chunk-RPPV4S6T.cjs +0 -4
  101. package/dist/chunk-UM26GH5B.js +0 -30
  102. package/dist/chunk-VRCZBFLH.js +0 -27
  103. package/dist/chunk-WK5X2MMA.cjs +0 -7
  104. package/dist/chunk-YBMWBXST.cjs +0 -2
  105. package/dist/chunk-ZOS2PM4A.js +0 -4
  106. package/dist/image-resources-Bipz8toD.d.ts +0 -211
@@ -1,152 +1,4 @@
1
- import { S as SupportedImageMime, c as ImageDecodePort } from './image-resources-Bipz8toD.js';
2
- import { O as OoxmlPart, ay as OoxmlInvariantIssue, N as OoxmlNode, ah as OoxmlElement, Q as OoxmlPackage, ax as RevisionAttributionInput, az as DrawingTreeDocOp, aA as OoxmlParagraphNode, aB as OoxmlNoteNode, aC as OoxmlContinuationSeparatorNode, aD as OoxmlEndnotesNode, aE as OoxmlFootnotesNode, aF as OoxmlNoteRefNode, aG as OoxmlNoteReferenceNode, aH as OoxmlSeparatorNode, aI as ImpactClass, ag as TreeDocOp, av as TreeOpRejection } from './types-barrel-CA0xB8kH.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';
14
-
15
- /** An edited part, or the invariant violations that rejected the edit. */
16
- type OoxmlEditResult = {
17
- readonly ok: true;
18
- readonly part: OoxmlPart;
19
- } | {
20
- readonly ok: false;
21
- readonly issues: readonly OoxmlInvariantIssue[];
22
- };
23
- /**
24
- * An opt-in relaxation of `ST_Lock` for hosts that protect WHAT a control says, not how it looks.
25
- *
26
- * ECMA-376 `contentLocked` forbids editing the control's contents and Word reads formatting as
27
- * part of that, which is the default here. A host that puts protected text in a control (a
28
- * template placeholder the user must not retype) but lets authors style it sets
29
- * `allowFormattingInLockedContent`: a run-property write (bold, colour, font, size, highlight …),
30
- * on runs or on a paragraph mark, then passes the CONTENT half of the lock. The exception never
31
- * admits a property that hides text (`w:vanish`, `w:specVanish`, `w:webHidden`), which the
32
- * run-property vocabulary refuses today anyway. Text edits, deletion of the content, paragraph properties,
33
- * removal of a `sdtLocked` wrapper and `w:dataBinding` refusals are unchanged.
34
- *
35
- * @public
36
- */
37
- interface ContentLockPolicy {
38
- /** Let run formatting pass a content lock; text and removal stay refused. Default `false`. */
39
- readonly allowFormattingInLockedContent?: boolean;
40
- }
41
- /**
42
- * How an edit primitive validates its result.
43
- *
44
- * By default every primitive runs the full-part invariant validation before handing its
45
- * result back — the safe reading for an isolated call. A TRANSACTION applying many ops pays
46
- * that full-tree walk once per primitive, which is what made a hundred-paragraph paste
47
- * quadratic; it defers instead, and runs the same validation ONCE on the final tree before
48
- * anything is published. Deferring is only sound for a caller that owns a commit boundary:
49
- * nothing may escape between the unvalidated intermediate and the validated result.
50
- */
51
- interface EditOptions {
52
- readonly deferValidation?: boolean;
53
- /**
54
- * How a content control's `w:lock` is read for ops that only restyle text. Absent means
55
- * the default: `contentLocked` / `sdtContentLocked` refuse every write, formatting included.
56
- */
57
- readonly contentLockPolicy?: ContentLockPolicy;
58
- /**
59
- * A shared minter for `@w:id` on a NEW revision, for the ops of one transaction.
60
- *
61
- * `nextRevisionId` walks the whole part to find the highest id in use, and a formatting
62
- * write emits one op per run — so a Select All + Bold in suggesting mode paid a
63
- * document-wide walk thousands of times over. The transaction hands its ops one minter
64
- * instead, and it is DROPPED the moment an op that can import foreign revision ids runs
65
- * (a paste carries its own), because a stale minter would hand out an id the document had
66
- * just started using — and two revisions sharing an address are one card in the pane.
67
- *
68
- * Absent means "mint your own": every applier falls back to its own walk, which is what a
69
- * caller outside a transaction gets.
70
- */
71
- readonly revisionIds?: () => string;
72
- /**
73
- * The transaction's revision-id bookkeeping for the tracked TEXT ops, where `revisionIds`
74
- * above hands the property ops one shared id for their whole group.
75
- *
76
- * Absent means "mint your own and remember nothing", which is what a caller outside a
77
- * transaction gets: every applier falls back to its own walk of the part.
78
- */
79
- readonly trackedRevisionIds?: TransactionRevisionIds;
80
- /**
81
- * How the op addresses saved field results (`field-result-mode.ts`). Absent keeps the mode
82
- * of the enclosing store call, which is `atomic` outside any editable transaction.
83
- */
84
- readonly fieldResults?: FieldResultsMode;
85
- }
86
- /**
87
- * What one transaction knows about the revision ids its tracked text ops write.
88
- *
89
- * TWO questions, and they are the same bookkeeping. `nextRevisionId` walks the whole part, so
90
- * a replacement — a `deleteText` and an `insertText` in one transaction — walked it twice, and
91
- * on a five-hundred-page document that doubled the cost of every one. And an insertion aimed
92
- * at the front edge of a deletion relocates past it only when that deletion is THIS
93
- * transaction's own: typing over a selection. Backspace, then a keystroke, is two
94
- * transactions, and there the typed text belongs in FRONT of the struck character, as Word
95
- * writes it. Adjacency alone cannot tell the two apart; the ids can.
96
- */
97
- interface TransactionRevisionIds {
98
- /** A fresh `@w:id`, from this transaction's one walk of the part. */
99
- mint(): string;
100
- /**
101
- * Record a wrapper this transaction wrote, by its FULL `CT_TrackChange` identity — id,
102
- * author and date, as `revisionKey` spells it. Not by the id alone: that number comes out
103
- * of the file, nothing makes it unique, and a strike joining `w:id="3"` in one paragraph
104
- * would otherwise claim an unrelated `w:id="3"` elsewhere as this edit's own.
105
- */
106
- wrote(key: string): void;
107
- /** Whether this transaction wrote a wrapper with that identity. */
108
- wroteUnder(key: string): boolean;
109
- }
110
- /** Every node id currently present in the part. */
111
- declare function collectNodeIds(part: OoxmlPart): Set<string>;
112
- /**
113
- * Mint ids for nodes an edit introduces.
114
- *
115
- * Deterministic and collision-checked against the whole part: a structural-path id from the
116
- * original parse (`/word/document.xml#0.1.2`) and a minted one (`/word/document.xml#new:3`)
117
- * can never coincide, and the counter skips anything already taken so repeated edits in one
118
- * session stay unique.
119
- *
120
- * Allocators share reservations across edited roots and restored roots. Preview edits,
121
- * detached nodes, and undo branches must not reuse an identity that collaboration retains.
122
- *
123
- * Checks the part's node index directly rather than copying every id into a fresh set: the
124
- * copy was O(document) per op, and an allocator is created for every op.
125
- */
126
- declare function createNodeIdAllocator(part: OoxmlPart, family?: 'new' | 'paste'): () => string;
127
- /** Whether a node id exists in the part. */
128
- declare function hasNode(part: OoxmlPart, nodeId: string): boolean;
129
- /** Read a node back out of a part by id. */
130
- declare function findNode(part: OoxmlPart, nodeId: string): OoxmlNode | null;
131
- /** The element that holds a node, or null for the root and for unknown ids. */
132
- declare function parentNodeOf(part: OoxmlPart, nodeId: string): OoxmlElement | null;
133
- /** Replace one node's children wholesale. */
134
- declare function replaceChildren(part: OoxmlPart, nodeId: string, children: readonly OoxmlNode[], options?: EditOptions): OoxmlEditResult;
135
- /** Insert children into a node at `index` (clamped to the child list). */
136
- declare function insertChildren(part: OoxmlPart, nodeId: string, index: number, children: readonly OoxmlNode[], options?: EditOptions): OoxmlEditResult;
137
- /** Replace one node with another, keeping its position among its siblings. */
138
- declare function replaceNode(part: OoxmlPart, nodeId: string, replacement: OoxmlNode, options?: EditOptions): OoxmlEditResult;
139
- /** Remove a node and its subtree. */
140
- declare function removeNode(part: OoxmlPart, nodeId: string, options?: EditOptions): OoxmlEditResult;
141
- /**
142
- * Apply several primitives as ONE atomic step.
143
- *
144
- * Each edit runs against the result of the previous one, and the whole sequence is
145
- * validated once at the end. If any step fails, the ORIGINAL part is what the caller keeps
146
- * — there is no partially-edited intermediate to publish. This is the shape a multi-`DocOp`
147
- * store transaction needs.
148
- */
149
- declare function applyEdits(part: OoxmlPart, edits: readonly ((current: OoxmlPart) => OoxmlEditResult)[], options?: EditOptions): OoxmlEditResult;
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';
150
2
 
151
3
  /** A freshly allocated drawing property id, or the reason one could not be minted. */
152
4
  type DrawingPropertyIdResult = {
@@ -210,52 +62,6 @@ interface ExternalImageFetchPort {
210
62
  }>>;
211
63
  }
212
64
 
213
- interface InsertImageInput {
214
- readonly paragraphId: string;
215
- readonly offset: number;
216
- readonly bytes: Uint8Array;
217
- readonly mime: SupportedImageMime;
218
- readonly widthPoints: number;
219
- readonly heightPoints: number;
220
- readonly decodePort: ImageDecodePort;
221
- readonly expectedPackageRevision: number;
222
- readonly commitGuard?: () => boolean;
223
- readonly title?: string;
224
- readonly description?: string;
225
- readonly hyperlink?: string;
226
- /** Present in suggesting mode: the inserted drawing's run goes into a `w:ins`. */
227
- readonly revision?: RevisionAttributionInput;
228
- /**
229
- * Collaboration actor for the ids this insert mints — `wp:docPr/@id`, the image
230
- * relationship, and a hyperlink relationship when one is asked for.
231
- *
232
- * EXPLICIT, and not the ambient `runWithTransactionActor` binding, because this entry is
233
- * async: the mint runs after `await validateEmbeddedImageForCommit` and the ambient actor
234
- * is synchronous-only. A wrap around the call would compile, read as correct, and be gone
235
- * by the time the id is taken. Omitted (solo) keeps Word's dense `highest + 1` sequence.
236
- */
237
- readonly actorId?: string;
238
- }
239
- interface ReplaceImageOptions {
240
- readonly expectedPackageRevision: number;
241
- readonly commitGuard?: () => boolean;
242
- /** Collaboration actor for the replacement's minted ids. See {@link InsertImageInput.actorId}. */
243
- readonly actorId?: string;
244
- }
245
- type ImageIntentResult = (Extract<PackageTransactResult, {
246
- ok: true;
247
- }> & {
248
- readonly drawingNodeId?: string;
249
- readonly mediaPartName?: string;
250
- }) | Extract<PackageTransactResult, {
251
- ok: false;
252
- }>;
253
- interface ApplyImagePropertiesInput {
254
- readonly drawingNodeId: string;
255
- readonly ops: readonly DrawingTreeDocOp[];
256
- readonly hyperlink: string | null;
257
- }
258
-
259
65
  /** `w:headerReference w:type` vocabulary (ECMA-376 §17.10.5): default, first page, even pages. */
260
66
  type HeaderFooterVariant = 'default' | 'first' | 'even';
261
67
  /** Header vs footer region kind for chrome and lifecycle ops. */
@@ -369,6 +175,13 @@ type HeaderFooterLifecycleOp = {
369
175
  */
370
176
  readonly op: 'setDocumentProtection';
371
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;
372
185
  };
373
186
  /** Why a header/footer lifecycle op was refused. */
374
187
  type HeaderFooterLifecycleRejection = 'invalidArgs' | 'tree-invariant';
@@ -394,42 +207,600 @@ declare function isHeaderFooterLifecycleOp(op: {
394
207
  */
395
208
  declare function applyHeaderFooterLifecycleOp(pkg: OoxmlPackage, op: HeaderFooterLifecycleOp): HeaderFooterLifecycleResult;
396
209
 
397
- /** UTF-16 placeholder for one atomic note unit in `paragraphTextOf` / segments. */
398
- declare const NOTE_ATOM_CHAR = "\uFFFC";
399
- /** Signed 32-bit Word-compatible note id range (positive allocation only). */
400
- declare const NOTE_ID_MIN = 1;
401
- /** Largest note id Word accepts — signed 32-bit. */
402
- declare const NOTE_ID_MAX = 2147483647;
403
- /** Reserved separator / continuation ids — never allocated for normal notes. */
404
- declare const NOTE_SEPARATOR_ID = -1;
405
- /** Reserved id of the continuation-separator entry. Never allocated to a real note. */
406
- declare const NOTE_CONTINUATION_SEPARATOR_ID = 0;
407
- /** Which notes part a note lives in. Decides both placement and numbering rules. */
408
- type NoteKind = 'footnote' | 'endnote';
409
- /** Authored `ST_FtnEdn` including explicit `normal`. Absent means Word default (normal). */
410
210
  /**
411
- * What a note entry IS.
211
+ * How saved field results are addressed.
412
212
  *
413
- * Only `normal` is a note a reader sees. The others are the furniture Word stores in the same
414
- * 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.
415
218
  */
416
- type NoteType = 'normal' | 'separator' | 'continuationSeparator' | 'continuationNotice';
417
- /** Parse a signed decimal note id; reject non-integers and out-of-int32 values. */
418
- 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
+ };
419
229
  /**
420
- * 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.
421
231
  *
422
- * Footnote id `1` and endnote id `1` coexist, so the kind is part of the string:
423
- * `footnote:2` / `endnote:-1`. Do not invent a parallel scope union arm.
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
424
242
  */
425
- declare function formatNoteScopeId(noteKind: NoteKind, noteId: number): string;
243
+ interface ContentLockPolicy {
244
+ /** Let run formatting pass a content lock; text and removal stay refused. Default `false`. */
245
+ readonly allowFormattingInLockedContent?: boolean;
246
+ }
426
247
  /**
427
- * Parse {@link formatNoteScopeId}. Returns `null` for malformed / out-of-range ids.
428
- */
429
- declare function parseNoteScopeId(id: string): {
430
- readonly noteKind: NoteKind;
431
- readonly noteId: number;
432
- } | null;
248
+ * How an edit primitive validates its result.
249
+ *
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.
256
+ */
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;
291
+ }
292
+ /**
293
+ * What one transaction knows about the revision ids its tracked text ops write.
294
+ *
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.
302
+ */
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>;
318
+ /**
319
+ * Mint ids for nodes an edit introduces.
320
+ *
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.
325
+ *
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.
328
+ *
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.
331
+ */
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;
347
+ /**
348
+ * Apply several primitives as ONE atomic step.
349
+ *
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.
354
+ */
355
+ declare function applyEdits(part: OoxmlPart, edits: readonly ((current: OoxmlPart) => OoxmlEditResult)[], options?: EditOptions): OoxmlEditResult;
356
+
357
+ interface InsertImageInput {
358
+ readonly paragraphId: 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;
382
+ }
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;
388
+ }
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;
401
+ }
402
+
403
+ /** How one transaction behaves: its story scope, its attribution, and its selection marks. */
404
+ interface TransactOptions {
405
+ readonly origin?: string;
406
+ /** Stable actor attribution for collaboration and audit correlation. */
407
+ readonly actorId?: string;
408
+ /** Stable constituent identity for collaboration duplicate correlation. */
409
+ readonly operationId?: string;
410
+ /**
411
+ * Whether this transaction enters the legacy snapshot undo stack.
412
+ *
413
+ * Collaboration commits set this to false because their actor-local undo authority is the
414
+ * CRDT undo manager. Omitted preserves the ordinary non-collaborative history behavior.
415
+ */
416
+ readonly recordsHistory?: boolean;
417
+ /**
418
+ * A COMMAND is one user intent that may need several ops (a toolbar click applying a
419
+ * property across a multi-run selection). It is still exactly one history entry, which is
420
+ * the same rule a plain transaction follows — the option exists to say so explicitly at
421
+ * the call site rather than leaving it implied.
422
+ */
423
+ readonly scope?: 'transaction' | 'command';
424
+ /**
425
+ * Floor on the published impact. Header/footer story edits use `global` so every page
426
+ * sharing the part invalidates rather than keeping stale furniture.
427
+ */
428
+ readonly minimumImpact?: ImpactClass;
429
+ /** Story identity stamped onto the published ModelChange (package-aware targeting). */
430
+ readonly story?: TreeStoryRef;
431
+ /**
432
+ * The gesture this transaction belongs to: consecutive transactions carrying the SAME
433
+ * token extend one history entry (package from before the first, selection after the
434
+ * latest). Another token, none, undo or redo closes the group. See {@link HistoryGroup}.
435
+ */
436
+ readonly historyGroup?: HistoryGroup;
437
+ /**
438
+ * How the transaction's ops address saved field results. `atomic` (the default) keeps
439
+ * every field one offset unit; `editable` addresses saved results as text. See
440
+ * `field-result-mode.ts`.
441
+ */
442
+ readonly fieldResults?: FieldResultsMode;
443
+ }
444
+ /** How a store is constructed: its limits, its history depth, and its identity source. */
445
+ interface TreeDocumentStoreOptions {
446
+ /** Bound on retained history entries. Oldest entries drop first. */
447
+ readonly historyLimit?: number;
448
+ /**
449
+ * The document's `settings.xml`, for a store built from a PART rather than a package.
450
+ *
451
+ * Forms protection lives one part up from the op, and a store built from a part gets a
452
+ * synthetic one-part package that cannot see it — so `w:documentProtection w:edit="forms"`
453
+ * was enforced in the body and nowhere else, and a header, a footer or a note accepted every
454
+ * write a protected document is supposed to refuse. Read through a getter, not captured: a
455
+ * document can gain or lose protection while its stories stay open.
456
+ */
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;
463
+ }
464
+
465
+ /** A selection the caller wants restored when an entry is undone or redone. */
466
+ /** A selection captured with a transaction, so undo restores where the caret was. */
467
+ interface SelectionMark {
468
+ readonly paragraphId: string;
469
+ readonly start: number;
470
+ readonly end: number;
471
+ }
472
+ /**
473
+ * Which editable story a ModelChange came from.
474
+ *
475
+ * Mirrors `EditorScope` for body and header/footer — `{ kind: 'headerFooter'; rId }` —
476
+ * so package-aware mutation and the public scope contract stay one vocabulary. Body
477
+ * commits omit `rId`; header/footer commits carry the relationship id that addressed
478
+ * the part. Notes parts use `{ kind: 'notesPart'; noteKind }` (one store per part).
479
+ */
480
+ /** Which story a transaction targets: the body, a header/footer part, or a notes part. */
481
+ type TreeStoryRef = {
482
+ readonly kind: 'body';
483
+ readonly partName: string;
484
+ } | {
485
+ readonly kind: 'headerFooter';
486
+ readonly partName: string;
487
+ readonly rId: string;
488
+ } | {
489
+ readonly kind: 'notesPart';
490
+ readonly partName: string;
491
+ readonly noteKind: 'footnote' | 'endnote';
492
+ };
493
+ /**
494
+ * What one committed transaction changed: the revision, the ids touched, and the impact class.
495
+ *
496
+ * The ids are what let layout and paint re-do only the affected blocks instead of the document.
497
+ */
498
+ interface TreeModelChange {
499
+ readonly change: 'model-change';
500
+ readonly fromRevision: number;
501
+ readonly toRevision: number;
502
+ readonly commitId: string;
503
+ readonly origin: string;
504
+ /** Stable collaboration actor attribution, when the caller supplied it. */
505
+ readonly actorId?: string;
506
+ /** Stable collaboration operation identity, when the caller supplied it. */
507
+ readonly operationId?: string;
508
+ readonly dirty: readonly string[];
509
+ readonly created: readonly string[];
510
+ readonly deleted: readonly string[];
511
+ readonly splitJoin: readonly ({
512
+ readonly split: {
513
+ readonly from: string;
514
+ readonly tail: string;
515
+ };
516
+ } | {
517
+ readonly join: {
518
+ readonly kept: string;
519
+ readonly removed: string;
520
+ };
521
+ })[];
522
+ readonly dependencyKeys: readonly string[];
523
+ /** The widest impact among the transaction's ops — what layout must scope to. */
524
+ readonly impact: ImpactClass;
525
+ /**
526
+ * Story that published this change. Absent on body-only store publishes that predate
527
+ * package-aware targeting; `TreePackageStore` always sets it.
528
+ */
529
+ readonly story?: TreeStoryRef;
530
+ /**
531
+ * Committed collapsed caret for this transaction, when one exists.
532
+ * Matches history `selectionAfter` when that mark is collapsed; absent for explicit
533
+ * null, non-collapsed explicit selection, or when no caret was committed.
534
+ */
535
+ readonly caret?: SelectionMark;
536
+ }
537
+ /** Whether a transaction committed, or the typed reason it was refused. */
538
+ type TransactResult = {
539
+ readonly ok: true;
540
+ readonly change: TreeModelChange | null;
541
+ } | {
542
+ readonly ok: false;
543
+ readonly reason: TreeOpRejection;
544
+ readonly detail?: string;
545
+ };
546
+ /** What a transaction body is handed: the working tree, and the means to stage ops against it. */
547
+ interface TransactionContext {
548
+ /** Stage one op against the STORY part. Returns false once the transaction has failed. */
549
+ apply(op: TreeDocOp): boolean;
550
+ /**
551
+ * Stage one op against a named part.
552
+ *
553
+ * A comment body lives in `comments.xml` and is edited by the same ops that edit the story,
554
+ * so this is `apply` with the target named rather than a second vocabulary.
555
+ */
556
+ applyTo(partName: string, op: TreeDocOp): boolean;
557
+ /**
558
+ * Stage a whole-package edit: a new part, a relationship, a content-type override.
559
+ *
560
+ * The edit is a pure function of the working package, so a rejected transaction discards it
561
+ * with everything else. Returning the SAME package is a no-op, not a failure — a primitive
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.
567
+ */
568
+ applyPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): boolean;
569
+ /** The selection to restore when this entry is undone. */
570
+ selectionBefore(selection: SelectionMark | null): void;
571
+ /** The selection to restore when this entry is redone. */
572
+ selectionAfter(selection: SelectionMark | null): void;
573
+ }
574
+ interface HistoryEntry {
575
+ /**
576
+ * The whole package as it was, not just the story part.
577
+ *
578
+ * Affordable for the same reason a part snapshot was: parts are immutable and deep-frozen, so
579
+ * a package snapshot is a Map of references and every part the transaction did not touch is
580
+ * object-identical to the one before it. Undo stays a pointer swap, and it now reverses every
581
+ * part one intent wrote rather than only the story.
582
+ */
583
+ readonly pkg: OoxmlPackage;
584
+ readonly revision: number;
585
+ readonly selectionBefore: SelectionMark | null;
586
+ readonly selectionAfter: SelectionMark | null;
587
+ /**
588
+ * The gesture this entry is still collecting, while it is the newest entry and nothing
589
+ * has closed it. Only ever set on the top of the undo stack: {@link closeHistoryGroup}
590
+ * strips it, and undo and redo never copy it onto the entries they push.
591
+ */
592
+ readonly group?: HistoryGroup;
593
+ }
594
+ /**
595
+ * Opaque document+history checkpoint for package-coordinator rollback.
596
+ * Used when a story mutation may promote to a package undo unit (note-ref cascade).
597
+ */
598
+ /** A restorable point in history — one entry of the intent-scoped undo stack. */
599
+ interface TreeDocumentCheckpoint {
600
+ /**
601
+ * The whole package, not one part. The store owns the package so a transaction spanning
602
+ * several parts is one publication — a checkpoint of only the story part could not roll
603
+ * back the comment or numbering part the same transaction wrote.
604
+ */
605
+ readonly pkg: OoxmlPackage;
606
+ readonly revision: number;
607
+ readonly undoStack: readonly HistoryEntry[];
608
+ readonly redoStack: readonly HistoryEntry[];
609
+ readonly composition: {
610
+ readonly entry: HistoryEntry;
611
+ readonly committed: boolean;
612
+ } | null;
613
+ }
614
+ /**
615
+ * The document store: one transaction is one atomic publication and one history entry.
616
+ *
617
+ * `apply` STAGES ops against a working part and nothing is visible until `transact` returns, so a
618
+ * batch rejected halfway leaves the revision, the tree, the indexes and every subscriber exactly
619
+ * as they were. That all-or-nothing property is what lets a caller compose ops without having to
620
+ * reason about partial application.
621
+ */
622
+ declare class TreeDocumentStore {
623
+ private current;
624
+ /** The part `apply` targets and `part` returns: the story this store is editing. */
625
+ private readonly storyPartName;
626
+ private rev;
627
+ private commitCounter;
628
+ private readonly undoStack;
629
+ private readonly redoStack;
630
+ private pushCount;
631
+ private readonly subscribers;
632
+ private readonly historyLimit;
633
+ /** See {@link TreeDocumentStoreOptions.settingsPart}. */
634
+ private readonly settingsPartOverride;
635
+ /** See {@link TreeDocumentStoreOptions.contentLockPolicy}. */
636
+ private readonly contentLockPolicy;
637
+ /** Package-aware story tag applied to publishes (including undo/redo). */
638
+ private storyRef;
639
+ /** Open composition, if any. While set, transactions extend one entry (task 5.5). */
640
+ private composition;
641
+ /**
642
+ * Open a store over a package, editing the named story part.
643
+ *
644
+ * A bare part is accepted and wrapped in a single-part package, so callers that never open a
645
+ * real package — tests, headless tooling — are unaffected by the widening.
646
+ */
647
+ constructor(source: OoxmlPart | OoxmlPackage, storyPartNameOrOptions?: string | TreeDocumentStoreOptions, maybeOptions?: TreeDocumentStoreOptions);
648
+ /**
649
+ * Stamp story identity onto subsequent publishes for this store (including undo/redo).
650
+ * Used by the package coordinator so history navigation keeps the same scope tag.
651
+ */
652
+ setStoryRef(story: TreeStoryRef | null): void;
653
+ /** The story part being edited. Unchanged for every caller that predates the widening. */
654
+ get part(): OoxmlPart;
655
+ /** Every part, including the ones a multi-part transaction wrote. */
656
+ get package(): OoxmlPackage;
657
+ get revision(): number;
658
+ get canUndo(): boolean;
659
+ get canRedo(): boolean;
660
+ /** Retained entries — the unit `undo()` reverses, so tests can assert grouping. */
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;
668
+ get compositionActive(): boolean;
669
+ /**
670
+ * Replace the package OUTSIDE the transaction and history lanes.
671
+ *
672
+ * For package writes that are not a user intent and publish no revision: grafting
673
+ * `numbering.xml` into a document that never had one, which the caller performs as a
674
+ * precondition of the list op that follows. Those edits were previously kept in a package
675
+ * variable beside the store, which meant two owners of one value and, predictably, two
676
+ * values — a graft written to one and a save read from the other.
677
+ *
678
+ * Deliberately narrow and deliberately awkward to reach for: anything a user did belongs in
679
+ * `transact`, where it gets a revision, an undo entry and the invariant checks.
680
+ */
681
+ graftPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): void;
682
+ /**
683
+ * Replace the current part without recording history, but advance the revision so
684
+ * revision-keyed projections cannot survive a package snapshot install.
685
+ */
686
+ replacePart(part: OoxmlPart): void;
687
+ /**
688
+ * Snapshot part, revision, and undo/redo stacks so the package coordinator can roll
689
+ * back a story transaction that fails after commit (e.g. note-reference cascade) or
690
+ * discard a local history entry when promoting to a package undo unit.
691
+ */
692
+ checkpoint(): TreeDocumentCheckpoint;
693
+ /** Full restore — part, revision, history stacks, and composition. */
694
+ restoreCheckpoint(checkpoint: TreeDocumentCheckpoint): void;
695
+ /**
696
+ * Restore undo/redo stacks only, keeping the current part and revision.
697
+ * Used when a story mutation is promoted to a package history pointer so the local
698
+ * orphan entry does not steal a later undo.
699
+ */
700
+ restoreHistoryStacks(checkpoint: TreeDocumentCheckpoint): void;
701
+ subscribe(listener: (change: TreeModelChange) => void): () => void;
702
+ /**
703
+ * Run one atomic transaction.
704
+ *
705
+ * Ops are staged against a working copy. On the first rejection the whole transaction is
706
+ * abandoned: no revision, no history entry, no notification. On success exactly one
707
+ * revision is published and exactly one history entry is recorded — unless a composition
708
+ * is open, in which case the entry already exists and this transaction joins it.
709
+ */
710
+ transact(build: (ctx: TransactionContext) => void, options?: TransactOptions): TransactResult;
711
+ private commitTransaction;
712
+ /**
713
+ * Open one history entry for an IME composition.
714
+ *
715
+ * Everything committed until `endComposition` collapses into this single entry, which is
716
+ * what makes a composed word one undo step rather than one per intermediate transaction.
717
+ */
718
+ beginComposition(selectionBefore?: SelectionMark | null): void;
719
+ /** Close the composition, recording its entry only if anything actually committed. */
720
+ endComposition(): void;
721
+ /**
722
+ * Cancel an open composition without recording an entry, leaving whatever it committed
723
+ * in place. An IME cancel is not an undo request; the caller decides what to revert.
724
+ */
725
+ cancelComposition(): void;
726
+ /**
727
+ * Close the open history group, if any: the next transaction records its own entry.
728
+ * The store does this itself for another token, none, undo and redo; the package
729
+ * coordinator does it for history moving ABOVE the store (a package unit, another
730
+ * story's pointer), which the store cannot see.
731
+ */
732
+ closeHistoryGroup(): void;
733
+ undo(): TreeModelChange | null;
734
+ redo(): TreeModelChange | null;
735
+ /** The selection to restore for the entry `undo()` would reverse next. */
736
+ selectionForUndo(): SelectionMark | null;
737
+ /** The selection to restore for the entry `redo()` would reapply next. */
738
+ selectionForRedo(): SelectionMark | null;
739
+ /**
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
743
+ */
744
+ discardNewestUndoEntry(): Pick<HistoryEntry, 'selectionBefore' | 'selectionAfter'> | null;
745
+ private pushUndo;
746
+ private publish;
747
+ }
748
+
749
+ /**
750
+ * The identity of one continuous user gesture, for history grouping.
751
+ *
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.
757
+ *
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;
433
804
  /** Whether a node is a `w:footnotes` part root. */
434
805
  declare function isFootnotesNode(node: OoxmlNode): node is OoxmlFootnotesNode;
435
806
  /** Whether a node is a `w:endnotes` part root. */
@@ -616,445 +987,97 @@ type NoteLifecycleOp = {
616
987
  */
617
988
  readonly revision?: RevisionAttributionInput;
618
989
  } | {
619
- readonly op: 'deleteNote';
620
- readonly noteKind: NoteKind;
621
- readonly noteId: number;
622
- } | {
623
- readonly op: 'convertNote';
624
- readonly fromKind: NoteKind;
625
- readonly noteId: number;
626
- } | {
627
- /**
628
- * Convert every normal note of `fromKind` in document order. One atomic package
629
- * transaction / undo unit with the same validation as repeated `convertNote`.
630
- */
631
- readonly op: 'convertAllNotes';
632
- readonly fromKind: NoteKind;
633
- } | {
634
- readonly op: 'setNoteProperties';
635
- readonly scope: 'document' | 'section';
636
- readonly sectionIndex?: number;
637
- readonly footnote?: {
638
- readonly numFmt?: string;
639
- readonly numRestart?: string;
640
- readonly position?: string;
641
- readonly numStart?: number;
642
- };
643
- readonly endnote?: {
644
- readonly numFmt?: string;
645
- readonly numRestart?: string;
646
- readonly position?: string;
647
- readonly numStart?: number;
648
- };
649
- };
650
- /** Why a note lifecycle op was refused. */
651
- type NoteLifecycleRejection = 'invalidArgs' | 'invalid-property-value' | 'tree-invariant';
652
- /**
653
- * A new package, or a typed rejection.
654
- *
655
- * Application is PURE and all-or-nothing: there are no partial writes, so a refused op leaves the
656
- * caller's package untouched rather than half-migrated across four parts.
657
- */
658
- type NoteLifecycleResult = {
659
- readonly ok: true;
660
- readonly package: OoxmlPackage;
661
- readonly impact: NoteLifecycleImpact;
662
- readonly noteId?: number;
663
- readonly noteKind?: NoteKind;
664
- readonly createdPartName?: string;
665
- } | {
666
- readonly ok: false;
667
- readonly reason: NoteLifecycleRejection;
668
- readonly detail?: string;
669
- };
670
- /** Whether an op is a note lifecycle op rather than a story-level one. */
671
- declare function isNoteLifecycleOp(op: {
672
- readonly op: string;
673
- }): op is NoteLifecycleOp;
674
- /** Attribution and limits applied to one note lifecycle op. */
675
- interface NoteLifecycleOptions {
676
- /**
677
- * Shared part + visited-node budget for reference scans. When omitted a fresh default
678
- * budget is used. Truncation rejects the op with the original package unchanged.
679
- */
680
- readonly scanBudget?: NoteReferenceScanBudget;
681
- }
682
- /**
683
- * Apply one note lifecycle op atomically. Rejected ops leave the input package untouched.
684
- */
685
- declare function applyNoteLifecycleOp(pkg: OoxmlPackage, op: NoteLifecycleOp, options?: NoteLifecycleOptions): NoteLifecycleResult;
686
- /**
687
- * How deleting text cascades into the notes it referenced.
688
- *
689
- * A note's body and the citation reaching it are one thing to a reader, so removing the reference
690
- * must remove the body too or the notes part keeps an entry nothing points at.
691
- */
692
- interface CascadeDeletedNoteReferencesOptions {
693
- /**
694
- * Independent full budgets per snapshot. When omitted each snapshot gets its own
695
- * default budget — never share one counter across before/after walks.
696
- */
697
- readonly beforeBudget?: NoteReferenceScanBudget;
698
- readonly afterBudget?: NoteReferenceScanBudget;
699
- }
700
- /**
701
- * Remove note bodies for references that disappeared between two package snapshots.
702
- * Used when `deleteText` or `deleteBlock` removes a `noteReference` atom so body+ref stay
703
- * one undo unit.
704
- *
705
- * Each snapshot gets an independent full visited/part budget. If either scan truncates
706
- * the cascade fails closed so a hostile package cannot skip body deletion silently —
707
- * without accidentally halving capacity by charging both walks to one counter.
708
- */
709
- declare function cascadeDeletedNoteReferences(before: OoxmlPackage, after: OoxmlPackage, options?: CascadeDeletedNoteReferencesOptions): OoxmlPackage | null;
710
-
711
- /**
712
- * The identity of one continuous user gesture, for history grouping.
713
- *
714
- * A live color picker, a font-size stepper or a spacing slider applies every intermediate
715
- * value so the document updates under the pointer, and each of those writes is a
716
- * transaction in its own right — it must render, and a collaborator must receive it. What
717
- * they are NOT is separate undo steps. The caller mints one token when the gesture starts
718
- * (`Symbol('color-drag')`) and passes it with every write until the gesture ends.
719
- *
720
- * A symbol rather than a string, so two unrelated callers cannot collide by naming their
721
- * gesture the same way, and so a token cannot outlive its gesture by accident through
722
- * serialization.
723
- */
724
- type HistoryGroup = symbol;
725
- type HistoryCaptureKind = 'started' | 'extended' | 'split';
726
- type HistoryCaptureReason = 'package-unit' | 'composition';
727
- declare function observeHistoryGroup<T>(group: HistoryGroup, report: (kind: HistoryCaptureKind, reason?: HistoryCaptureReason) => void, run: () => T): T;
728
- declare function reportHistoryGroup(group: HistoryGroup | undefined, kind: HistoryCaptureKind, reason?: HistoryCaptureReason): void;
729
-
730
- /** How one transaction behaves: its story scope, its attribution, and its selection marks. */
731
- interface TransactOptions {
732
- readonly origin?: string;
733
- /** Stable actor attribution for collaboration and audit correlation. */
734
- readonly actorId?: string;
735
- /** Stable constituent identity for collaboration duplicate correlation. */
736
- readonly operationId?: string;
737
- /**
738
- * Whether this transaction enters the legacy snapshot undo stack.
739
- *
740
- * Collaboration commits set this to false because their actor-local undo authority is the
741
- * CRDT undo manager. Omitted preserves the ordinary non-collaborative history behavior.
742
- */
743
- readonly recordsHistory?: boolean;
744
- /**
745
- * A COMMAND is one user intent that may need several ops (a toolbar click applying a
746
- * property across a multi-run selection). It is still exactly one history entry, which is
747
- * the same rule a plain transaction follows — the option exists to say so explicitly at
748
- * the call site rather than leaving it implied.
749
- */
750
- readonly scope?: 'transaction' | 'command';
751
- /**
752
- * Floor on the published impact. Header/footer story edits use `global` so every page
753
- * sharing the part invalidates rather than keeping stale furniture.
754
- */
755
- readonly minimumImpact?: ImpactClass;
756
- /** Story identity stamped onto the published ModelChange (package-aware targeting). */
757
- readonly story?: TreeStoryRef;
758
- /**
759
- * The gesture this transaction belongs to: consecutive transactions carrying the SAME
760
- * token extend one history entry (package from before the first, selection after the
761
- * latest). Another token, none, undo or redo closes the group. See {@link HistoryGroup}.
762
- */
763
- readonly historyGroup?: HistoryGroup;
764
- /**
765
- * How the transaction's ops address saved field results. `atomic` (the default) keeps
766
- * every field one offset unit; `editable` addresses saved results as text. See
767
- * `field-result-mode.ts`.
768
- */
769
- readonly fieldResults?: FieldResultsMode;
770
- }
771
- /** How a store is constructed: its limits, its history depth, and its identity source. */
772
- interface TreeDocumentStoreOptions {
773
- /** Bound on retained history entries. Oldest entries drop first. */
774
- readonly historyLimit?: number;
775
- /**
776
- * The document's `settings.xml`, for a store built from a PART rather than a package.
777
- *
778
- * Forms protection lives one part up from the op, and a store built from a part gets a
779
- * synthetic one-part package that cannot see it — so `w:documentProtection w:edit="forms"`
780
- * was enforced in the body and nowhere else, and a header, a footer or a note accepted every
781
- * write a protected document is supposed to refuse. Read through a getter, not captured: a
782
- * document can gain or lose protection while its stories stay open.
783
- */
784
- readonly settingsPart?: () => OoxmlPart | null | undefined;
785
- /**
786
- * Opt-in relaxation of `w:lock` for run-property writes. See {@link ContentLockPolicy};
787
- * absent, `contentLocked` and `sdtContentLocked` refuse formatting like any other write.
788
- */
789
- readonly contentLockPolicy?: ContentLockPolicy;
790
- }
791
-
792
- /** A selection the caller wants restored when an entry is undone or redone. */
793
- /** A selection captured with a transaction, so undo restores where the caret was. */
794
- interface SelectionMark {
795
- readonly paragraphId: string;
796
- readonly start: number;
797
- readonly end: number;
798
- }
799
- /**
800
- * Which editable story a ModelChange came from.
801
- *
802
- * Mirrors `EditorScope` for body and header/footer — `{ kind: 'headerFooter'; rId }` —
803
- * so package-aware mutation and the public scope contract stay one vocabulary. Body
804
- * commits omit `rId`; header/footer commits carry the relationship id that addressed
805
- * the part. Notes parts use `{ kind: 'notesPart'; noteKind }` (one store per part).
806
- */
807
- /** Which story a transaction targets: the body, a header/footer part, or a notes part. */
808
- type TreeStoryRef = {
809
- readonly kind: 'body';
810
- readonly partName: string;
811
- } | {
812
- readonly kind: 'headerFooter';
813
- readonly partName: string;
814
- readonly rId: string;
815
- } | {
816
- readonly kind: 'notesPart';
817
- readonly partName: string;
818
- readonly noteKind: 'footnote' | 'endnote';
819
- };
820
- /**
821
- * What one committed transaction changed: the revision, the ids touched, and the impact class.
822
- *
823
- * The ids are what let layout and paint re-do only the affected blocks instead of the document.
824
- */
825
- interface TreeModelChange {
826
- readonly change: 'model-change';
827
- readonly fromRevision: number;
828
- readonly toRevision: number;
829
- readonly commitId: string;
830
- readonly origin: string;
831
- /** Stable collaboration actor attribution, when the caller supplied it. */
832
- readonly actorId?: string;
833
- /** Stable collaboration operation identity, when the caller supplied it. */
834
- readonly operationId?: string;
835
- readonly dirty: readonly string[];
836
- readonly created: readonly string[];
837
- readonly deleted: readonly string[];
838
- readonly splitJoin: readonly ({
839
- readonly split: {
840
- readonly from: string;
841
- readonly tail: string;
842
- };
843
- } | {
844
- readonly join: {
845
- readonly kept: string;
846
- readonly removed: string;
847
- };
848
- })[];
849
- readonly dependencyKeys: readonly string[];
850
- /** The widest impact among the transaction's ops — what layout must scope to. */
851
- readonly impact: ImpactClass;
852
- /**
853
- * Story that published this change. Absent on body-only store publishes that predate
854
- * package-aware targeting; `TreePackageStore` always sets it.
855
- */
856
- readonly story?: TreeStoryRef;
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
+ } | {
857
998
  /**
858
- * Committed collapsed caret for this transaction, when one exists.
859
- * Matches history `selectionAfter` when that mark is collapsed; absent for explicit
860
- * null, non-collapsed explicit selection, or when no caret was committed.
999
+ * Convert every normal note of `fromKind` in document order. One atomic package
1000
+ * transaction / undo unit with the same validation as repeated `convertNote`.
861
1001
  */
862
- readonly caret?: SelectionMark;
863
- }
864
- /** Whether a transaction committed, or the typed reason it was refused. */
865
- type TransactResult = {
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 = {
866
1030
  readonly ok: true;
867
- readonly change: TreeModelChange | null;
1031
+ readonly package: OoxmlPackage;
1032
+ readonly impact: NoteLifecycleImpact;
1033
+ readonly noteId?: number;
1034
+ readonly noteKind?: NoteKind;
1035
+ readonly createdPartName?: string;
868
1036
  } | {
869
1037
  readonly ok: false;
870
- readonly reason: TreeOpRejection;
1038
+ readonly reason: NoteLifecycleRejection;
871
1039
  readonly detail?: string;
872
1040
  };
873
- /** What a transaction body is handed: the working tree, and the means to stage ops against it. */
874
- interface TransactionContext {
875
- /** Stage one op against the STORY part. Returns false once the transaction has failed. */
876
- apply(op: TreeDocOp): boolean;
877
- /**
878
- * Stage one op against a named part.
879
- *
880
- * A comment body lives in `comments.xml` and is edited by the same ops that edit the story,
881
- * so this is `apply` with the target named rather than a second vocabulary.
882
- */
883
- applyTo(partName: string, op: TreeDocOp): boolean;
884
- /**
885
- * Stage a whole-package edit: a new part, a relationship, a content-type override.
886
- *
887
- * The edit is a pure function of the working package, so a rejected transaction discards it
888
- * with everything else. Returning the SAME package is a no-op, not a failure — a primitive
889
- * that finds nothing to do says so that way.
890
- */
891
- applyPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): boolean;
892
- /** The selection to restore when this entry is undone. */
893
- selectionBefore(selection: SelectionMark | null): void;
894
- /** The selection to restore when this entry is redone. */
895
- selectionAfter(selection: SelectionMark | null): void;
896
- }
897
- interface HistoryEntry {
898
- /**
899
- * The whole package as it was, not just the story part.
900
- *
901
- * Affordable for the same reason a part snapshot was: parts are immutable and deep-frozen, so
902
- * a package snapshot is a Map of references and every part the transaction did not touch is
903
- * object-identical to the one before it. Undo stays a pointer swap, and it now reverses every
904
- * part one intent wrote rather than only the story.
905
- */
906
- readonly pkg: OoxmlPackage;
907
- readonly revision: number;
908
- readonly selectionBefore: SelectionMark | null;
909
- readonly selectionAfter: SelectionMark | null;
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 {
910
1047
  /**
911
- * The gesture this entry is still collecting, while it is the newest entry and nothing
912
- * has closed it. Only ever set on the top of the undo stack: {@link closeHistoryGroup}
913
- * strips it, and undo and redo never copy it onto the entries they push.
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.
914
1050
  */
915
- readonly group?: HistoryGroup;
1051
+ readonly scanBudget?: NoteReferenceScanBudget;
916
1052
  }
917
1053
  /**
918
- * Opaque document+history checkpoint for package-coordinator rollback.
919
- * Used when a story mutation may promote to a package undo unit (note-ref cascade).
1054
+ * Apply one note lifecycle op atomically. Rejected ops leave the input package untouched.
920
1055
  */
921
- /** A restorable point in history — one entry of the intent-scoped undo stack. */
922
- interface TreeDocumentCheckpoint {
923
- /**
924
- * The whole package, not one part. The store owns the package so a transaction spanning
925
- * several parts is one publication — a checkpoint of only the story part could not roll
926
- * back the comment or numbering part the same transaction wrote.
927
- */
928
- readonly pkg: OoxmlPackage;
929
- readonly revision: number;
930
- readonly undoStack: readonly HistoryEntry[];
931
- readonly redoStack: readonly HistoryEntry[];
932
- readonly composition: {
933
- readonly entry: HistoryEntry;
934
- readonly committed: boolean;
935
- } | null;
936
- }
1056
+ declare function applyNoteLifecycleOp(pkg: OoxmlPackage, op: NoteLifecycleOp, options?: NoteLifecycleOptions): NoteLifecycleResult;
937
1057
  /**
938
- * The document store: one transaction is one atomic publication and one history entry.
1058
+ * How deleting text cascades into the notes it referenced.
939
1059
  *
940
- * `apply` STAGES ops against a working part and nothing is visible until `transact` returns, so a
941
- * batch rejected halfway leaves the revision, the tree, the indexes and every subscriber exactly
942
- * as they were. That all-or-nothing property is what lets a caller compose ops without having to
943
- * reason about partial application.
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.
944
1062
  */
945
- declare class TreeDocumentStore {
946
- private current;
947
- /** The part `apply` targets and `part` returns: the story this store is editing. */
948
- private readonly storyPartName;
949
- private rev;
950
- private commitCounter;
951
- private readonly undoStack;
952
- private readonly redoStack;
953
- private readonly subscribers;
954
- private readonly historyLimit;
955
- /** See {@link TreeDocumentStoreOptions.settingsPart}. */
956
- private readonly settingsPartOverride;
957
- /** See {@link TreeDocumentStoreOptions.contentLockPolicy}. */
958
- private readonly contentLockPolicy;
959
- /** Package-aware story tag applied to publishes (including undo/redo). */
960
- private storyRef;
961
- /** Open composition, if any. While set, transactions extend one entry (task 5.5). */
962
- private composition;
963
- /**
964
- * Open a store over a package, editing the named story part.
965
- *
966
- * A bare part is accepted and wrapped in a single-part package, so callers that never open a
967
- * real package — tests, headless tooling — are unaffected by the widening.
968
- */
969
- constructor(source: OoxmlPart | OoxmlPackage, storyPartNameOrOptions?: string | TreeDocumentStoreOptions, maybeOptions?: TreeDocumentStoreOptions);
970
- /**
971
- * Stamp story identity onto subsequent publishes for this store (including undo/redo).
972
- * Used by the package coordinator so history navigation keeps the same scope tag.
973
- */
974
- setStoryRef(story: TreeStoryRef | null): void;
975
- /** The story part being edited. Unchanged for every caller that predates the widening. */
976
- get part(): OoxmlPart;
977
- /** Every part, including the ones a multi-part transaction wrote. */
978
- get package(): OoxmlPackage;
979
- get revision(): number;
980
- get canUndo(): boolean;
981
- get canRedo(): boolean;
982
- /** Retained entries — the unit `undo()` reverses, so tests can assert grouping. */
983
- get historyDepth(): number;
984
- get compositionActive(): boolean;
985
- /**
986
- * Replace the package OUTSIDE the transaction and history lanes.
987
- *
988
- * For package writes that are not a user intent and publish no revision: grafting
989
- * `numbering.xml` into a document that never had one, which the caller performs as a
990
- * precondition of the list op that follows. Those edits were previously kept in a package
991
- * variable beside the store, which meant two owners of one value and, predictably, two
992
- * values — a graft written to one and a save read from the other.
993
- *
994
- * Deliberately narrow and deliberately awkward to reach for: anything a user did belongs in
995
- * `transact`, where it gets a revision, an undo entry and the invariant checks.
996
- */
997
- graftPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): void;
998
- /**
999
- * Replace the current part without recording history, but advance the revision so
1000
- * revision-keyed projections cannot survive a package snapshot install.
1001
- */
1002
- replacePart(part: OoxmlPart): void;
1003
- /**
1004
- * Snapshot part, revision, and undo/redo stacks so the package coordinator can roll
1005
- * back a story transaction that fails after commit (e.g. note-reference cascade) or
1006
- * discard a local history entry when promoting to a package undo unit.
1007
- */
1008
- checkpoint(): TreeDocumentCheckpoint;
1009
- /** Full restore — part, revision, history stacks, and composition. */
1010
- restoreCheckpoint(checkpoint: TreeDocumentCheckpoint): void;
1011
- /**
1012
- * Restore undo/redo stacks only, keeping the current part and revision.
1013
- * Used when a story mutation is promoted to a package history pointer so the local
1014
- * orphan entry does not steal a later undo.
1015
- */
1016
- restoreHistoryStacks(checkpoint: TreeDocumentCheckpoint): void;
1017
- subscribe(listener: (change: TreeModelChange) => void): () => void;
1018
- /**
1019
- * Run one atomic transaction.
1020
- *
1021
- * Ops are staged against a working copy. On the first rejection the whole transaction is
1022
- * abandoned: no revision, no history entry, no notification. On success exactly one
1023
- * revision is published and exactly one history entry is recorded — unless a composition
1024
- * is open, in which case the entry already exists and this transaction joins it.
1025
- */
1026
- transact(build: (ctx: TransactionContext) => void, options?: TransactOptions): TransactResult;
1027
- private commitTransaction;
1028
- /**
1029
- * Open one history entry for an IME composition.
1030
- *
1031
- * Everything committed until `endComposition` collapses into this single entry, which is
1032
- * what makes a composed word one undo step rather than one per intermediate transaction.
1033
- */
1034
- beginComposition(selectionBefore?: SelectionMark | null): void;
1035
- /** Close the composition, recording its entry only if anything actually committed. */
1036
- endComposition(): void;
1037
- /**
1038
- * Cancel an open composition without recording an entry, leaving whatever it committed
1039
- * in place. An IME cancel is not an undo request; the caller decides what to revert.
1040
- */
1041
- cancelComposition(): void;
1063
+ interface CascadeDeletedNoteReferencesOptions {
1042
1064
  /**
1043
- * Close the open history group, if any: the next transaction records its own entry.
1044
- * The store does this itself for another token, none, undo and redo; the package
1045
- * coordinator does it for history moving ABOVE the store (a package unit, another
1046
- * story's pointer), which the store cannot see.
1065
+ * Independent full budgets per snapshot. When omitted each snapshot gets its own
1066
+ * default budget — never share one counter across before/after walks.
1047
1067
  */
1048
- closeHistoryGroup(): void;
1049
- undo(): TreeModelChange | null;
1050
- redo(): TreeModelChange | null;
1051
- /** The selection to restore for the entry `undo()` would reverse next. */
1052
- selectionForUndo(): SelectionMark | null;
1053
- /** The selection to restore for the entry `redo()` would reapply next. */
1054
- selectionForRedo(): SelectionMark | null;
1055
- private pushUndo;
1056
- private publish;
1068
+ readonly beforeBudget?: NoteReferenceScanBudget;
1069
+ readonly afterBudget?: NoteReferenceScanBudget;
1057
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;
1058
1081
 
1059
1082
  /** Decoded fragment payloads above this cap are refused before any package work. */
1060
1083
  declare const MAX_FRAGMENT_DECODED_BYTES: number;
@@ -1170,6 +1193,7 @@ declare class TreePackageStore {
1170
1193
  private readonly rIdToPartName;
1171
1194
  private readonly undoOrder;
1172
1195
  private readonly redoOrder;
1196
+ private readonly historyGroups;
1173
1197
  private readonly subscribers;
1174
1198
  private readonly historyLimit;
1175
1199
  private readonly contentLockPolicy;
@@ -1254,6 +1278,8 @@ declare class TreePackageStore {
1254
1278
  */
1255
1279
  applyLifecycleOp(op: HeaderFooterLifecycleOp | NoteLifecycleOp | TreeDocOp, options?: {
1256
1280
  readonly actorId?: string;
1281
+ /** @internal */
1282
+ readonly historyGroup?: HistoryGroup;
1257
1283
  }): PackageTransactResult;
1258
1284
  private commitLifecycleOp;
1259
1285
  undo(): TreeModelChange | null;
@@ -1324,6 +1350,8 @@ declare class TreePackageStore {
1324
1350
  * started from to this instead, the same promotion the note cascade does.
1325
1351
  */
1326
1352
  adoptPackageUnit(before: OoxmlPackage): void;
1353
+ /** {@link adoptPackageUnit} as a frame of a history group. @internal */
1354
+ adoptGroupedPackageUnit(before: OoxmlPackage, historyGroup: HistoryGroup | undefined): void;
1327
1355
  /** Install a full package snapshot (public seam for post-fetch cleanup). */
1328
1356
  installPackageSnapshot(snapshot: OoxmlPackage): void;
1329
1357
  /**
@@ -1344,6 +1372,8 @@ declare class TreePackageStore {
1344
1372
  private openHeaderFooterStore;
1345
1373
  private storyRefForPart;
1346
1374
  private syncPackageFromStore;
1375
+ /** Close every open history group: the story stores' and the coordinator's. */
1376
+ private closeHistoryGroups;
1347
1377
  private pushUndoPointer;
1348
1378
  /** Keep history-reachable stories and prune unreachable shell hyperlink owners. */
1349
1379
  private evictUnreachableStories;