@docx-editor.dev/core 2.35.0-canary.20261104032723.sha-43c2012 → 2.35.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 (115) hide show
  1. package/README.md +9 -14
  2. package/dist/automation.cjs +1 -1
  3. package/dist/automation.d.ts +16 -11
  4. package/dist/automation.js +1 -1
  5. package/dist/binding.cjs +1 -1
  6. package/dist/binding.d.ts +12 -12
  7. package/dist/binding.js +1 -1
  8. package/dist/{chrome-print-CK3v5NhC.d.ts → chrome-print-H0uSxdg3.d.ts} +70 -17
  9. package/dist/{chunk-AMOFXSEO.cjs → chunk-2C75IWUN.cjs} +1 -1
  10. package/dist/chunk-2NFQ7OXY.cjs +1 -0
  11. package/dist/chunk-2V3V57SC.js +3 -0
  12. package/dist/chunk-32YOHLEK.cjs +32 -0
  13. package/dist/chunk-4NISR27M.cjs +31 -0
  14. package/dist/chunk-5ZE7ROGP.js +31 -0
  15. package/dist/{chunk-LZ64O473.js → chunk-66ZAVRGB.js} +1 -1
  16. package/dist/{chunk-A5GMOQWD.js → chunk-6767S7UF.js} +1 -1
  17. package/dist/chunk-6BZINLEN.cjs +1 -0
  18. package/dist/{chunk-EBIMIIC3.js → chunk-AVGSD7FB.js} +5 -5
  19. package/dist/{chunk-TZTKEPCC.cjs → chunk-CBJMRC4L.cjs} +1 -1
  20. package/dist/chunk-DCILO4XD.cjs +236 -0
  21. package/dist/{chunk-FBGZOGLU.cjs → chunk-DSIDCA3V.cjs} +4 -4
  22. package/dist/chunk-FLEFHRJG.cjs +5 -0
  23. package/dist/chunk-FMSXJ22B.js +2 -0
  24. package/dist/{chunk-7ACXKFKO.js → chunk-I4OZW2NO.js} +1 -1
  25. package/dist/chunk-JGE2O6IF.cjs +3 -0
  26. package/dist/chunk-K2BBJDM7.js +32 -0
  27. package/dist/chunk-LMASPHYS.js +1 -0
  28. package/dist/chunk-NYZP5NM3.js +4 -0
  29. package/dist/chunk-P3XUE72N.js +236 -0
  30. package/dist/{chunk-JZ4LO3ZZ.js → chunk-Q5YVLUGG.js} +1 -1
  31. package/dist/chunk-QALWAAPL.cjs +4 -0
  32. package/dist/{chunk-ELR6HXOO.js → chunk-QDUMZYTX.js} +1 -1
  33. package/dist/chunk-QLANEOTJ.cjs +7 -0
  34. package/dist/{chunk-4NPNKO5U.cjs → chunk-SJV6WCTP.cjs} +1 -1
  35. package/dist/{chunk-I4WSXFY6.js → chunk-TMCMXIBT.js} +4 -4
  36. package/dist/{chunk-GTICLPKS.cjs → chunk-TY5CE2JR.cjs} +1 -1
  37. package/dist/chunk-UAUB5YV6.js +7 -0
  38. package/dist/{chunk-Q4MUV5KX.cjs → chunk-WWZWT26M.cjs} +1 -1
  39. package/dist/chunk-Y3JSN52G.cjs +2 -0
  40. package/dist/chunk-YD3KSZJM.js +1 -0
  41. package/dist/collaboration/replication.cjs +1 -1
  42. package/dist/collaboration/replication.d.ts +3 -3
  43. package/dist/collaboration/replication.js +1 -1
  44. package/dist/collaboration.d.ts +2 -2
  45. package/dist/contracts/document.d.ts +2 -2
  46. package/dist/contracts/editor.d.ts +8 -8
  47. package/dist/contracts/modules.cjs +1 -1
  48. package/dist/contracts/modules.d.ts +34 -13
  49. package/dist/contracts/modules.js +1 -1
  50. package/dist/contracts/types.d.ts +3 -3
  51. package/dist/{custom-node-writes-BC3z8NwO.d.ts → custom-node-writes-Cj0itq58.d.ts} +2 -2
  52. package/dist/{document-protection-8hXPdn5U.d.ts → document-protection-CDLoYQI3.d.ts} +1 -1
  53. package/dist/{drawing-graphic-kind-_5u2E4ee.d.ts → drawing-graphic-kind-8zt6pugF.d.ts} +2 -2
  54. package/dist/{drawing-preview-package-JyAqL6OV.d.ts → drawing-preview-package-8o9LSfzP.d.ts} +1 -1
  55. package/dist/{editor-BGCdE3tB.d.ts → editor-CujQcn1z.d.ts} +20 -10
  56. package/dist/editor.cjs +1 -1
  57. package/dist/editor.css +3 -3
  58. package/dist/editor.d.ts +52 -29
  59. package/dist/editor.js +1 -1
  60. package/dist/export.cjs +2 -2
  61. package/dist/export.d.ts +6 -6
  62. package/dist/export.js +1 -1
  63. package/dist/index.cjs +1 -1
  64. package/dist/index.d.ts +18 -18
  65. package/dist/index.js +1 -1
  66. package/dist/{layout-shaping-D2qNes9-.d.ts → layout-shaping-ZjLRz_qa.d.ts} +1 -1
  67. package/dist/layout.cjs +1 -1
  68. package/dist/layout.d.ts +25 -21
  69. package/dist/layout.js +1 -1
  70. package/dist/{note-properties-DGPzKUuA.d.ts → note-properties-BRpskBeh.d.ts} +1 -1
  71. package/dist/output.cjs +1 -1
  72. package/dist/output.d.ts +10 -10
  73. package/dist/output.js +1 -1
  74. package/dist/{protocol-0y3iaYj9.d.ts → protocol-GVVQQwCX.d.ts} +13 -2
  75. package/dist/{review-paragraph-order-DeQp2_iu.d.ts → review-paragraph-order-aEHnQTt7.d.ts} +5 -2
  76. package/dist/{revision-projection-CrAv-HP6.d.ts → revision-projection-vU_UiTLx.d.ts} +1 -1
  77. package/dist/{semantic-cell-selection-CeG_qqDV.d.ts → semantic-cell-selection-CaRIISu-.d.ts} +2 -2
  78. package/dist/{semantic-interaction-DcE_ZsiD.d.ts → semantic-interaction-LJtUJJS5.d.ts} +1 -1
  79. package/dist/{semantic-paint-DfeN6UQF.d.ts → semantic-paint-2LBYlQcn.d.ts} +11 -4
  80. package/dist/{semantic-record-queries-qwd9Ict6.d.ts → semantic-record-queries-C_YK2gCz.d.ts} +1 -1
  81. package/dist/{semantic-records-thL1rcmG.d.ts → semantic-records-AU2pIYRp.d.ts} +3 -3
  82. package/dist/store.cjs +1 -1
  83. package/dist/store.d.ts +1664 -1664
  84. package/dist/store.js +1 -1
  85. package/dist/{tree-op-types-CGlwCQXg.d.ts → tree-op-types-DmrePCMo.d.ts} +320 -11
  86. package/dist/{tree-package-store-D0_BOm-r.d.ts → tree-package-fragment-DXZ2B7x0.d.ts} +702 -691
  87. package/dist/{tree-session-contract-DkThhHjy.d.ts → tree-session-contract-D3e-5YNq.d.ts} +19 -4
  88. package/dist/{unsupported-elements-qj0sXi2s.d.ts → unsupported-elements-9Pz4I_F6.d.ts} +1 -1
  89. package/docs/accept-reject-without-scrolling.md +31 -48
  90. package/docs/comment-on-found-text.md +47 -23
  91. package/docs/custom-review-cards.md +50 -34
  92. package/docs/highlight-ranges.md +26 -6
  93. package/docs/index.md +21 -21
  94. package/docs/propose-rewrite.md +87 -138
  95. package/docs/tracked-replacement.md +41 -20
  96. package/package.json +2 -2
  97. package/dist/chunk-4C7JUJDX.cjs +0 -1
  98. package/dist/chunk-4HENIAFG.js +0 -1
  99. package/dist/chunk-6IYKYYWF.js +0 -32
  100. package/dist/chunk-CDAFCPT6.cjs +0 -3
  101. package/dist/chunk-CU6QPXKD.js +0 -2
  102. package/dist/chunk-EMITIPLN.js +0 -4
  103. package/dist/chunk-IGB66DIX.cjs +0 -30
  104. package/dist/chunk-JQPTNBLT.js +0 -236
  105. package/dist/chunk-JVWRILO3.cjs +0 -4
  106. package/dist/chunk-KOYEBRLV.js +0 -1
  107. package/dist/chunk-KZZPOZOF.cjs +0 -2
  108. package/dist/chunk-NPWMZIPA.cjs +0 -32
  109. package/dist/chunk-NS2QCOAS.js +0 -7
  110. package/dist/chunk-O2PGRL5D.js +0 -3
  111. package/dist/chunk-TFJUIP4X.cjs +0 -236
  112. package/dist/chunk-V3N4SOAK.cjs +0 -1
  113. package/dist/chunk-V6FOPP2H.js +0 -30
  114. package/dist/chunk-XFEIUKOS.cjs +0 -7
  115. package/dist/chunk-ZF4N3N3L.cjs +0 -5
@@ -1,4 +1,4 @@
1
- import { a5 as OoxmlPackage, an as SupportedImageMime, O as OoxmlPart, a4 as OoxmlNode, av as OoxmlElement, aH as PackageWatermarkWrite, aI as OoxmlInvariantIssue, ad as ImageDecodePort, aE as RevisionAttributionInput, aJ as DrawingTreeDocOp, aK as ImpactClass, au as TreeDocOp, aF 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 './tree-op-types-CGlwCQXg.js';
1
+ import { r as OoxmlPackage, aG as SupportedImageMime, O as OoxmlPart, am as OoxmlNode, aU as OoxmlElement, a$ as PackageWatermarkWrite, b0 as OoxmlInvariantIssue, b1 as OoxmlParagraphNode, b2 as OoxmlNoteNode, b3 as OoxmlContinuationSeparatorNode, b4 as OoxmlEndnotesNode, b5 as OoxmlFootnotesNode, b6 as OoxmlNoteRefNode, b7 as OoxmlNoteReferenceNode, b8 as OoxmlSeparatorNode, aY as RevisionAttributionInput, aw as ImageDecodePort, b9 as DrawingTreeDocOp, ba as ImpactClass, aO as TreeDocOp, aZ as TreeOpRejection } from './tree-op-types-DmrePCMo.js';
2
2
 
3
3
  /** A freshly allocated drawing property id, or the reason one could not be minted. */
4
4
  type DrawingPropertyIdResult = {
@@ -256,6 +256,14 @@ interface ContentLockPolicy {
256
256
  */
257
257
  interface EditOptions {
258
258
  readonly deferValidation?: boolean;
259
+ /**
260
+ * The text of the glossary block a control's `w:placeholder/w:docPart` names, so a control
261
+ * an edit empties shows the document's own prompt. Absent, or `undefined` for a name, means
262
+ * the type's default prompt.
263
+ *
264
+ * @internal
265
+ */
266
+ readonly placeholderText?: (docPart: string) => string | undefined;
259
267
  /**
260
268
  * How a content control's `w:lock` is read for ops that only restyle text. Absent means
261
269
  * the default: `contentLocked` / `sdtContentLocked` refuse every write, formatting included.
@@ -354,769 +362,739 @@ declare function removeNode(part: OoxmlPart, nodeId: string, options?: EditOptio
354
362
  */
355
363
  declare function applyEdits(part: OoxmlPart, edits: readonly ((current: OoxmlPart) => OoxmlEditResult)[], options?: EditOptions): OoxmlEditResult;
356
364
 
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
- }
365
+ /** UTF-16 placeholder for one atomic note unit in `paragraphTextOf` / segments. */
366
+ declare const NOTE_ATOM_CHAR = "\uFFFC";
367
+ /** Signed 32-bit Word-compatible note id range (positive allocation only). */
368
+ declare const NOTE_ID_MIN = 1;
369
+ /** Largest note id Word accepts — signed 32-bit. */
370
+ declare const NOTE_ID_MAX = 2147483647;
371
+ /** Reserved separator / continuation ids — never allocated for normal notes. */
372
+ declare const NOTE_SEPARATOR_ID = -1;
373
+ /** Reserved id of the continuation-separator entry. Never allocated to a real note. */
374
+ declare const NOTE_CONTINUATION_SEPARATOR_ID = 0;
375
+ /** Which notes part a note lives in. Decides both placement and numbering rules. */
376
+ type NoteKind = 'footnote' | 'endnote';
377
+ /** Authored `ST_FtnEdn` including explicit `normal`. Absent means Word default (normal). */
472
378
  /**
473
- * Which editable story a ModelChange came from.
379
+ * What a note entry IS.
474
380
  *
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).
381
+ * Only `normal` is a note a reader sees. The others are the furniture Word stores in the same
382
+ * part — the rules and notices drawn around the note area — reached by reserved ids.
479
383
  */
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
- };
384
+ type NoteType = 'normal' | 'separator' | 'continuationSeparator' | 'continuationNotice';
385
+ /** Parse a signed decimal note id; reject non-integers and out-of-int32 values. */
386
+ declare function parseNoteId(raw: string | undefined): number | null;
493
387
  /**
494
- * What one committed transaction changed: the revision, the ids touched, and the impact class.
388
+ * Canonical `EditorScope { kind: 'note'; id }` encoding.
495
389
  *
496
- * The ids are what let layout and paint re-do only the affected blocks instead of the document.
390
+ * Footnote id `1` and endnote id `1` coexist, so the kind is part of the string:
391
+ * `footnote:2` / `endnote:-1`. Do not invent a parallel scope union arm.
497
392
  */
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;
393
+ declare function formatNoteScopeId(noteKind: NoteKind, noteId: number): string;
394
+ /**
395
+ * Parse {@link formatNoteScopeId}. Returns `null` for malformed / out-of-range ids.
396
+ */
397
+ declare function parseNoteScopeId(id: string): {
398
+ readonly noteKind: NoteKind;
399
+ readonly noteId: number;
400
+ } | null;
401
+ /** Whether a node is a `w:footnotes` part root. */
402
+ declare function isFootnotesNode(node: OoxmlNode): node is OoxmlFootnotesNode;
403
+ /** Whether a node is a `w:endnotes` part root. */
404
+ declare function isEndnotesNode(node: OoxmlNode): node is OoxmlEndnotesNode;
405
+ /** Whether a node is a `w:footnote` or `w:endnote` body. */
406
+ declare function isNoteNode(node: OoxmlNode): node is OoxmlNoteNode;
407
+ /** Whether a node is a body-side `w:footnoteReference` / `w:endnoteReference`. */
408
+ declare function isNoteReferenceNode(node: OoxmlNode): node is OoxmlNoteReferenceNode;
409
+ /** Whether a node is the `w:footnoteRef` / `w:endnoteRef` mark inside a note's own body. */
410
+ declare function isNoteRefNode(node: OoxmlNode): node is OoxmlNoteRefNode;
411
+ /** Whether a node is the `w:separator` rule drawn above the note area. */
412
+ declare function isSeparatorNode(node: OoxmlNode): node is OoxmlSeparatorNode;
413
+ /** Whether a node is the `w:continuationSeparator` rule used on continuation pages. */
414
+ declare function isContinuationSeparatorNode(node: OoxmlNode): node is OoxmlContinuationSeparatorNode;
415
+ /** Typed or generic `w:footnote` / `w:endnote`. */
416
+ declare function noteKindOf(node: OoxmlNode): NoteKind | null;
417
+ /** Authored `@w:id` on a note or noteReference when parseable. */
418
+ declare function noteIdOf(node: OoxmlNode): number | null;
419
+ /** Authored `@w:type` (`ST_FtnEdn`) when present and schema-legal; else `undefined`. */
420
+ declare function noteTypeOf(node: OoxmlNode): NoteType | undefined;
421
+ /** Whether a note is a normal (body) note — absent type and explicit `normal` both count. */
422
+ declare function isNormalNote(node: OoxmlNode): boolean;
423
+ /** Typed or generic footnote/endnote reference kind. */
424
+ declare function noteReferenceKindOf(node: OoxmlNode): NoteKind | null;
425
+ /** Typed or generic `w:footnoteRef` / `w:endnoteRef`. */
426
+ declare function noteRefKindOf(node: OoxmlNode): NoteKind | null;
427
+ /**
428
+ * Read `@w:customMarkFollows` on a note reference.
429
+ * Returns `undefined` when absent; otherwise the OOXML on/off interpretation.
430
+ */
431
+ declare function customMarkFollows(node: OoxmlNode): boolean | undefined;
432
+ /** Model text contributed by one atomic note unit. */
433
+ declare function noteAtomText(): typeof NOTE_ATOM_CHAR;
434
+ /**
435
+ * True when a run-inner node is a typed note atom that contributes one UTF-16 unit.
436
+ * Demoted/malformed known locals stay generic and contribute nothing.
437
+ */
438
+ declare function isNoteAtomNode(node: OoxmlNode): boolean;
439
+ /**
440
+ * One atomic note span inside a paragraph for caret / delete / selection.
441
+ *
442
+ * Each typed note atom is a single-node span (`removeNodeIds` = that node).
443
+ */
444
+ interface AtomicNoteSpan {
445
+ readonly kind: 'noteReference' | 'noteRef' | 'separator' | 'continuationSeparator';
446
+ readonly node: OoxmlNode;
447
+ readonly runId: string;
448
+ readonly removeNodeIds: readonly string[];
536
449
  }
537
- /** Whether a transaction committed, or the typed reason it was refused. */
538
- type TransactResult = {
539
- readonly ok: true;
540
- readonly change: TreeModelChange | null;
450
+ /**
451
+ * Collect typed note atoms in document order (one segment each).
452
+ *
453
+ * Walks the same paragraph-inline surface as `segmentsOf` / `walkParagraphInline`:
454
+ * hyperlinks and content controls flatten; only direct run children contribute atoms.
455
+ * A demoted run-inner SDT husk stays opaque — no phantom addressable hit.
456
+ */
457
+ declare function atomicNoteSpansOf(paragraph: OoxmlParagraphNode): readonly AtomicNoteSpan[];
458
+ /** Cap on notes scanned when allocating / indexing a notes part. */
459
+ declare const MAX_NOTES_PER_PART = 10000;
460
+ /**
461
+ * Allocate the next positive signed 32-bit note id for a notes-part root.
462
+ *
463
+ * Solo: `max(existing)+1`, the dense sequence Word writes. `null` on exhaustion, so the
464
+ * caller refuses rather than wrapping onto an id a note already holds.
465
+ *
466
+ * With a collaboration actor bound — explicit argument, or the open store transaction — the
467
+ * id is the next unused value in that actor's stripe. Two peers each adding their first
468
+ * footnote from one snapshot otherwise both mint `w:id="1"`: the merge keeps both bodies
469
+ * under one id, both `w:footnoteReference` marks resolve to the first, and one author's note
470
+ * becomes unreachable.
471
+ *
472
+ * The reserved ids stay reserved either way. `0` (continuation separator) is seeded as used,
473
+ * so a residue of 0 advances one class instead of taking Word's furniture, and a stripe walk
474
+ * only ever climbs from a non-negative residue, so `-1` (separator) is unreachable.
475
+ */
476
+ declare function allocateNoteId(notesRoot: OoxmlNode, actorId?: string): number | null;
477
+ /** Collect note bodies from a footnotes/endnotes root, bounded. */
478
+ declare function notesOf(root: OoxmlNode): readonly OoxmlNoteNode[];
479
+ /** Find a typed note by id inside a notes-part root. */
480
+ declare function findNoteById(root: OoxmlNode, noteId: number): OoxmlNoteNode | undefined;
481
+
482
+ /** Cap on nodes visited while scanning for note references across stories. */
483
+ declare const MAX_NOTE_REFERENCE_SCAN = 20000;
484
+ /**
485
+ * Cap on XML parts walked in one package-wide note-reference scan (N/N+1 gate).
486
+ * Soft targets are not allowed: exceeding this marks the shared budget truncated.
487
+ */
488
+ declare const MAX_NOTE_REFERENCE_PARTS = 256;
489
+ /**
490
+ * A load-time note problem worth reporting.
491
+ *
492
+ * `dangling-note-reference` is a citation pointing at no note; `note-reference-scan-truncated`
493
+ * says the scan hit its budget, so absence of further diagnostics is not proof of correctness.
494
+ */
495
+ type NoteDiagnosticCode = 'dangling-note-reference' | 'note-reference-scan-truncated';
496
+ /**
497
+ * Load-time note diagnostics. Array API preserved; truncation is signaled as a typed
498
+ * entry rather than by throwing or rejecting the package.
499
+ */
500
+ type NoteDiagnostic = {
501
+ readonly code: 'dangling-note-reference';
502
+ readonly noteKind: NoteKind;
503
+ readonly noteId: number;
504
+ /** Paragraph / container node id when known. */
505
+ readonly sourceNodeId?: string;
541
506
  } | {
542
- readonly ok: false;
543
- readonly reason: TreeOpRejection;
544
- readonly detail?: string;
507
+ /** Hard visited/part budget or soft hit cap stopped the scan before full coverage. */
508
+ readonly code: 'note-reference-scan-truncated';
545
509
  };
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;
510
+ /** One note reference found in a story, with where it sits. */
511
+ interface NoteReferenceHit {
512
+ readonly noteKind: NoteKind;
513
+ readonly noteId: number;
514
+ readonly nodeId: string;
515
+ readonly paragraphId: string;
516
+ /** Canonical UTF-16 atom offset within {@link paragraphId} (U+FFFC model). */
517
+ readonly atomOffset: number;
518
+ readonly customMarkFollows: boolean;
519
+ /** Canonical part name that owns this reference. */
520
+ readonly partName: string;
521
+ }
522
+ /** Mutable visited-node + part budget shared across parts / package snapshots. */
523
+ interface NoteReferenceScanBudget {
524
+ visited: number;
525
+ readonly maxVisited: number;
526
+ parts: number;
527
+ readonly maxParts: number;
528
+ /** Set when a walk stops before finishing because a cap was hit. */
529
+ truncated: boolean;
530
+ }
531
+ /** A bounded budget for scanning note references, so a crafted document cannot stall a load. */
532
+ declare function createNoteReferenceScanBudget(maxVisited?: number, maxParts?: number): NoteReferenceScanBudget;
533
+ /**
534
+ * Resolve the footnotes or endnotes part via safe Internal document relationships.
535
+ *
536
+ * Unusable matching relationships (External, unsafe target, missing part, wrong root)
537
+ * are skipped — never fetched, never accepted — so a decoy first match cannot hide a
538
+ * later usable Internal notes part (same continue-past-bad pattern as settingsPartOf).
539
+ */
540
+ declare function resolveNotesPart(pkg: OoxmlPackage, noteKind: NoteKind): OoxmlPart | null;
541
+ /** Walk a part without applying a layout projection. */
542
+ declare function collectNoteReferences(part: OoxmlPart, options?: {
543
+ readonly maxHits?: number;
544
+ readonly budget?: NoteReferenceScanBudget;
545
+ }): readonly NoteReferenceHit[];
546
+ /** Collect references across every XML part under one shared part + visited-node budget. */
547
+ declare function collectPackageNoteReferences(pkg: OoxmlPackage, options?: {
548
+ readonly budget?: NoteReferenceScanBudget;
549
+ /** Soft hit cap for diagnostics. Omit / Infinity for mutation scans. */
550
+ readonly maxHits?: number;
551
+ }): readonly NoteReferenceHit[];
552
+ /**
553
+ * Load diagnostics for dangling note references. Fail-open: never throws or mutates;
554
+ * returns diagnostics for callers to surface. Does not invent missing note bodies.
555
+ *
556
+ * When the hard visited/part budget truncates or the soft hit cap binds, appends a single
557
+ * `note-reference-scan-truncated` entry so incomplete coverage is visible without breaking
558
+ * consumers that filter on `dangling-note-reference`.
559
+ */
560
+ declare function diagnoseNoteReferences(pkg: OoxmlPackage): readonly NoteDiagnostic[];
561
+ /** Whether a notes-part root contains a note with the given id (any type). */
562
+ declare function notesPartHasId(part: OoxmlPart, noteId: number): boolean;
563
+ /** List normal (body) note ids in document order, bounded. */
564
+ declare function normalNoteIds(part: OoxmlPart): readonly number[];
565
+
566
+ /** How far a note lifecycle op reaches — which parts a caller must expect to have changed. */
567
+ type NoteLifecycleImpact = 'flow-structural' | 'global';
568
+ /**
569
+ * A note lifecycle mutation: insert, delete, convert, or set properties.
570
+ *
571
+ * Package-level rather than story-level, because every one of these touches the main document,
572
+ * the notes part, the document relationships AND `[Content_Types].xml` together.
573
+ */
574
+ type NoteLifecycleOp = {
575
+ readonly op: 'insertNote';
576
+ readonly noteKind: NoteKind;
577
+ readonly paragraphId: string;
578
+ readonly offset: number;
557
579
  /**
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.
580
+ * Write the citing reference as a TRACKED insertion: the reference run goes inside a
581
+ * `w:ins` carrying this attribution. The note body stays plain — rejecting the
582
+ * reference removes it, and the orphaned body is swept by the same cascade an
583
+ * untracked reference deletion triggers.
567
584
  */
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 {
585
+ readonly revision?: RevisionAttributionInput;
586
+ } | {
587
+ readonly op: 'deleteNote';
588
+ readonly noteKind: NoteKind;
589
+ readonly noteId: number;
590
+ } | {
591
+ readonly op: 'convertNote';
592
+ readonly fromKind: NoteKind;
593
+ readonly noteId: number;
594
+ } | {
575
595
  /**
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.
596
+ * Convert every normal note of `fromKind` in document order. One atomic package
597
+ * transaction / undo unit with the same validation as repeated `convertNote`.
582
598
  */
583
- readonly pkg: OoxmlPackage;
584
- readonly revision: number;
585
- readonly selectionBefore: SelectionMark | null;
586
- readonly selectionAfter: SelectionMark | null;
599
+ readonly op: 'convertAllNotes';
600
+ readonly fromKind: NoteKind;
601
+ } | {
602
+ readonly op: 'setNoteProperties';
603
+ readonly scope: 'document' | 'section';
604
+ readonly sectionIndex?: number;
605
+ readonly footnote?: {
606
+ readonly numFmt?: string;
607
+ readonly numRestart?: string;
608
+ readonly position?: string;
609
+ readonly numStart?: number;
610
+ };
611
+ readonly endnote?: {
612
+ readonly numFmt?: string;
613
+ readonly numRestart?: string;
614
+ readonly position?: string;
615
+ readonly numStart?: number;
616
+ };
617
+ };
618
+ /** Why a note lifecycle op was refused. */
619
+ type NoteLifecycleRejection = 'invalidArgs' | 'invalid-property-value' | 'tree-invariant';
620
+ /**
621
+ * A new package, or a typed rejection.
622
+ *
623
+ * Application is PURE and all-or-nothing: there are no partial writes, so a refused op leaves the
624
+ * caller's package untouched rather than half-migrated across four parts.
625
+ */
626
+ type NoteLifecycleResult = {
627
+ readonly ok: true;
628
+ readonly package: OoxmlPackage;
629
+ readonly impact: NoteLifecycleImpact;
630
+ readonly noteId?: number;
631
+ readonly noteKind?: NoteKind;
632
+ readonly createdPartName?: string;
633
+ } | {
634
+ readonly ok: false;
635
+ readonly reason: NoteLifecycleRejection;
636
+ readonly detail?: string;
637
+ };
638
+ /** Whether an op is a note lifecycle op rather than a story-level one. */
639
+ declare function isNoteLifecycleOp(op: {
640
+ readonly op: string;
641
+ }): op is NoteLifecycleOp;
642
+ /** Attribution and limits applied to one note lifecycle op. */
643
+ interface NoteLifecycleOptions {
587
644
  /**
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.
645
+ * Shared part + visited-node budget for reference scans. When omitted a fresh default
646
+ * budget is used. Truncation rejects the op with the original package unchanged.
591
647
  */
592
- readonly group?: HistoryGroup;
648
+ readonly scanBudget?: NoteReferenceScanBudget;
593
649
  }
594
650
  /**
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).
651
+ * Apply one note lifecycle op atomically. Rejected ops leave the input package untouched.
597
652
  */
598
- /** A restorable point in history — one entry of the intent-scoped undo stack. */
599
- interface TreeDocumentCheckpoint {
653
+ declare function applyNoteLifecycleOp(pkg: OoxmlPackage, op: NoteLifecycleOp, options?: NoteLifecycleOptions): NoteLifecycleResult;
654
+ /**
655
+ * How deleting text cascades into the notes it referenced.
656
+ *
657
+ * A note's body and the citation reaching it are one thing to a reader, so removing the reference
658
+ * must remove the body too or the notes part keeps an entry nothing points at.
659
+ */
660
+ interface CascadeDeletedNoteReferencesOptions {
600
661
  /**
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.
662
+ * Independent full budgets per snapshot. When omitted each snapshot gets its own
663
+ * default budget — never share one counter across before/after walks.
604
664
  */
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;
665
+ readonly beforeBudget?: NoteReferenceScanBudget;
666
+ readonly afterBudget?: NoteReferenceScanBudget;
613
667
  }
614
668
  /**
615
- * The document store: one transaction is one atomic publication and one history entry.
669
+ * Remove note bodies for references that disappeared between two package snapshots.
670
+ * Used when `deleteText` or `deleteBlock` removes a `noteReference` atom so body+ref stay
671
+ * one undo unit.
616
672
  *
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.
673
+ * Each snapshot gets an independent full visited/part budget. If either scan truncates
674
+ * the cascade fails closed so a hostile package cannot skip body deletion silently —
675
+ * without accidentally halving capacity by charging both walks to one counter.
621
676
  */
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;
677
+ declare function cascadeDeletedNoteReferences(before: OoxmlPackage, after: OoxmlPackage, options?: CascadeDeletedNoteReferencesOptions): OoxmlPackage | null;
678
+
679
+ interface InsertImageInput {
680
+ readonly paragraphId: string;
681
+ readonly offset: number;
682
+ readonly bytes: Uint8Array;
683
+ readonly mime: SupportedImageMime;
684
+ readonly widthPoints: number;
685
+ readonly heightPoints: number;
686
+ readonly decodePort: ImageDecodePort;
687
+ readonly expectedPackageRevision: number;
688
+ readonly commitGuard?: () => boolean;
689
+ readonly title?: string;
690
+ readonly description?: string;
691
+ readonly hyperlink?: string;
692
+ /** Present in suggesting mode: the inserted drawing's run goes into a `w:ins`. */
693
+ readonly revision?: RevisionAttributionInput;
641
694
  /**
642
- * Open a store over a package, editing the named story part.
695
+ * Collaboration actor for the ids this insert mints — `wp:docPr/@id`, the image
696
+ * relationship, and a hyperlink relationship when one is asked for.
643
697
  *
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
698
+ * EXPLICIT, and not the ambient `runWithTransactionActor` binding, because this entry is
699
+ * async: the mint runs after `await validateEmbeddedImageForCommit` and the ambient actor
700
+ * is synchronous-only. A wrap around the call would compile, read as correct, and be gone
701
+ * by the time the id is taken. Omitted (solo) keeps Word's dense `highest + 1` sequence.
666
702
  */
667
- get historyPushCount(): number;
668
- get compositionActive(): boolean;
703
+ readonly actorId?: string;
704
+ }
705
+ interface ReplaceImageOptions {
706
+ readonly expectedPackageRevision: number;
707
+ readonly commitGuard?: () => boolean;
708
+ /** Collaboration actor for the replacement's minted ids. See {@link InsertImageInput.actorId}. */
709
+ readonly actorId?: string;
710
+ }
711
+ type ImageIntentResult = (Extract<PackageTransactResult, {
712
+ ok: true;
713
+ }> & {
714
+ readonly drawingNodeId?: string;
715
+ readonly mediaPartName?: string;
716
+ }) | Extract<PackageTransactResult, {
717
+ ok: false;
718
+ }>;
719
+ interface ApplyImagePropertiesInput {
720
+ readonly drawingNodeId: string;
721
+ readonly ops: readonly DrawingTreeDocOp[];
722
+ readonly hyperlink: string | null;
723
+ }
724
+
725
+ /** How one transaction behaves: its story scope, its attribution, and its selection marks. */
726
+ interface TransactOptions {
727
+ readonly origin?: string;
728
+ /** Stable actor attribution for collaboration and audit correlation. */
729
+ readonly actorId?: string;
730
+ /** Stable constituent identity for collaboration duplicate correlation. */
731
+ readonly operationId?: string;
669
732
  /**
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.
733
+ * Whether this transaction enters the legacy snapshot undo stack.
677
734
  *
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.
735
+ * Collaboration commits set this to false because their actor-local undo authority is the
736
+ * CRDT undo manager. Omitted preserves the ordinary non-collaborative history behavior.
680
737
  */
681
- graftPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): void;
738
+ readonly recordsHistory?: boolean;
682
739
  /**
683
- * Replace the current part without recording history, but advance the revision so
684
- * revision-keyed projections cannot survive a package snapshot install.
740
+ * A COMMAND is one user intent that may need several ops (a toolbar click applying a
741
+ * property across a multi-run selection). It is still exactly one history entry, which is
742
+ * the same rule a plain transaction follows — the option exists to say so explicitly at
743
+ * the call site rather than leaving it implied.
685
744
  */
686
- replacePart(part: OoxmlPart): void;
745
+ readonly scope?: 'transaction' | 'command';
687
746
  /**
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.
747
+ * Floor on the published impact. Header/footer story edits use `global` so every page
748
+ * sharing the part invalidates rather than keeping stale furniture.
691
749
  */
692
- checkpoint(): TreeDocumentCheckpoint;
693
- /** Full restore — part, revision, history stacks, and composition. */
694
- restoreCheckpoint(checkpoint: TreeDocumentCheckpoint): void;
750
+ readonly minimumImpact?: ImpactClass;
751
+ /** Story identity stamped onto the published ModelChange (package-aware targeting). */
752
+ readonly story?: TreeStoryRef;
695
753
  /**
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.
754
+ * The gesture this transaction belongs to: consecutive transactions carrying the SAME
755
+ * token extend one history entry (package from before the first, selection after the
756
+ * latest). Another token, none, undo or redo closes the group. See {@link HistoryGroup}.
699
757
  */
700
- restoreHistoryStacks(checkpoint: TreeDocumentCheckpoint): void;
701
- subscribe(listener: (change: TreeModelChange) => void): () => void;
758
+ readonly historyGroup?: HistoryGroup;
702
759
  /**
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.
760
+ * How the transaction's ops address saved field results. `atomic` (the default) keeps
761
+ * every field one offset unit; `editable` addresses saved results as text. See
762
+ * `field-result-mode.ts`.
709
763
  */
710
- transact(build: (ctx: TransactionContext) => void, options?: TransactOptions): TransactResult;
711
- private commitTransaction;
764
+ readonly fieldResults?: FieldResultsMode;
765
+ }
766
+ /** How a store is constructed: its limits, its history depth, and its identity source. */
767
+ interface TreeDocumentStoreOptions {
768
+ /** Bound on retained history entries. Oldest entries drop first. */
769
+ readonly historyLimit?: number;
712
770
  /**
713
- * Open one history entry for an IME composition.
771
+ * The document's `settings.xml`, for a store built from a PART rather than a package.
714
772
  *
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.
773
+ * Forms protection lives one part up from the op, and a store built from a part gets a
774
+ * synthetic one-part package that cannot see it — so `w:documentProtection w:edit="forms"`
775
+ * was enforced in the body and nowhere else, and a header, a footer or a note accepted every
776
+ * write a protected document is supposed to refuse. Read through a getter, not captured: a
777
+ * document can gain or lose protection while its stories stay open.
731
778
  */
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;
779
+ readonly settingsPart?: () => OoxmlPart | null | undefined;
739
780
  /**
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
781
+ * Opt-in relaxation of `w:lock` for run-property writes. See {@link ContentLockPolicy};
782
+ * absent, `contentLocked` and `sdtContentLocked` refuse formatting like any other write.
743
783
  */
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;
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;
784
+ readonly contentLockPolicy?: ContentLockPolicy;
785
+ /**
786
+ * The glossary prompt a control's `w:placeholder/w:docPart` names, for a store built from
787
+ * a PART. Its synthetic package holds no glossary, so an emptied control in a header or a
788
+ * note would show the type's default prompt instead of the document's own.
789
+ *
790
+ * @internal
791
+ */
792
+ readonly placeholderText?: (docPart: string) => string | undefined;
793
+ }
884
794
 
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;
795
+ /** A selection the caller wants restored when an entry is undone or redone. */
796
+ /** A selection captured with a transaction, so undo restores where the caret was. */
797
+ interface SelectionMark {
918
798
  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;
799
+ readonly start: number;
800
+ readonly end: number;
933
801
  }
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
802
  /**
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.
803
+ * Which editable story a ModelChange came from.
958
804
  *
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`.
805
+ * Mirrors `EditorScope` for body and header/footer — `{ kind: 'headerFooter'; rId }` —
806
+ * so package-aware mutation and the public scope contract stay one vocabulary. Body
807
+ * commits omit `rId`; header/footer commits carry the relationship id that addressed
808
+ * the part. Notes parts use `{ kind: 'notesPart'; noteKind }` (one store per part).
962
809
  */
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';
810
+ /** Which story a transaction targets: the body, a header/footer part, or a notes part. */
811
+ type TreeStoryRef = {
812
+ readonly kind: 'body';
813
+ readonly partName: string;
814
+ } | {
815
+ readonly kind: 'headerFooter';
816
+ readonly partName: string;
817
+ readonly rId: string;
818
+ } | {
819
+ readonly kind: 'notesPart';
820
+ readonly partName: string;
821
+ readonly noteKind: 'footnote' | 'endnote';
822
+ };
971
823
  /**
972
- * A note lifecycle mutation: insert, delete, convert, or set properties.
824
+ * What one committed transaction changed: the revision, the ids touched, and the impact class.
973
825
  *
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.
826
+ * The ids are what let layout and paint re-do only the affected blocks instead of the document.
976
827
  */
977
- type NoteLifecycleOp = {
978
- readonly op: 'insertNote';
979
- readonly noteKind: NoteKind;
980
- readonly paragraphId: string;
981
- readonly offset: number;
828
+ interface TreeModelChange {
829
+ readonly change: 'model-change';
830
+ readonly fromRevision: number;
831
+ readonly toRevision: number;
832
+ readonly commitId: string;
833
+ readonly origin: string;
834
+ /** Stable collaboration actor attribution, when the caller supplied it. */
835
+ readonly actorId?: string;
836
+ /** Stable collaboration operation identity, when the caller supplied it. */
837
+ readonly operationId?: string;
838
+ readonly dirty: readonly string[];
839
+ readonly created: readonly string[];
840
+ readonly deleted: readonly string[];
841
+ readonly splitJoin: readonly ({
842
+ readonly split: {
843
+ readonly from: string;
844
+ readonly tail: string;
845
+ };
846
+ } | {
847
+ readonly join: {
848
+ readonly kept: string;
849
+ readonly removed: string;
850
+ };
851
+ })[];
852
+ readonly dependencyKeys: readonly string[];
853
+ /** The widest impact among the transaction's ops — what layout must scope to. */
854
+ readonly impact: ImpactClass;
982
855
  /**
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.
856
+ * Story that published this change. Absent on body-only store publishes that predate
857
+ * package-aware targeting; `TreePackageStore` always sets it.
987
858
  */
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
- } | {
859
+ readonly story?: TreeStoryRef;
998
860
  /**
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
+ * Committed collapsed caret for this transaction, when one exists.
862
+ * Matches history `selectionAfter` when that mark is collapsed; absent for explicit
863
+ * null, non-collapsed explicit selection, or when no caret was committed.
1001
864
  */
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 = {
865
+ readonly caret?: SelectionMark;
866
+ }
867
+ /** Whether a transaction committed, or the typed reason it was refused. */
868
+ type TransactResult = {
1030
869
  readonly ok: true;
1031
- readonly package: OoxmlPackage;
1032
- readonly impact: NoteLifecycleImpact;
1033
- readonly noteId?: number;
1034
- readonly noteKind?: NoteKind;
1035
- readonly createdPartName?: string;
870
+ readonly change: TreeModelChange | null;
1036
871
  } | {
1037
872
  readonly ok: false;
1038
- readonly reason: NoteLifecycleRejection;
873
+ readonly reason: TreeOpRejection;
1039
874
  readonly detail?: string;
1040
875
  };
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 {
876
+ /** What a transaction body is handed: the working tree, and the means to stage ops against it. */
877
+ interface TransactionContext {
878
+ /** Stage one op against the STORY part. Returns false once the transaction has failed. */
879
+ apply(op: TreeDocOp): boolean;
880
+ /**
881
+ * Stage one op against a named part.
882
+ *
883
+ * A comment body lives in `comments.xml` and is edited by the same ops that edit the story,
884
+ * so this is `apply` with the target named rather than a second vocabulary.
885
+ */
886
+ applyTo(partName: string, op: TreeDocOp): boolean;
887
+ /**
888
+ * Stage a whole-package edit: a new part, a relationship, a content-type override.
889
+ *
890
+ * The edit is a pure function of the working package, so a rejected transaction discards it
891
+ * with everything else. Returning the SAME package is a no-op, not a failure — a primitive
892
+ * that finds nothing to do says so that way.
893
+ *
894
+ * Engine package edits refuse by throwing the internal `PackageEditRefusal`
895
+ * (`package-edit-refusal.ts`): the transaction is refused with its reason, `package-changed`
896
+ * by default, and nothing is applied. Any other throw escapes `transact` as a crash.
897
+ */
898
+ applyPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): boolean;
899
+ /** The selection to restore when this entry is undone. */
900
+ selectionBefore(selection: SelectionMark | null): void;
901
+ /** The selection to restore when this entry is redone. */
902
+ selectionAfter(selection: SelectionMark | null): void;
903
+ }
904
+ interface HistoryEntry {
905
+ /**
906
+ * The whole package as it was, not just the story part.
907
+ *
908
+ * Affordable for the same reason a part snapshot was: parts are immutable and deep-frozen, so
909
+ * a package snapshot is a Map of references and every part the transaction did not touch is
910
+ * object-identical to the one before it. Undo stays a pointer swap, and it now reverses every
911
+ * part one intent wrote rather than only the story.
912
+ */
913
+ readonly pkg: OoxmlPackage;
914
+ readonly revision: number;
915
+ readonly selectionBefore: SelectionMark | null;
916
+ readonly selectionAfter: SelectionMark | null;
1047
917
  /**
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.
918
+ * The gesture this entry is still collecting, while it is the newest entry and nothing
919
+ * has closed it. Only ever set on the top of the undo stack: {@link closeHistoryGroup}
920
+ * strips it, and undo and redo never copy it onto the entries they push.
1050
921
  */
1051
- readonly scanBudget?: NoteReferenceScanBudget;
922
+ readonly group?: HistoryGroup;
1052
923
  }
1053
924
  /**
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.
925
+ * Opaque document+history checkpoint for package-coordinator rollback.
926
+ * Used when a story mutation may promote to a package undo unit (note-ref cascade).
1062
927
  */
1063
- interface CascadeDeletedNoteReferencesOptions {
928
+ /** A restorable point in history — one entry of the intent-scoped undo stack. */
929
+ interface TreeDocumentCheckpoint {
1064
930
  /**
1065
- * Independent full budgets per snapshot. When omitted each snapshot gets its own
1066
- * default budget — never share one counter across before/after walks.
931
+ * The whole package, not one part. The store owns the package so a transaction spanning
932
+ * several parts is one publication — a checkpoint of only the story part could not roll
933
+ * back the comment or numbering part the same transaction wrote.
1067
934
  */
1068
- readonly beforeBudget?: NoteReferenceScanBudget;
1069
- readonly afterBudget?: NoteReferenceScanBudget;
935
+ readonly pkg: OoxmlPackage;
936
+ readonly revision: number;
937
+ readonly undoStack: readonly HistoryEntry[];
938
+ readonly redoStack: readonly HistoryEntry[];
939
+ readonly composition: {
940
+ readonly entry: HistoryEntry;
941
+ readonly committed: boolean;
942
+ } | null;
1070
943
  }
1071
944
  /**
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.
945
+ * The document store: one transaction is one atomic publication and one history entry.
1075
946
  *
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.
947
+ * `apply` STAGES ops against a working part and nothing is visible until `transact` returns, so a
948
+ * batch rejected halfway leaves the revision, the tree, the indexes and every subscriber exactly
949
+ * as they were. That all-or-nothing property is what lets a caller compose ops without having to
950
+ * reason about partial application.
1079
951
  */
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[];
952
+ declare class TreeDocumentStore {
953
+ private current;
954
+ /** The part `apply` targets and `part` returns: the story this store is editing. */
955
+ private readonly storyPartName;
956
+ private rev;
957
+ private commitCounter;
958
+ private readonly undoStack;
959
+ private readonly redoStack;
960
+ private pushCount;
961
+ private readonly subscribers;
962
+ private readonly historyLimit;
963
+ /** See {@link TreeDocumentStoreOptions.settingsPart}. */
964
+ private readonly settingsPartOverride;
965
+ private readonly placeholderTextOverride;
966
+ /** See {@link TreeDocumentStoreOptions.contentLockPolicy}. */
967
+ private readonly contentLockPolicy;
968
+ /** Package-aware story tag applied to publishes (including undo/redo). */
969
+ private storyRef;
970
+ /** Open composition, if any. While set, transactions extend one entry (task 5.5). */
971
+ private composition;
1092
972
  /**
1093
- * Collaboration actor for the ids this paste mints.
973
+ * Open a store over a package, editing the named story part.
1094
974
  *
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.
975
+ * A bare part is accepted and wrapped in a single-part package, so callers that never open a
976
+ * real package — tests, headless tooling — are unaffected by the widening.
1099
977
  */
1100
- readonly actorId?: string;
978
+ constructor(source: OoxmlPart | OoxmlPackage, storyPartNameOrOptions?: string | TreeDocumentStoreOptions, maybeOptions?: TreeDocumentStoreOptions);
1101
979
  /**
1102
- * Suggesting mode: the author and date the whole paste is proposed under, as one tracked
1103
- * insertion (`trackedFragment`). The caller tracks `priorOps` itself.
980
+ * Stamp story identity onto subsequent publishes for this store (including undo/redo).
981
+ * Used by the package coordinator so history navigation keeps the same scope tag.
1104
982
  */
1105
- readonly revision?: RevisionAttributionInput;
983
+ setStoryRef(story: TreeStoryRef | null): void;
984
+ /** The story part being edited. Unchanged for every caller that predates the widening. */
985
+ get part(): OoxmlPart;
986
+ /** Every part, including the ones a multi-part transaction wrote. */
987
+ get package(): OoxmlPackage;
988
+ get revision(): number;
989
+ get canUndo(): boolean;
990
+ get canRedo(): boolean;
991
+ /** Retained entries — the unit `undo()` reverses, so tests can assert grouping. */
992
+ get historyDepth(): number;
993
+ /**
994
+ * How many entries this store has ever pushed. Monotonic, unlike `historyDepth`, which
995
+ * stands still at the history limit because the oldest entry is shifted out.
996
+ * @internal
997
+ */
998
+ get historyPushCount(): number;
999
+ get compositionActive(): boolean;
1000
+ /**
1001
+ * Replace the package OUTSIDE the transaction and history lanes.
1002
+ *
1003
+ * For package writes that are not a user intent and publish no revision: grafting
1004
+ * `numbering.xml` into a document that never had one, which the caller performs as a
1005
+ * precondition of the list op that follows. Those edits were previously kept in a package
1006
+ * variable beside the store, which meant two owners of one value and, predictably, two
1007
+ * values — a graft written to one and a save read from the other.
1008
+ *
1009
+ * Deliberately narrow and deliberately awkward to reach for: anything a user did belongs in
1010
+ * `transact`, where it gets a revision, an undo entry and the invariant checks.
1011
+ */
1012
+ graftPackage(edit: (pkg: OoxmlPackage) => OoxmlPackage): void;
1013
+ /**
1014
+ * Replace the current part without recording history, but advance the revision so
1015
+ * revision-keyed projections cannot survive a package snapshot install.
1016
+ */
1017
+ replacePart(part: OoxmlPart): void;
1018
+ /**
1019
+ * Snapshot part, revision, and undo/redo stacks so the package coordinator can roll
1020
+ * back a story transaction that fails after commit (e.g. note-reference cascade) or
1021
+ * discard a local history entry when promoting to a package undo unit.
1022
+ */
1023
+ checkpoint(): TreeDocumentCheckpoint;
1024
+ /** Full restore — part, revision, history stacks, and composition. */
1025
+ restoreCheckpoint(checkpoint: TreeDocumentCheckpoint): void;
1026
+ /**
1027
+ * Restore undo/redo stacks only, keeping the current part and revision.
1028
+ * Used when a story mutation is promoted to a package history pointer so the local
1029
+ * orphan entry does not steal a later undo.
1030
+ */
1031
+ restoreHistoryStacks(checkpoint: TreeDocumentCheckpoint): void;
1032
+ subscribe(listener: (change: TreeModelChange) => void): () => void;
1033
+ /**
1034
+ * Run one atomic transaction.
1035
+ *
1036
+ * Ops are staged against a working copy. On the first rejection the whole transaction is
1037
+ * abandoned: no revision, no history entry, no notification. On success exactly one
1038
+ * revision is published and exactly one history entry is recorded — unless a composition
1039
+ * is open, in which case the entry already exists and this transaction joins it.
1040
+ */
1041
+ transact(build: (ctx: TransactionContext) => void, options?: TransactOptions): TransactResult;
1042
+ private commitTransaction;
1043
+ /**
1044
+ * Open one history entry for an IME composition.
1045
+ *
1046
+ * Everything committed until `endComposition` collapses into this single entry, which is
1047
+ * what makes a composed word one undo step rather than one per intermediate transaction.
1048
+ */
1049
+ beginComposition(selectionBefore?: SelectionMark | null): void;
1050
+ /** Close the composition, recording its entry only if anything actually committed. */
1051
+ endComposition(): void;
1052
+ /**
1053
+ * Cancel an open composition without recording an entry, leaving whatever it committed
1054
+ * in place. An IME cancel is not an undo request; the caller decides what to revert.
1055
+ */
1056
+ cancelComposition(): void;
1057
+ /**
1058
+ * Close the open history group, if any: the next transaction records its own entry.
1059
+ * The store does this itself for another token, none, undo and redo; the package
1060
+ * coordinator does it for history moving ABOVE the store (a package unit, another
1061
+ * story's pointer), which the store cannot see.
1062
+ */
1063
+ closeHistoryGroup(): void;
1064
+ undo(): TreeModelChange | null;
1065
+ redo(): TreeModelChange | null;
1066
+ /** The selection to restore for the entry `undo()` would reverse next. */
1067
+ selectionForUndo(): SelectionMark | null;
1068
+ /** The selection to restore for the entry `redo()` would reapply next. */
1069
+ selectionForRedo(): SelectionMark | null;
1070
+ /**
1071
+ * Remove the newest undo entry and answer its selections, for the package coordinator
1072
+ * when a package history group absorbs the story pointer that entry belonged to.
1073
+ * @internal
1074
+ */
1075
+ discardNewestUndoEntry(): Pick<HistoryEntry, 'selectionBefore' | 'selectionAfter'> | null;
1076
+ private pushUndo;
1077
+ private publish;
1106
1078
  }
1107
- type FragmentPasteResult = (Extract<PackageTransactResult, {
1108
- ok: true;
1109
- }> & {
1110
- readonly blockCount: number;
1111
- }) | Extract<PackageTransactResult, {
1112
- ok: false;
1113
- }>;
1079
+
1114
1080
  /**
1115
- * Read the fragment through the bounded file-open trust boundary, merge its resources,
1116
- * and land its blocks — all inside one transaction. Any refusal leaves the document
1117
- * exactly as it was; the caller (the paste router) degrades to the next flavour.
1081
+ * The identity of one continuous user gesture, for history grouping.
1082
+ *
1083
+ * A live color picker, a font-size stepper or a spacing slider applies every intermediate
1084
+ * value so the document updates under the pointer, and each of those writes is a
1085
+ * transaction in its own right — it must render, and a collaborator must receive it. What
1086
+ * they are NOT is separate undo steps. The caller mints one token when the gesture starts
1087
+ * (`Symbol('color-drag')`) and passes it with every write until the gesture ends.
1088
+ *
1089
+ * A symbol rather than a string, so two unrelated callers cannot collide by naming their
1090
+ * gesture the same way, and so a token cannot outlive its gesture by accident through
1091
+ * serialization.
1118
1092
  */
1119
- declare function applyFragmentPaste(store: TreePackageStore, scope: StoryScope, input: FragmentPasteInput): FragmentPasteResult;
1093
+ type HistoryGroup = symbol;
1094
+ type HistoryCaptureKind = 'started' | 'extended' | 'split';
1095
+ type HistoryCaptureReason = 'package-unit' | 'composition';
1096
+ declare function observeHistoryGroup<T>(group: HistoryGroup, report: (kind: HistoryCaptureKind, reason?: HistoryCaptureReason) => void, run: () => T): T;
1097
+ declare function reportHistoryGroup(group: HistoryGroup | undefined, kind: HistoryCaptureKind, reason?: HistoryCaptureReason): void;
1120
1098
 
1121
1099
  /** Attribution stamped on one remote canonical publication. */
1122
1100
  interface RemotePackageAttribution {
@@ -1226,13 +1204,7 @@ declare class TreePackageStore {
1226
1204
  * a matching tuple proves the snapshot cannot differ; nothing invalidates explicitly.
1227
1205
  */
1228
1206
  private currentPackageMemo;
1229
- /**
1230
- * What the body and every story store open with. `settingsPart` is the SAME live getter for
1231
- * all of them: a story store is built from a PART, whose synthetic package holds no
1232
- * `settings.xml`, and a store keeps its own working copy, so a replaced `settings.xml` left one
1233
- * story reading stale protection. Protection that holds in a header but not the body is worse
1234
- * than none: it looks enforced. The lock policy reaches every story for the same reason.
1235
- */
1207
+ /** What the body and every story store open with; see {@link storyStoreOptionsFor}. */
1236
1208
  private storyStoreOptions;
1237
1209
  constructor(pkg: OoxmlPackage, main: OoxmlPart, options?: TreePackageStoreOptions);
1238
1210
  get packageRevision(): number;
@@ -1386,5 +1358,44 @@ declare class TreePackageStore {
1386
1358
  private publishSynthetic;
1387
1359
  }
1388
1360
 
1361
+ /** Decoded fragment payloads above this cap are refused before any package work. */
1362
+ declare const MAX_FRAGMENT_DECODED_BYTES: number;
1363
+ interface FragmentPasteInput {
1364
+ readonly paragraphId: string;
1365
+ readonly offset: number;
1366
+ /** The fragment package zip (from the clipboard codec or the HTML projection). */
1367
+ readonly fragmentBytes: Uint8Array;
1368
+ readonly lastMarkCovered: boolean;
1369
+ /** Ops that clear the current selection first (the surface's deleteSelectionPlan). */
1370
+ readonly priorOps?: readonly TreeDocOp[];
1371
+ /**
1372
+ * Collaboration actor for the ids this paste mints.
1373
+ *
1374
+ * A paste freshens three package-wide namespaces — bookmark `@w:id`, revision `@w:id` and
1375
+ * `wp:docPr/@id` — and the merge reads the actor off the open transaction. Without one, two
1376
+ * replicas pasting the same fragment from one snapshot mint the same numbers, and only the
1377
+ * merged document shows it. Omitted (solo) keeps Word's dense sequences.
1378
+ */
1379
+ readonly actorId?: string;
1380
+ /**
1381
+ * Suggesting mode: the author and date the whole paste is proposed under, as one tracked
1382
+ * insertion (`trackedFragment`). The caller tracks `priorOps` itself.
1383
+ */
1384
+ readonly revision?: RevisionAttributionInput;
1385
+ }
1386
+ type FragmentPasteResult = (Extract<PackageTransactResult, {
1387
+ ok: true;
1388
+ }> & {
1389
+ readonly blockCount: number;
1390
+ }) | Extract<PackageTransactResult, {
1391
+ ok: false;
1392
+ }>;
1393
+ /**
1394
+ * Read the fragment through the bounded file-open trust boundary, merge its resources,
1395
+ * and land its blocks — all inside one transaction. Any refusal leaves the document
1396
+ * exactly as it was; the caller (the paste router) degrades to the next flavour.
1397
+ */
1398
+ declare function applyFragmentPaste(store: TreePackageStore, scope: StoryScope, input: FragmentPasteInput): FragmentPasteResult;
1399
+
1389
1400
  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 d, 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 };
1390
1401
  export type { PackageTransactResult as $, ApplyImagePropertiesInput as A, ContentLockPolicy as C, EditOptions as E, FieldResultsMode as F, HeaderFooterVariant 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 _, StoryTargetRejection 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, HistoryGroup as b, TreeModelChange as c, HeaderFooterParts as e, HeaderFooterSectionResolution as f, 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 };