@beyondwork/docx-react-component 1.0.101 → 1.0.103

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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/core/commands/formatting-commands.ts +8 -7
  3. package/src/core/commands/paragraph-layout-commands.ts +11 -10
  4. package/src/core/commands/section-layout-commands.ts +7 -6
  5. package/src/core/commands/style-commands.ts +3 -2
  6. package/src/io/export/build-app-properties-xml.ts +24 -0
  7. package/src/io/normalize/normalize-text.ts +6 -5
  8. package/src/io/ooxml/docprops.ts +298 -0
  9. package/src/io/ooxml/parse-anchor.ts +15 -15
  10. package/src/io/ooxml/parse-drawing.ts +5 -5
  11. package/src/io/ooxml/parse-fields.ts +16 -15
  12. package/src/io/ooxml/parse-font-table.ts +2 -1
  13. package/src/io/ooxml/parse-footnotes.ts +3 -2
  14. package/src/io/ooxml/parse-headers-footers.ts +7 -6
  15. package/src/io/ooxml/parse-main-document.ts +41 -40
  16. package/src/io/ooxml/parse-numbering.ts +3 -2
  17. package/src/io/ooxml/parse-object.ts +6 -6
  18. package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
  19. package/src/io/ooxml/parse-picture.ts +16 -16
  20. package/src/io/ooxml/parse-run-formatting.ts +11 -10
  21. package/src/io/ooxml/parse-settings.ts +2 -1
  22. package/src/io/ooxml/parse-shapes.ts +18 -17
  23. package/src/io/ooxml/parse-styles.ts +16 -16
  24. package/src/io/ooxml/parse-theme.ts +5 -4
  25. package/src/model/canonical-document.ts +920 -815
  26. package/src/runtime/formatting/document-lookup.ts +3 -2
  27. package/src/runtime/formatting/formatting-context.ts +66 -25
  28. package/src/runtime/formatting/index.ts +18 -0
  29. package/src/runtime/formatting/layout-inputs.ts +256 -0
  30. package/src/runtime/formatting/numbering/geometry.ts +13 -12
  31. package/src/runtime/formatting/style-cascade.ts +2 -1
  32. package/src/runtime/formatting/table-style-resolver.ts +8 -7
  33. package/src/runtime/surface-projection.ts +31 -36
  34. package/src/session/export/stateful-export-pipeline.ts +9 -4
  35. package/src/session/export/stateful-export.ts +22 -6
  36. package/src/session/import/canonical-assembly.ts +2 -3
  37. package/src/session/import/loader-types.ts +3 -1
  38. package/src/session/import/loader.ts +12 -0
  39. package/src/session/import/normalize.ts +2 -1
  40. package/src/session/import/source-package-evidence.ts +1016 -0
  41. package/src/session/shared/session-utils.ts +9 -0
@@ -105,7 +105,7 @@ export interface CanonicalDocument {
105
105
  */
106
106
  export interface CanonicalDocumentFragment {
107
107
  /** Block-level children of the fragment, in document order. */
108
- readonly blocks: ReadonlyArray<BlockNode>;
108
+ readonly blocks: BlockNode[];
109
109
  }
110
110
 
111
111
  /**
@@ -138,18 +138,20 @@ export type MutableCanonicalDocumentFragment = Mutable<CanonicalDocumentFragment
138
138
  * Never cast a value received as a parameter. Only cast values you just
139
139
  * created via object literal, spread, `structuredClone`, or `createCanonicalDocument`.
140
140
  *
141
- * **Shallow only.** `Mutable<T>` strips `readonly` at the top level of `T`;
142
- * nested `ReadonlyArray<…>`, `Readonly<…>`, and inner `readonly` modifiers
143
- * declared inside referenced types are preserved. For example,
144
- * `Mutable<CanonicalDocument>` does NOT make `draft.content.children`
145
- * mutable `content` is `DocumentRootNode`, whose `children` field is
146
- * declared as it is in the source type. If you need to mutate nested
147
- * structure, build the nested value first (also as a `Mutable<…>` of the
148
- * nested type) and assign it onto the draft as a complete replacement.
149
- * Deep-readonly propagation across nested catalogs/stores/nodes is a
150
- * deferred D1 follow-up (see `docs/plans/refactor/02-canonical-document-deferred.md`).
141
+ * `Mutable<T>` strips readonly recursively, including `…[]`,
142
+ * `Readonly<…>`, and inner readonly modifiers declared inside referenced
143
+ * types. That is intentional for D1: parser/runtime producers can build a
144
+ * fully local draft with normal assignment and array operations, then return
145
+ * it widened to the readonly canonical type at the function boundary.
151
146
  */
152
- export type Mutable<T> = { -readonly [K in keyof T]: T[K] };
147
+ export type Mutable<T> =
148
+ T extends (...args: infer Args) => infer Return
149
+ ? (...args: Args) => Return
150
+ : T extends (infer Item)[]
151
+ ? Mutable<Item>[]
152
+ : T extends object
153
+ ? { -readonly [K in keyof T]: Mutable<T[K]> }
154
+ : T;
153
155
 
154
156
  /**
155
157
  * Draft-only mutable view of `CanonicalDocument`. Equivalent to
@@ -186,70 +188,92 @@ export type MutableCanonicalFontTable = Mutable<CanonicalFontTable>;
186
188
 
187
189
 
188
190
  export interface DocumentMetadata {
189
- title?: string;
190
- subject?: string;
191
- description?: string;
192
- language?: string;
193
- keywords?: string[];
194
- category?: string;
195
- importMode?: string;
196
- customProperties: Record<string, string>;
191
+ readonly title?: string;
192
+ readonly subject?: string;
193
+ readonly description?: string;
194
+ readonly creator?: string;
195
+ readonly language?: string;
196
+ readonly keywords?: string[];
197
+ readonly category?: string;
198
+ readonly lastModifiedBy?: string;
199
+ readonly contentStatus?: string;
200
+ readonly revision?: string;
201
+ readonly version?: string;
202
+ readonly createdUtc?: string;
203
+ readonly modifiedUtc?: string;
204
+ readonly importMode?: string;
205
+ readonly appProperties?: DocumentAppProperties;
206
+ readonly customProperties: Record<string, string>;
207
+ }
208
+
209
+ export interface DocumentAppProperties {
210
+ readonly application?: string;
211
+ readonly appVersion?: string;
212
+ readonly template?: string;
213
+ readonly pages?: number;
214
+ readonly words?: number;
215
+ readonly characters?: number;
216
+ readonly charactersWithSpaces?: number;
217
+ readonly totalTime?: number;
218
+ readonly company?: string;
219
+ readonly manager?: string;
220
+ readonly docSecurity?: number;
197
221
  }
198
222
 
199
223
  export interface StylesCatalog {
200
- paragraphs: Record<string, ParagraphStyleDefinition>;
201
- characters: Record<string, CharacterStyleDefinition>;
202
- tables: Record<string, TableStyleDefinition>;
224
+ readonly paragraphs: Record<string, ParagraphStyleDefinition>;
225
+ readonly characters: Record<string, CharacterStyleDefinition>;
226
+ readonly tables: Record<string, TableStyleDefinition>;
203
227
  /**
204
228
  * `w:type="numbering"` styles — preserve-only. Word emits these for
205
229
  * style-linked list families (List Bullet, List Number). Canonical
206
230
  * consumers do not apply numbering via these styles; they survive
207
231
  * only for round-trip fidelity.
208
232
  */
209
- numberingStyles?: Record<string, NumberingStyleDefinition>;
210
- latentStyles?: Record<string, LatentStyleDefinition>;
211
- fromPackage?: boolean;
212
- docDefaults?: DocumentDefaults;
233
+ readonly numberingStyles?: Record<string, NumberingStyleDefinition>;
234
+ readonly latentStyles?: Record<string, LatentStyleDefinition>;
235
+ readonly fromPackage?: boolean;
236
+ readonly docDefaults?: DocumentDefaults;
213
237
  }
214
238
 
215
239
  /** A `<w:style w:type="numbering">` entry. Preserve-only. */
216
240
  export interface NumberingStyleDefinition {
217
- styleId: string;
218
- displayName: string;
219
- kind: "numbering";
220
- isDefault: boolean;
221
- basedOn?: string;
222
- aliases?: string[];
241
+ readonly styleId: string;
242
+ readonly displayName: string;
243
+ readonly kind: "numbering";
244
+ readonly isDefault: boolean;
245
+ readonly basedOn?: string;
246
+ readonly aliases?: string[];
223
247
  /**
224
248
  * The `<w:numId w:val="..."/>` inside the `<w:pPr><w:numPr>` subtree,
225
249
  * when present. Consumers that later resolve numbering-type styles to
226
250
  * numbering instances (Lane 3a) will use this.
227
251
  */
228
- numberingInstanceId?: string;
252
+ readonly numberingInstanceId?: string;
229
253
  }
230
254
 
231
255
  export interface ParagraphStyleDefinition {
232
- styleId: string;
233
- basedOn?: string;
234
- nextStyle?: string;
235
- outlineLevel?: number;
236
- numbering?: ParagraphStyleNumberingReference;
237
- displayName: string;
256
+ readonly styleId: string;
257
+ readonly basedOn?: string;
258
+ readonly nextStyle?: string;
259
+ readonly outlineLevel?: number;
260
+ readonly numbering?: ParagraphStyleNumberingReference;
261
+ readonly displayName: string;
238
262
  /**
239
263
  * Alternate display names parsed from `<w:aliases w:val="A,B"/>`
240
264
  * (ECMA-376 §17.7.4.2). Comma-separated in source; stored as array.
241
265
  */
242
- aliases?: string[];
266
+ readonly aliases?: string[];
243
267
  /**
244
268
  * `<w:autoRedefine/>` — when true, Word re-records paragraph direct
245
269
  * formatting into the style definition as the user types (ECMA-376
246
270
  * §17.7.4.3). Round-trip preservation only; no render effect.
247
271
  */
248
- autoRedefine?: boolean;
249
- kind: "paragraph";
250
- isDefault: boolean;
251
- paragraphProperties?: CanonicalParagraphFormatting;
252
- runProperties?: CanonicalRunFormatting;
272
+ readonly autoRedefine?: boolean;
273
+ readonly kind: "paragraph";
274
+ readonly isDefault: boolean;
275
+ readonly paragraphProperties?: CanonicalParagraphFormatting;
276
+ readonly runProperties?: CanonicalRunFormatting;
253
277
  /**
254
278
  * Style ID of the linked character style (from `<w:link w:val="..."/>`).
255
279
  * Populated during parse; the second-pass resolver in `parse-styles.ts`
@@ -257,55 +281,55 @@ export interface ParagraphStyleDefinition {
257
281
  * declares the relationship on one side. Mirrors LibreOffice's
258
282
  * `StyleSheetTable.cxx:535` second pass.
259
283
  */
260
- linkedStyleId?: string;
284
+ readonly linkedStyleId?: string;
261
285
  }
262
286
 
263
287
  export interface ParagraphStyleNumberingReference {
264
- numberingInstanceId: string;
265
- level?: number;
288
+ readonly numberingInstanceId: string;
289
+ readonly level?: number;
266
290
  }
267
291
 
268
292
  export interface CharacterStyleDefinition {
269
- styleId: string;
270
- basedOn?: string;
271
- displayName: string;
293
+ readonly styleId: string;
294
+ readonly basedOn?: string;
295
+ readonly displayName: string;
272
296
  /** See `ParagraphStyleDefinition.aliases`. */
273
- aliases?: string[];
274
- kind: "character";
275
- isDefault: boolean;
276
- runProperties?: CanonicalRunFormatting;
297
+ readonly aliases?: string[];
298
+ readonly kind: "character";
299
+ readonly isDefault: boolean;
300
+ readonly runProperties?: CanonicalRunFormatting;
277
301
  /**
278
302
  * Style ID of the linked paragraph style (from `<w:link w:val="..."/>`).
279
303
  * See `ParagraphStyleDefinition.linkedStyleId` for the second-pass
280
304
  * reciprocal-resolution contract.
281
305
  */
282
- linkedStyleId?: string;
306
+ readonly linkedStyleId?: string;
283
307
  }
284
308
 
285
309
  export interface TableStyleDefinition {
286
- styleId: string;
287
- basedOn?: string;
288
- displayName: string;
310
+ readonly styleId: string;
311
+ readonly basedOn?: string;
312
+ readonly displayName: string;
289
313
  /** See `ParagraphStyleDefinition.aliases`. */
290
- aliases?: string[];
291
- kind: "table";
292
- isDefault: boolean;
293
- formatting?: TableStyleFormatting;
294
- conditionalFormatting?: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>>;
314
+ readonly aliases?: string[];
315
+ readonly kind: "table";
316
+ readonly isDefault: boolean;
317
+ readonly formatting?: TableStyleFormatting;
318
+ readonly conditionalFormatting?: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>>;
295
319
  }
296
320
 
297
321
  export interface LatentStyleDefinition {
298
- name: string;
299
- locked?: boolean;
300
- semiHidden?: boolean;
301
- unhideWhenUsed?: boolean;
302
- qFormat?: boolean;
303
- uiPriority?: number;
322
+ readonly name: string;
323
+ readonly locked?: boolean;
324
+ readonly semiHidden?: boolean;
325
+ readonly unhideWhenUsed?: boolean;
326
+ readonly qFormat?: boolean;
327
+ readonly uiPriority?: number;
304
328
  }
305
329
 
306
330
  export interface NumberingCatalog {
307
- abstractDefinitions: Record<string, AbstractNumberingDefinition>;
308
- instances: Record<string, NumberingInstance>;
331
+ readonly abstractDefinitions: Record<string, AbstractNumberingDefinition>;
332
+ readonly instances: Record<string, NumberingInstance>;
309
333
  /**
310
334
  * `<w:numPicBullet>` media-catalog entries keyed by `w:numPicBulletId`.
311
335
  * Each entry preserves the raw drawing/pic XML plus a resolved media
@@ -313,7 +337,7 @@ export interface NumberingCatalog {
313
337
  * Consumed by numbering-prefix when `NumberingLevelDefinition.picBulletId`
314
338
  * is set (ECMA-376 §17.9.19). Rendering is Lane 6 territory.
315
339
  */
316
- numPicBullets?: Record<string, NumPicBullet>;
340
+ readonly numPicBullets?: Record<string, NumPicBullet>;
317
341
  }
318
342
 
319
343
  /**
@@ -326,101 +350,101 @@ export interface NumberingCatalog {
326
350
  */
327
351
  export interface NumPicBullet {
328
352
  /** `w:numPicBulletId` attribute value — the lookup key. */
329
- numPicBulletId: string;
353
+ readonly numPicBulletId: string;
330
354
  /** Verbatim XML for the `<w:numPicBullet>` element. */
331
- rawXml: string;
355
+ readonly rawXml: string;
332
356
  /**
333
357
  * Resolved media-catalog media id when the bullet's inner image references
334
358
  * a relationship we could resolve at parse time. Undefined when the
335
359
  * bullet uses an unresolved blipRef or a VML path.
336
360
  */
337
- mediaId?: string;
361
+ readonly mediaId?: string;
338
362
  /**
339
363
  * Bullet cell extent in EMU (English Metric Units) when parsed from the
340
364
  * drawing's `wp:extent` — lets the renderer size the bullet image without
341
365
  * round-tripping through a new measurement pass. Undefined for VML paths.
342
366
  */
343
- widthEmu?: number;
344
- heightEmu?: number;
367
+ readonly widthEmu?: number;
368
+ readonly heightEmu?: number;
345
369
  }
346
370
 
347
371
  export interface AbstractNumberingDefinition {
348
- abstractNumberingId: string;
349
- levels: NumberingLevelDefinition[];
350
- nsid?: string;
351
- multiLevelType?: "singleLevel" | "multilevel" | "hybridMultilevel";
372
+ readonly abstractNumberingId: string;
373
+ readonly levels: NumberingLevelDefinition[];
374
+ readonly nsid?: string;
375
+ readonly multiLevelType?: "singleLevel" | "multilevel" | "hybridMultilevel";
352
376
  /** ECMA-376 17.9.26 `<w:tmpl>` — template code identifying the abstractNum's origin template (ST_LongHexNumber). Preserved for round-trip. */
353
- tplc?: string;
354
- styleLink?: string;
355
- numStyleLink?: string;
377
+ readonly tplc?: string;
378
+ readonly styleLink?: string;
379
+ readonly numStyleLink?: string;
356
380
  }
357
381
 
358
382
  export interface NumberingLevelParagraphGeometry {
359
- justification?: "left" | "center" | "right" | "both" | "distribute";
360
- spacing?: ParagraphSpacing;
361
- indentation?: ParagraphIndentation;
362
- tabStops?: TabStop[];
383
+ readonly justification?: "left" | "center" | "right" | "both" | "distribute";
384
+ readonly spacing?: ParagraphSpacing;
385
+ readonly indentation?: ParagraphIndentation;
386
+ readonly tabStops?: TabStop[];
363
387
  }
364
388
 
365
389
  export interface NumberingLevelDefinition {
366
- level: number;
367
- format: string;
368
- text: string;
369
- startAt?: number;
370
- paragraphStyleId?: string;
371
- isLegalNumbering?: boolean;
372
- suffix?: "tab" | "space" | "nothing";
373
- paragraphGeometry?: NumberingLevelParagraphGeometry;
374
- runProperties?: CanonicalRunFormatting;
375
- restartAfterLevel?: number;
390
+ readonly level: number;
391
+ readonly format: string;
392
+ readonly text: string;
393
+ readonly startAt?: number;
394
+ readonly paragraphStyleId?: string;
395
+ readonly isLegalNumbering?: boolean;
396
+ readonly suffix?: "tab" | "space" | "nothing";
397
+ readonly paragraphGeometry?: NumberingLevelParagraphGeometry;
398
+ readonly runProperties?: CanonicalRunFormatting;
399
+ readonly restartAfterLevel?: number;
376
400
  /**
377
401
  * `<w:lvlPicBulletId w:val="N"/>` reference — picks the matching entry
378
402
  * from `NumberingCatalog.numPicBullets`. When set, the marker renders as
379
403
  * an inline image instead of a text glyph. ECMA-376 §17.9.12.
380
404
  */
381
- picBulletId?: string;
405
+ readonly picBulletId?: string;
382
406
  }
383
407
 
384
408
  export interface NumberingLevelOverrideDefinition {
385
- level: number;
386
- format?: string;
387
- text?: string;
388
- startAt?: number;
389
- paragraphStyleId?: string;
390
- isLegalNumbering?: boolean;
391
- suffix?: "tab" | "space" | "nothing";
392
- paragraphGeometry?: NumberingLevelParagraphGeometry;
393
- runProperties?: CanonicalRunFormatting;
394
- restartAfterLevel?: number;
395
- picBulletId?: string;
409
+ readonly level: number;
410
+ readonly format?: string;
411
+ readonly text?: string;
412
+ readonly startAt?: number;
413
+ readonly paragraphStyleId?: string;
414
+ readonly isLegalNumbering?: boolean;
415
+ readonly suffix?: "tab" | "space" | "nothing";
416
+ readonly paragraphGeometry?: NumberingLevelParagraphGeometry;
417
+ readonly runProperties?: CanonicalRunFormatting;
418
+ readonly restartAfterLevel?: number;
419
+ readonly picBulletId?: string;
396
420
  }
397
421
 
398
422
  export interface NumberingInstance {
399
- numberingInstanceId: string;
400
- abstractNumberingId: string;
401
- overrides: NumberingLevelOverride[];
423
+ readonly numberingInstanceId: string;
424
+ readonly abstractNumberingId: string;
425
+ readonly overrides: NumberingLevelOverride[];
402
426
  }
403
427
 
404
428
  export interface NumberingLevelOverride {
405
- level: number;
406
- startAt?: number;
407
- levelDefinition?: NumberingLevelOverrideDefinition;
429
+ readonly level: number;
430
+ readonly startAt?: number;
431
+ readonly levelDefinition?: NumberingLevelOverrideDefinition;
408
432
  }
409
433
 
410
434
  export interface MediaCatalog {
411
- items: Record<string, MediaItem>;
435
+ readonly items: Record<string, MediaItem>;
412
436
  }
413
437
 
414
438
  export interface MediaItem {
415
- mediaId: string;
416
- contentType: string;
417
- filename: string;
418
- relationshipId?: string;
419
- packagePartName: string;
420
- altText?: string;
421
- display?: "inline" | "floating";
422
- widthEmu?: number;
423
- heightEmu?: number;
439
+ readonly mediaId: string;
440
+ readonly contentType: string;
441
+ readonly filename: string;
442
+ readonly relationshipId?: string;
443
+ readonly packagePartName: string;
444
+ readonly altText?: string;
445
+ readonly display?: "inline" | "floating";
446
+ readonly widthEmu?: number;
447
+ readonly heightEmu?: number;
424
448
  }
425
449
 
426
450
  // ---- Sub-part canonical types ----
@@ -428,25 +452,25 @@ export interface MediaItem {
428
452
  export type HeaderFooterVariant = "default" | "first" | "even";
429
453
 
430
454
  export interface HeaderDocument {
431
- variant: HeaderFooterVariant;
432
- partPath: string;
433
- relationshipId: string;
434
- blocks: BlockNode[];
435
- sectionIndex?: number;
455
+ readonly variant: HeaderFooterVariant;
456
+ readonly partPath: string;
457
+ readonly relationshipId: string;
458
+ readonly blocks: BlockNode[];
459
+ readonly sectionIndex?: number;
436
460
  }
437
461
 
438
462
  export interface FooterDocument {
439
- variant: HeaderFooterVariant;
440
- partPath: string;
441
- relationshipId: string;
442
- blocks: BlockNode[];
443
- sectionIndex?: number;
463
+ readonly variant: HeaderFooterVariant;
464
+ readonly partPath: string;
465
+ readonly relationshipId: string;
466
+ readonly blocks: BlockNode[];
467
+ readonly sectionIndex?: number;
444
468
  }
445
469
 
446
470
  export interface FootnoteDefinition {
447
- noteId: string;
448
- kind: "footnote" | "endnote";
449
- blocks: BlockNode[];
471
+ readonly noteId: string;
472
+ readonly kind: "footnote" | "endnote";
473
+ readonly blocks: BlockNode[];
450
474
  }
451
475
 
452
476
  /**
@@ -461,33 +485,33 @@ export interface FootnoteDefinition {
461
485
  */
462
486
  export interface FootnoteSeparators {
463
487
  /** Raw XML of the <w:r> children inside the separator paragraph. */
464
- separatorContent?: string;
488
+ readonly separatorContent?: string;
465
489
  /** Full XML of the first separator paragraph. */
466
- separatorParagraphXml?: string;
490
+ readonly separatorParagraphXml?: string;
467
491
  /** Raw XML of the <w:r> children inside the continuationSeparator paragraph. */
468
- continuationSeparatorContent?: string;
492
+ readonly continuationSeparatorContent?: string;
469
493
  /** Full XML of the first continuation-separator paragraph. */
470
- continuationSeparatorParagraphXml?: string;
494
+ readonly continuationSeparatorParagraphXml?: string;
471
495
  }
472
496
 
473
497
  export interface FootnoteCollection {
474
- footnotes: Record<string, FootnoteDefinition>;
475
- endnotes: Record<string, FootnoteDefinition>;
498
+ readonly footnotes: Record<string, FootnoteDefinition>;
499
+ readonly endnotes: Record<string, FootnoteDefinition>;
476
500
  /** Separator content parsed from footnotes.xml special entries. */
477
- footnoteSeparators?: FootnoteSeparators;
501
+ readonly footnoteSeparators?: FootnoteSeparators;
478
502
  /** Separator content parsed from endnotes.xml special entries. */
479
- endnoteSeparators?: FootnoteSeparators;
503
+ readonly endnoteSeparators?: FootnoteSeparators;
480
504
  }
481
505
 
482
506
  export interface ThemeColorScheme {
483
- name: string;
484
- colors: Record<string, string>;
507
+ readonly name: string;
508
+ readonly colors: Record<string, string>;
485
509
  }
486
510
 
487
511
  export interface ThemeFontScheme {
488
- name: string;
489
- majorFont?: string;
490
- minorFont?: string;
512
+ readonly name: string;
513
+ readonly majorFont?: string;
514
+ readonly minorFont?: string;
491
515
  }
492
516
 
493
517
  /**
@@ -519,9 +543,9 @@ export type ThemeFontSlot =
519
543
  | "minorBidi";
520
544
 
521
545
  export interface ThemeDefinition {
522
- name?: string;
523
- colorScheme?: ThemeColorScheme;
524
- fontScheme?: ThemeFontScheme;
546
+ readonly name?: string;
547
+ readonly colorScheme?: ThemeColorScheme;
548
+ readonly fontScheme?: ThemeFontScheme;
525
549
  }
526
550
 
527
551
  /**
@@ -533,49 +557,49 @@ export interface ThemeDefinition {
533
557
  * future serializer can re-emit byte-stable diffs.
534
558
  */
535
559
  export interface CompatSetting {
536
- name: string;
537
- uri: string;
538
- value: string;
560
+ readonly name: string;
561
+ readonly uri: string;
562
+ readonly value: string;
539
563
  }
540
564
 
541
565
  export interface DocumentSettings {
542
- evenAndOddHeaders?: boolean;
543
- zoomLevel?: "pageWidth" | "onePage" | number;
566
+ readonly evenAndOddHeaders?: boolean;
567
+ readonly zoomLevel?: "pageWidth" | "onePage" | number;
544
568
  /**
545
569
  * Document-wide default tab stop interval from `<w:defaultTabStop w:val>`.
546
570
  * Value is stored in twips and feeds layout measurement whenever a paragraph
547
571
  * does not declare an explicit next tab stop.
548
572
  */
549
- defaultTabStop?: number;
573
+ readonly defaultTabStop?: number;
550
574
  /**
551
575
  * Settings-level default footnote configuration from `<w:footnotePr>`.
552
576
  * Section-level `SectionProperties.footnotePr` overrides this when present.
553
577
  */
554
- footnotePr?: FootnoteProperties;
578
+ readonly footnotePr?: FootnoteProperties;
555
579
  /**
556
580
  * Settings-level default endnote configuration from `<w:endnotePr>`.
557
581
  * Section-level `SectionProperties.endnotePr` overrides this when present.
558
582
  */
559
- endnotePr?: EndnoteProperties;
583
+ readonly endnotePr?: EndnoteProperties;
560
584
  /**
561
585
  * Ordered list of <w:compatSetting> entries inside <w:compat>. Insertion
562
586
  * order is preserved for serializer diff stability.
563
587
  */
564
- compatSettings?: CompatSetting[];
588
+ readonly compatSettings?: CompatSetting[];
565
589
  /**
566
590
  * Boolean flag children of <w:compat> that are NOT <w:compatSetting>
567
591
  * (e.g. <w:spaceForUL/>, <w:doNotExpandShiftReturn/>). Keyed by local
568
592
  * element name. The value reflects ST_OnOff semantics: missing `w:val` is
569
593
  * true; explicit `w:val="0"`/`"false"` is false.
570
594
  */
571
- compatFlags?: Record<string, boolean>;
595
+ readonly compatFlags?: Record<string, boolean>;
572
596
  /**
573
597
  * Settings-level (NOT inside <w:compat>) compat-adjacent boolean flags
574
598
  * such as <w:doNotEmbedSmartTags/>. Kept in a separate field from
575
599
  * compatFlags because the OOXML location differs and the future
576
600
  * serializer must re-emit them at root, not inside <w:compat>.
577
601
  */
578
- rootCompatFlags?: Record<string, boolean>;
602
+ readonly rootCompatFlags?: Record<string, boolean>;
579
603
  /**
580
604
  * <w:themeFontLang> attribute bag, captured verbatim so the future
581
605
  * serializer can re-emit unknown attributes Word may add.
@@ -586,7 +610,7 @@ export interface DocumentSettings {
586
610
  * - { "w:val": "en-US", … }: attributes preserved with their qualified
587
611
  * names so the serializer round-trips namespace prefixes intact.
588
612
  */
589
- themeFontLang?: Record<string, string>;
613
+ readonly themeFontLang?: Record<string, string>;
590
614
  /**
591
615
  * Local names of every direct child of <w:settings> that this parser does
592
616
  * not model individually. Insertion order preserved; duplicates retained.
@@ -596,35 +620,35 @@ export interface DocumentSettings {
596
620
  * use this list as a validator assertion that every preserved child still
597
621
  * appears in the re-emitted output.
598
622
  */
599
- unmodelledSettingsChildren?: string[];
623
+ readonly unmodelledSettingsChildren?: string[];
600
624
  /**
601
625
  * Parsed from `<w:clrSchemeMapping>` in settings.xml. Maps document-level
602
626
  * color style slot names to physical clrScheme slots. Absent when the
603
627
  * element was not present in the document (identity mapping applies).
604
628
  */
605
- clrSchemeMapping?: ClrSchemeMapping;
629
+ readonly clrSchemeMapping?: ClrSchemeMapping;
606
630
  }
607
631
 
608
632
  export interface SubPartsCatalog {
609
- headers: HeaderDocument[];
610
- footers: FooterDocument[];
611
- footnoteCollection?: FootnoteCollection;
612
- theme?: ThemeDefinition;
613
- finalSectionProperties?: SectionProperties;
614
- resolvedTheme?: ResolvedTheme;
615
- settings?: DocumentSettings;
633
+ readonly headers: HeaderDocument[];
634
+ readonly footers: FooterDocument[];
635
+ readonly footnoteCollection?: FootnoteCollection;
636
+ readonly theme?: ThemeDefinition;
637
+ readonly finalSectionProperties?: SectionProperties;
638
+ readonly resolvedTheme?: ResolvedTheme;
639
+ readonly settings?: DocumentSettings;
616
640
  /**
617
641
  * Fully materialized theme for runtime resolution — combines theme1.xml
618
642
  * color/font scheme with the clrSchemeMapping from settings.xml.
619
643
  * Available after CO1 load wiring lands in docx-session.ts.
620
644
  */
621
- canonicalTheme?: CanonicalTheme;
645
+ readonly canonicalTheme?: CanonicalTheme;
622
646
  }
623
647
 
624
648
  export interface ResolvedTheme {
625
- colors: Record<string, string>;
626
- majorFont?: string;
627
- minorFont?: string;
649
+ readonly colors: Record<string, string>;
650
+ readonly majorFont?: string;
651
+ readonly minorFont?: string;
628
652
  }
629
653
 
630
654
  /**
@@ -666,22 +690,22 @@ export type ClrSchemeMapping = Partial<Record<ClrSchemeMappingSlot, ThemeColorSl
666
690
  * documents with no theme part.
667
691
  */
668
692
  export interface CanonicalTheme {
669
- clrScheme: ThemeColorScheme;
670
- fontScheme?: ThemeFontScheme;
693
+ readonly clrScheme: ThemeColorScheme;
694
+ readonly fontScheme?: ThemeFontScheme;
671
695
  /** Resolved clrSchemeMapping: styleSlot → clrScheme slot name. Empty = identity. */
672
- clrMap: ClrSchemeMapping;
696
+ readonly clrMap: ClrSchemeMapping;
673
697
  /** Stable content hash of clrScheme.colors (sorted key:value pairs). */
674
- themeHash: string;
698
+ readonly themeHash: string;
675
699
  /** Stable content hash of clrMap (sorted key:value pairs). */
676
- clrMapHash: string;
700
+ readonly clrMapHash: string;
677
701
  }
678
702
 
679
703
  // ---- Inline footnote reference node ----
680
704
 
681
705
  export interface FootnoteRefNode {
682
- type: "footnote_ref";
683
- noteId: string;
684
- noteKind: "footnote" | "endnote";
706
+ readonly type: "footnote_ref";
707
+ readonly noteId: string;
708
+ readonly noteKind: "footnote" | "endnote";
685
709
  }
686
710
 
687
711
  export type DocumentNode =
@@ -719,8 +743,8 @@ export type DocumentNode =
719
743
  | OleEmbedNode;
720
744
 
721
745
  export interface DocumentRootNode {
722
- type: "doc";
723
- children: BlockNode[];
746
+ readonly type: "doc";
747
+ readonly children: BlockNode[];
724
748
  }
725
749
 
726
750
  export type BlockNode =
@@ -733,74 +757,74 @@ export type BlockNode =
733
757
  | OpaqueBlockNode;
734
758
 
735
759
  export interface ParagraphSpacing {
736
- before?: number;
737
- after?: number;
738
- line?: number;
739
- lineRule?: "auto" | "exact" | "atLeast";
760
+ readonly before?: number;
761
+ readonly after?: number;
762
+ readonly line?: number;
763
+ readonly lineRule?: "auto" | "exact" | "atLeast";
740
764
  }
741
765
 
742
766
  export interface ParagraphIndentation {
743
- left?: number;
744
- right?: number;
745
- firstLine?: number;
746
- hanging?: number;
767
+ readonly left?: number;
768
+ readonly right?: number;
769
+ readonly firstLine?: number;
770
+ readonly hanging?: number;
747
771
  }
748
772
 
749
773
  export interface TabStop {
750
- position: number;
751
- align: "left" | "center" | "right" | "decimal" | "num" | "bar" | "clear";
752
- leader?: "none" | "dot" | "hyphen" | "underscore" | "heavy" | "middleDot";
774
+ readonly position: number;
775
+ readonly align: "left" | "center" | "right" | "decimal" | "num" | "bar" | "clear";
776
+ readonly leader?: "none" | "dot" | "hyphen" | "underscore" | "heavy" | "middleDot";
753
777
  }
754
778
 
755
779
  export interface ParagraphBorders {
756
- top?: BorderSpec;
757
- left?: BorderSpec;
758
- bottom?: BorderSpec;
759
- right?: BorderSpec;
760
- bar?: BorderSpec;
761
- between?: BorderSpec;
780
+ readonly top?: BorderSpec;
781
+ readonly left?: BorderSpec;
782
+ readonly bottom?: BorderSpec;
783
+ readonly right?: BorderSpec;
784
+ readonly bar?: BorderSpec;
785
+ readonly between?: BorderSpec;
762
786
  }
763
787
 
764
788
  export interface ParagraphShading {
765
- fill?: string;
766
- color?: string;
767
- val?: string;
789
+ readonly fill?: string;
790
+ readonly color?: string;
791
+ readonly val?: string;
768
792
  /**
769
793
  * Theme shading references (§17.3.5). When `themeFill` is set and `fill`
770
794
  * is absent or `"auto"`, render-time shading resolves through the theme
771
795
  * color resolver. The raw theme attrs remain on the canonical model so
772
796
  * export can round-trip the original `<w:shd>` byte-for-byte.
773
797
  */
774
- themeFill?: string;
775
- themeFillTint?: string;
776
- themeFillShade?: string;
777
- themeColor?: string;
778
- themeColorTint?: string;
779
- themeColorShade?: string;
798
+ readonly themeFill?: string;
799
+ readonly themeFillTint?: string;
800
+ readonly themeFillShade?: string;
801
+ readonly themeColor?: string;
802
+ readonly themeColorTint?: string;
803
+ readonly themeColorShade?: string;
780
804
  }
781
805
 
782
806
  /** Body of an OOXML `<w:rPr>` (run properties). All fields optional; absence = "not specified at this level". */
783
807
  export interface CanonicalRunFormatting {
784
- bold?: boolean;
785
- italic?: boolean;
786
- underline?: "single" | "double" | "thick" | "dotted" | "dash" | "wave" | "none";
787
- strikethrough?: boolean;
788
- doubleStrikethrough?: boolean;
789
- vanish?: boolean;
790
- allCaps?: boolean;
791
- smallCaps?: boolean;
792
- verticalAlign?: "baseline" | "superscript" | "subscript";
808
+ readonly bold?: boolean;
809
+ readonly italic?: boolean;
810
+ readonly underline?: "single" | "double" | "thick" | "dotted" | "dash" | "wave" | "none";
811
+ readonly strikethrough?: boolean;
812
+ readonly doubleStrikethrough?: boolean;
813
+ readonly vanish?: boolean;
814
+ readonly allCaps?: boolean;
815
+ readonly smallCaps?: boolean;
816
+ readonly verticalAlign?: "baseline" | "superscript" | "subscript";
793
817
  /**
794
818
  * Convenience alias for the primary font family — the first non-empty of
795
819
  * `fontFamilyAscii` → `fontFamilyHAnsi` → `fontFamilyEastAsia` → `fontFamilyCs`.
796
820
  * Script-aware consumers should read the specific `fontFamily{Ascii,HAnsi,EastAsia,Cs}`
797
821
  * fields directly.
798
822
  */
799
- fontFamily?: string;
800
- fontFamilyAscii?: string;
801
- fontFamilyHAnsi?: string;
802
- fontFamilyEastAsia?: string;
803
- fontFamilyCs?: string;
823
+ readonly fontFamily?: string;
824
+ readonly fontFamilyAscii?: string;
825
+ readonly fontFamilyHAnsi?: string;
826
+ readonly fontFamilyEastAsia?: string;
827
+ readonly fontFamilyCs?: string;
804
828
  /**
805
829
  * ECMA-376 §17.3.2.26 theme-slot bindings on `<w:rFonts>`
806
830
  * (`w:asciiTheme`, `w:hAnsiTheme`, `w:eastAsiaTheme`, `w:cstheme`).
@@ -815,18 +839,18 @@ export interface CanonicalRunFormatting {
815
839
  * concrete name wins. Keep the theme slot on the canonical so the
816
840
  * export path can re-emit it byte-identically.
817
841
  */
818
- asciiTheme?: ThemeFontSlot;
819
- hAnsiTheme?: ThemeFontSlot;
820
- eastAsiaTheme?: ThemeFontSlot;
821
- csTheme?: ThemeFontSlot;
822
- fontSizeHalfPoints?: number;
823
- fontSizeCsHalfPoints?: number;
842
+ readonly asciiTheme?: ThemeFontSlot;
843
+ readonly hAnsiTheme?: ThemeFontSlot;
844
+ readonly eastAsiaTheme?: ThemeFontSlot;
845
+ readonly csTheme?: ThemeFontSlot;
846
+ readonly fontSizeHalfPoints?: number;
847
+ readonly fontSizeCsHalfPoints?: number;
824
848
  /**
825
849
  * Color value from `<w:color w:val>`. Either an OOXML hex (e.g., `"2E74B5"`)
826
850
  * or the sentinel `"auto"` (which serializers must round-trip verbatim).
827
851
  */
828
- colorHex?: string;
829
- colorThemeSlot?: string;
852
+ readonly colorHex?: string;
853
+ readonly colorThemeSlot?: string;
830
854
  /**
831
855
  * `w:themeTint` hex byte (0x00–0xFF) read from `<w:color>`. ECMA-376
832
856
  * §17.18.85. Applied at render/cascade time against `colorThemeSlot`:
@@ -835,17 +859,17 @@ export interface CanonicalRunFormatting {
835
859
  * for byte-stable round-trip; resolution math lives in the runtime
836
860
  * theme-color resolver.
837
861
  */
838
- colorThemeTint?: string;
862
+ readonly colorThemeTint?: string;
839
863
  /**
840
864
  * `w:themeShade` hex byte (0x00–0xFF) from `<w:color>`. ECMA-376
841
865
  * §17.18.83. Applied at render/cascade time: luminance mod =
842
866
  * shade/255 * L (darkens toward black; 0xFF means no modulation).
843
867
  */
844
- colorThemeShade?: string;
845
- highlight?: string;
846
- characterSpacingTwips?: number;
847
- characterStyleId?: string;
848
- languageCode?: string;
868
+ readonly colorThemeShade?: string;
869
+ readonly highlight?: string;
870
+ readonly characterSpacingTwips?: number;
871
+ readonly characterStyleId?: string;
872
+ readonly languageCode?: string;
849
873
  /**
850
874
  * Unmodelled direct children of `<w:rPr>` captured verbatim for round-trip.
851
875
  * See `src/io/ooxml/property-grab-bag.ts` and
@@ -854,27 +878,27 @@ export interface CanonicalRunFormatting {
854
878
  * `<w:em>`, `<w:kern>` through parse→serialize round-trip even though the
855
879
  * runtime does not model them.
856
880
  */
857
- unknownPropertyChildren?: UnknownPropertyChild[];
881
+ readonly unknownPropertyChildren?: UnknownPropertyChild[];
858
882
  }
859
883
 
860
884
  /** Body of an OOXML `<w:pPr>` (paragraph properties). All fields optional; absence = "not specified at this level". */
861
885
  export interface CanonicalParagraphFormatting {
862
- spacing?: ParagraphSpacing;
863
- indentation?: ParagraphIndentation;
864
- alignment?: "left" | "center" | "right" | "both" | "distribute" | "start" | "end";
865
- borders?: ParagraphBorders;
866
- shading?: ParagraphShading;
867
- tabStops?: TabStop[];
868
- contextualSpacing?: boolean;
869
- keepNext?: boolean;
870
- keepLines?: boolean;
871
- widowControl?: boolean;
872
- pageBreakBefore?: boolean;
873
- outlineLevel?: number;
874
- bidi?: boolean;
875
- suppressLineNumbers?: boolean;
876
- suppressAutoHyphens?: boolean;
877
- paragraphMarkRunProperties?: CanonicalRunFormatting;
886
+ readonly spacing?: ParagraphSpacing;
887
+ readonly indentation?: ParagraphIndentation;
888
+ readonly alignment?: "left" | "center" | "right" | "both" | "distribute" | "start" | "end";
889
+ readonly borders?: ParagraphBorders;
890
+ readonly shading?: ParagraphShading;
891
+ readonly tabStops?: TabStop[];
892
+ readonly contextualSpacing?: boolean;
893
+ readonly keepNext?: boolean;
894
+ readonly keepLines?: boolean;
895
+ readonly widowControl?: boolean;
896
+ readonly pageBreakBefore?: boolean;
897
+ readonly outlineLevel?: number;
898
+ readonly bidi?: boolean;
899
+ readonly suppressLineNumbers?: boolean;
900
+ readonly suppressAutoHyphens?: boolean;
901
+ readonly paragraphMarkRunProperties?: CanonicalRunFormatting;
878
902
  /**
879
903
  * `<w:framePr>` — paragraph text-frame properties (ECMA-376 §17.3.1.11).
880
904
  * Absent on ordinary paragraphs. When present, the paragraph renders
@@ -888,7 +912,7 @@ export interface CanonicalParagraphFormatting {
888
912
  * `<w:framePr>`, which hid the paragraph from L04 pagination and L11
889
913
  * render. Canonical representation unblocks downstream adoption.
890
914
  */
891
- frameProperties?: FrameProperties;
915
+ readonly frameProperties?: FrameProperties;
892
916
  /**
893
917
  * Unmodelled direct children of `<w:pPr>` captured verbatim for round-trip.
894
918
  * See `src/io/ooxml/property-grab-bag.ts` for the mechanism and Lane 3 O2
@@ -900,7 +924,7 @@ export interface CanonicalParagraphFormatting {
900
924
  * or `<w:kinsoku>` survive a parse→serialize round-trip even though the
901
925
  * runtime doesn't understand them.
902
926
  */
903
- unknownPropertyChildren?: UnknownPropertyChild[];
927
+ readonly unknownPropertyChildren?: UnknownPropertyChild[];
904
928
  }
905
929
 
906
930
  /**
@@ -915,57 +939,57 @@ export interface CanonicalParagraphFormatting {
915
939
  */
916
940
  export interface FrameProperties {
917
941
  /** `w:w` — frame width in twips. Absence = auto-width from content. */
918
- widthTwips?: number;
942
+ readonly widthTwips?: number;
919
943
  /** `w:h` — frame height in twips. Interpretation depends on `hRule`. */
920
- heightTwips?: number;
944
+ readonly heightTwips?: number;
921
945
  /**
922
946
  * `w:hRule` — height rule.
923
947
  * - `"auto"` (default): height follows content.
924
948
  * - `"atLeast"`: content grows the frame; `heightTwips` is the floor.
925
949
  * - `"exact"`: content is clipped to `heightTwips`.
926
950
  */
927
- hRule?: "auto" | "atLeast" | "exact";
951
+ readonly hRule?: "auto" | "atLeast" | "exact";
928
952
  /** `w:x` — horizontal absolute position in twips from `hAnchor`. */
929
- xTwips?: number;
953
+ readonly xTwips?: number;
930
954
  /** `w:y` — vertical absolute position in twips from `vAnchor`. */
931
- yTwips?: number;
955
+ readonly yTwips?: number;
932
956
  /**
933
957
  * `w:xAlign` — horizontal alignment keyword; overrides `xTwips` if
934
958
  * both are set (Word behavior). `"inside"` / `"outside"` are
935
959
  * mirror-aware for even/odd pages.
936
960
  */
937
- xAlign?: "left" | "center" | "right" | "inside" | "outside";
961
+ readonly xAlign?: "left" | "center" | "right" | "inside" | "outside";
938
962
  /**
939
963
  * `w:yAlign` — vertical alignment keyword; overrides `yTwips` if
940
964
  * both are set. `"inline"` places the frame in the text flow.
941
965
  */
942
- yAlign?: "top" | "center" | "bottom" | "inside" | "outside" | "inline";
966
+ readonly yAlign?: "top" | "center" | "bottom" | "inside" | "outside" | "inline";
943
967
  /** `w:hAnchor` — what `x` / `xAlign` is measured from. */
944
- hAnchor?: "text" | "margin" | "page";
968
+ readonly hAnchor?: "text" | "margin" | "page";
945
969
  /** `w:vAnchor` — what `y` / `yAlign` is measured from. */
946
- vAnchor?: "text" | "margin" | "page";
970
+ readonly vAnchor?: "text" | "margin" | "page";
947
971
  /**
948
972
  * `w:wrap` — how surrounding text flows around the frame.
949
973
  * `"around"`: text wraps on both sides; `"notBeside"`: text stops
950
974
  * above/below the frame; `"none"`: no text on the frame line;
951
975
  * `"auto"` / `"tight"` / `"through"`: extended wrap modes.
952
976
  */
953
- wrap?: "around" | "auto" | "none" | "notBeside" | "tight" | "through";
977
+ readonly wrap?: "around" | "auto" | "none" | "notBeside" | "tight" | "through";
954
978
  /** `w:hSpace` — horizontal clear-space around the frame, twips. */
955
- hSpaceTwips?: number;
979
+ readonly hSpaceTwips?: number;
956
980
  /** `w:vSpace` — vertical clear-space around the frame, twips. */
957
- vSpaceTwips?: number;
981
+ readonly vSpaceTwips?: number;
958
982
  /**
959
983
  * `w:dropCap` — drop-cap semantics when the frame holds an initial
960
984
  * letter. `"none"` (default): not a drop-cap frame.
961
985
  * `"drop"`: character spans `lines` lines, text wraps around it.
962
986
  * `"margin"`: character hangs in the margin.
963
987
  */
964
- dropCap?: "none" | "drop" | "margin";
988
+ readonly dropCap?: "none" | "drop" | "margin";
965
989
  /** `w:lines` — number of lines the drop-cap spans. Only meaningful when `dropCap !== "none"`. */
966
- lines?: number;
990
+ readonly lines?: number;
967
991
  /** `w:anchorLock` — prevents the frame's anchor paragraph from moving between pages during reflow. */
968
- anchorLock?: boolean;
992
+ readonly anchorLock?: boolean;
969
993
  /**
970
994
  * Verbatim source XML of the `<w:framePr>` element. Populated by
971
995
  * the parser for lossless round-trip of extension attributes
@@ -974,7 +998,7 @@ export interface FrameProperties {
974
998
  * modeled fields and merge in any extension attributes from
975
999
  * `rawXml` that aren't covered by the modeled set.
976
1000
  */
977
- rawXml?: string;
1001
+ readonly rawXml?: string;
978
1002
  }
979
1003
 
980
1004
  /**
@@ -986,65 +1010,65 @@ export interface FrameProperties {
986
1010
  */
987
1011
  export interface UnknownPropertyChild {
988
1012
  /** Qualified element name as it appeared in source, e.g. "w15:collapsed". */
989
- elementName: string;
1013
+ readonly elementName: string;
990
1014
  /** Verbatim source XML for the child element, including closing/self-closing form. */
991
- rawXml: string;
1015
+ readonly rawXml: string;
992
1016
  }
993
1017
 
994
1018
  /** Body of an OOXML `<w:docDefaults>` — baseline formatting applied before style chain. */
995
1019
  export interface DocumentDefaults {
996
- paragraph?: CanonicalParagraphFormatting;
997
- run?: CanonicalRunFormatting;
1020
+ readonly paragraph?: CanonicalParagraphFormatting;
1021
+ readonly run?: CanonicalRunFormatting;
998
1022
  }
999
1023
 
1000
1024
  /** Materialized `fontTable.xml` — one entry per `<w:font>` element. */
1001
1025
  export interface CanonicalFontTable {
1002
- fonts: Record<string, CanonicalFontEntry>;
1026
+ readonly fonts: Record<string, CanonicalFontEntry>;
1003
1027
  }
1004
1028
 
1005
1029
  /** Metadata for a single `<w:font>` element from fontTable.xml. */
1006
1030
  export interface CanonicalFontEntry {
1007
1031
  /** `w:name` attribute — the key used in rFonts references. */
1008
- name: string;
1032
+ readonly name: string;
1009
1033
  /**
1010
1034
  * `w:family` value. Qualitative category per ECMA-376 §17.8.3.10.
1011
1035
  * "roman" = proportional serif, "swiss" = proportional sans-serif,
1012
1036
  * "modern" = fixed-pitch, "script" = handwriting, "decorative" = display.
1013
1037
  */
1014
- family?: "roman" | "swiss" | "modern" | "script" | "decorative";
1038
+ readonly family?: "roman" | "swiss" | "modern" | "script" | "decorative";
1015
1039
  /** `w:pitch` value — "fixed" maps to monospace metrics. */
1016
- pitch?: "fixed" | "variable" | "default";
1040
+ readonly pitch?: "fixed" | "variable" | "default";
1017
1041
  /**
1018
1042
  * Windows charset code from `w:charset w:val`.
1019
1043
  * 2 = Symbol font (charset override required for correct glyph mapping).
1020
1044
  */
1021
- charset?: number;
1045
+ readonly charset?: number;
1022
1046
  /** `w:altName` — substitution name when the primary font is unavailable. */
1023
- altName?: string;
1047
+ readonly altName?: string;
1024
1048
  }
1025
1049
 
1026
1050
  export interface ParagraphNode {
1027
- type: "paragraph";
1028
- styleId?: string;
1051
+ readonly type: "paragraph";
1052
+ readonly styleId?: string;
1029
1053
  numbering?: {
1030
- numberingInstanceId: string;
1031
- level: number;
1054
+ readonly numberingInstanceId: string;
1055
+ readonly level: number;
1032
1056
  };
1033
- alignment?: "left" | "center" | "right" | "both" | "distribute";
1034
- spacing?: ParagraphSpacing;
1035
- contextualSpacing?: boolean;
1036
- indentation?: ParagraphIndentation;
1037
- tabStops?: TabStop[];
1038
- keepNext?: boolean;
1039
- keepLines?: boolean;
1040
- outlineLevel?: number;
1041
- pageBreakBefore?: boolean;
1042
- widowControl?: boolean;
1043
- borders?: ParagraphBorders;
1044
- shading?: ParagraphShading;
1045
- bidi?: boolean;
1046
- suppressLineNumbers?: boolean;
1047
- cnfStyle?: string;
1057
+ readonly alignment?: "left" | "center" | "right" | "both" | "distribute";
1058
+ readonly spacing?: ParagraphSpacing;
1059
+ readonly contextualSpacing?: boolean;
1060
+ readonly indentation?: ParagraphIndentation;
1061
+ readonly tabStops?: TabStop[];
1062
+ readonly keepNext?: boolean;
1063
+ readonly keepLines?: boolean;
1064
+ readonly outlineLevel?: number;
1065
+ readonly pageBreakBefore?: boolean;
1066
+ readonly widowControl?: boolean;
1067
+ readonly borders?: ParagraphBorders;
1068
+ readonly shading?: ParagraphShading;
1069
+ readonly bidi?: boolean;
1070
+ readonly suppressLineNumbers?: boolean;
1071
+ readonly cnfStyle?: string;
1048
1072
  /**
1049
1073
  * `<w:framePr>` — inline paragraph text-frame properties set
1050
1074
  * directly on the paragraph (not through the style cascade).
@@ -1062,7 +1086,7 @@ export interface ParagraphNode {
1062
1086
  * to `OpaqueBlockNode` because the parser had no canonical target
1063
1087
  * at the node level.
1064
1088
  */
1065
- frameProperties?: FrameProperties;
1089
+ readonly frameProperties?: FrameProperties;
1066
1090
  /**
1067
1091
  * Preserved w14 extension identifiers for this paragraph.
1068
1092
  * Round-trip (§2 A.7) requires these to survive import → export so the
@@ -1071,65 +1095,65 @@ export interface ParagraphNode {
1071
1095
  * 8-hex uppercase strings per ECMA-376 Part 1 Appendix A.
1072
1096
  */
1073
1097
  wordExtensionIds?: {
1074
- paraId?: string;
1075
- textId?: string;
1098
+ readonly paraId?: string;
1099
+ readonly textId?: string;
1076
1100
  };
1077
- children: InlineNode[];
1101
+ readonly children: InlineNode[];
1078
1102
  }
1079
1103
 
1080
1104
  export interface BorderSpec {
1081
- value?: string;
1082
- size?: number;
1083
- space?: number;
1084
- color?: string;
1105
+ readonly value?: string;
1106
+ readonly size?: number;
1107
+ readonly space?: number;
1108
+ readonly color?: string;
1085
1109
  }
1086
1110
 
1087
1111
  export interface TableBorders {
1088
- top?: BorderSpec;
1089
- left?: BorderSpec;
1090
- bottom?: BorderSpec;
1091
- right?: BorderSpec;
1092
- insideH?: BorderSpec;
1093
- insideV?: BorderSpec;
1112
+ readonly top?: BorderSpec;
1113
+ readonly left?: BorderSpec;
1114
+ readonly bottom?: BorderSpec;
1115
+ readonly right?: BorderSpec;
1116
+ readonly insideH?: BorderSpec;
1117
+ readonly insideV?: BorderSpec;
1094
1118
  }
1095
1119
 
1096
1120
  export interface TableCellBorders {
1097
- top?: BorderSpec;
1098
- left?: BorderSpec;
1099
- bottom?: BorderSpec;
1100
- right?: BorderSpec;
1101
- insideH?: BorderSpec;
1102
- insideV?: BorderSpec;
1121
+ readonly top?: BorderSpec;
1122
+ readonly left?: BorderSpec;
1123
+ readonly bottom?: BorderSpec;
1124
+ readonly right?: BorderSpec;
1125
+ readonly insideH?: BorderSpec;
1126
+ readonly insideV?: BorderSpec;
1103
1127
  }
1104
1128
 
1105
1129
  export interface TableWidth {
1106
- value: number;
1107
- type: "dxa" | "auto" | "pct" | "nil";
1130
+ readonly value: number;
1131
+ readonly type: "dxa" | "auto" | "pct" | "nil";
1108
1132
  }
1109
1133
 
1110
1134
  export interface TableIndent {
1111
- value: number;
1112
- type: "dxa" | "auto" | "pct" | "nil";
1135
+ readonly value: number;
1136
+ readonly type: "dxa" | "auto" | "pct" | "nil";
1113
1137
  }
1114
1138
 
1115
1139
  export interface TableFloatingProperties {
1116
- horizontalAnchor?: "margin" | "page" | "text";
1117
- verticalAnchor?: "margin" | "page" | "text";
1118
- horizontalAlign?: "left" | "center" | "right" | "inside" | "outside";
1119
- horizontalOffset?: number;
1120
- verticalAlign?: "top" | "center" | "bottom" | "inside" | "outside";
1121
- verticalOffset?: number;
1122
- leftFromText?: number;
1123
- rightFromText?: number;
1124
- topFromText?: number;
1125
- bottomFromText?: number;
1126
- overlap?: boolean;
1140
+ readonly horizontalAnchor?: "margin" | "page" | "text";
1141
+ readonly verticalAnchor?: "margin" | "page" | "text";
1142
+ readonly horizontalAlign?: "left" | "center" | "right" | "inside" | "outside";
1143
+ readonly horizontalOffset?: number;
1144
+ readonly verticalAlign?: "top" | "center" | "bottom" | "inside" | "outside";
1145
+ readonly verticalOffset?: number;
1146
+ readonly leftFromText?: number;
1147
+ readonly rightFromText?: number;
1148
+ readonly topFromText?: number;
1149
+ readonly bottomFromText?: number;
1150
+ readonly overlap?: boolean;
1127
1151
  }
1128
1152
 
1129
1153
  export interface CellShading {
1130
- fill?: string;
1131
- color?: string;
1132
- val?: string;
1154
+ readonly fill?: string;
1155
+ readonly color?: string;
1156
+ readonly val?: string;
1133
1157
  /**
1134
1158
  * SOW gap G3 — theme-shading references (§17.3.5). When `themeFill` is set
1135
1159
  * and `fill` is absent or "auto", the runtime resolves the shading via
@@ -1137,29 +1161,29 @@ export interface CellShading {
1137
1161
  * themeFillShade)`. Fields are kept verbatim so export can round-trip the
1138
1162
  * original `w:shd` element without re-encoding the resolved hex.
1139
1163
  */
1140
- themeFill?: string;
1141
- themeFillTint?: string;
1142
- themeFillShade?: string;
1143
- themeColor?: string;
1144
- themeColorTint?: string;
1145
- themeColorShade?: string;
1164
+ readonly themeFill?: string;
1165
+ readonly themeFillTint?: string;
1166
+ readonly themeFillShade?: string;
1167
+ readonly themeColor?: string;
1168
+ readonly themeColorTint?: string;
1169
+ readonly themeColorShade?: string;
1146
1170
  }
1147
1171
 
1148
1172
  export interface TableCellMargins {
1149
- top?: number;
1150
- left?: number;
1151
- bottom?: number;
1152
- right?: number;
1173
+ readonly top?: number;
1174
+ readonly left?: number;
1175
+ readonly bottom?: number;
1176
+ readonly right?: number;
1153
1177
  }
1154
1178
 
1155
1179
  export interface TableLook {
1156
- val?: string;
1157
- firstRow?: boolean;
1158
- lastRow?: boolean;
1159
- firstColumn?: boolean;
1160
- lastColumn?: boolean;
1161
- noHBand?: boolean;
1162
- noVBand?: boolean;
1180
+ readonly val?: string;
1181
+ readonly firstRow?: boolean;
1182
+ readonly lastRow?: boolean;
1183
+ readonly firstColumn?: boolean;
1184
+ readonly lastColumn?: boolean;
1185
+ readonly noHBand?: boolean;
1186
+ readonly noVBand?: boolean;
1163
1187
  }
1164
1188
 
1165
1189
  export type TableStyleConditionalRegion =
@@ -1174,140 +1198,140 @@ export type TableStyleConditionalRegion =
1174
1198
 
1175
1199
  export interface TableStyleFormatting {
1176
1200
  table?: {
1177
- width?: TableWidth;
1178
- alignment?: "left" | "center" | "right";
1179
- borders?: TableBorders;
1180
- cellMargins?: TableCellMargins;
1181
- tblLook?: TableLook;
1201
+ readonly width?: TableWidth;
1202
+ readonly alignment?: "left" | "center" | "right";
1203
+ readonly borders?: TableBorders;
1204
+ readonly cellMargins?: TableCellMargins;
1205
+ readonly tblLook?: TableLook;
1182
1206
  };
1183
1207
  row?: {
1184
- height?: number;
1185
- heightRule?: "auto" | "atLeast" | "exact";
1186
- isHeader?: boolean;
1208
+ readonly height?: number;
1209
+ readonly heightRule?: "auto" | "atLeast" | "exact";
1210
+ readonly isHeader?: boolean;
1187
1211
  };
1188
1212
  cell?: {
1189
- width?: TableWidth;
1190
- borders?: TableCellBorders;
1191
- shading?: CellShading;
1192
- verticalAlign?: "top" | "center" | "bottom";
1213
+ readonly width?: TableWidth;
1214
+ readonly borders?: TableCellBorders;
1215
+ readonly shading?: CellShading;
1216
+ readonly verticalAlign?: "top" | "center" | "bottom";
1193
1217
  };
1194
1218
  /**
1195
1219
  * `<w:pPr>` directly inside the table style body — applies to every
1196
1220
  * cell paragraph by default. Conditional-region pPr (inside
1197
1221
  * `<w:tblStylePr>`) is not captured here; see Lane 3a follow-up.
1198
1222
  */
1199
- paragraphProperties?: CanonicalParagraphFormatting;
1223
+ readonly paragraphProperties?: CanonicalParagraphFormatting;
1200
1224
  /**
1201
1225
  * `<w:rPr>` directly inside the table style body — applies to every
1202
1226
  * run in every cell paragraph by default.
1203
1227
  */
1204
- runProperties?: CanonicalRunFormatting;
1228
+ readonly runProperties?: CanonicalRunFormatting;
1205
1229
  }
1206
1230
 
1207
1231
  export interface TableNode {
1208
- type: "table";
1209
- styleId?: string;
1232
+ readonly type: "table";
1233
+ readonly styleId?: string;
1210
1234
  /** @deprecated Use `unknownPropertyChildren`. Kept for snapshot back-compat. */
1211
- propertiesXml?: string;
1212
- unknownPropertyChildren?: UnknownPropertyChild[];
1213
- gridColumns: number[];
1214
- rows: TableRowNode[];
1215
- width?: TableWidth;
1216
- alignment?: "left" | "center" | "right";
1217
- borders?: TableBorders;
1218
- cellMargins?: TableCellMargins;
1219
- tblLook?: TableLook;
1220
- indent?: TableIndent;
1221
- layoutMode?: "fixed" | "autofit";
1222
- cellSpacing?: TableWidth;
1223
- caption?: string;
1224
- description?: string;
1225
- bidiVisual?: boolean;
1226
- floating?: TableFloatingProperties;
1235
+ readonly propertiesXml?: string;
1236
+ readonly unknownPropertyChildren?: UnknownPropertyChild[];
1237
+ readonly gridColumns: number[];
1238
+ readonly rows: TableRowNode[];
1239
+ readonly width?: TableWidth;
1240
+ readonly alignment?: "left" | "center" | "right";
1241
+ readonly borders?: TableBorders;
1242
+ readonly cellMargins?: TableCellMargins;
1243
+ readonly tblLook?: TableLook;
1244
+ readonly indent?: TableIndent;
1245
+ readonly layoutMode?: "fixed" | "autofit";
1246
+ readonly cellSpacing?: TableWidth;
1247
+ readonly caption?: string;
1248
+ readonly description?: string;
1249
+ readonly bidiVisual?: boolean;
1250
+ readonly floating?: TableFloatingProperties;
1227
1251
  }
1228
1252
 
1229
1253
  export interface TableRowNode {
1230
- type: "table_row";
1254
+ readonly type: "table_row";
1231
1255
  /** @deprecated Use `unknownPropertyChildren`. Kept for snapshot back-compat. */
1232
- propertiesXml?: string;
1233
- unknownPropertyChildren?: UnknownPropertyChild[];
1234
- cells: TableCellNode[];
1235
- gridBefore?: number;
1236
- widthBefore?: TableWidth;
1237
- gridAfter?: number;
1238
- widthAfter?: TableWidth;
1239
- height?: number;
1240
- heightRule?: "auto" | "atLeast" | "exact";
1241
- isHeader?: boolean;
1242
- cantSplit?: boolean;
1243
- horizontalAlignment?: "left" | "center" | "right";
1244
- cnfStyle?: string;
1256
+ readonly propertiesXml?: string;
1257
+ readonly unknownPropertyChildren?: UnknownPropertyChild[];
1258
+ readonly cells: TableCellNode[];
1259
+ readonly gridBefore?: number;
1260
+ readonly widthBefore?: TableWidth;
1261
+ readonly gridAfter?: number;
1262
+ readonly widthAfter?: TableWidth;
1263
+ readonly height?: number;
1264
+ readonly heightRule?: "auto" | "atLeast" | "exact";
1265
+ readonly isHeader?: boolean;
1266
+ readonly cantSplit?: boolean;
1267
+ readonly horizontalAlignment?: "left" | "center" | "right";
1268
+ readonly cnfStyle?: string;
1245
1269
  }
1246
1270
 
1247
1271
  export interface TableCellNode {
1248
- type: "table_cell";
1272
+ readonly type: "table_cell";
1249
1273
  /** @deprecated Use `unknownPropertyChildren`. Kept for snapshot back-compat. */
1250
- propertiesXml?: string;
1251
- unknownPropertyChildren?: UnknownPropertyChild[];
1252
- gridSpan?: number;
1253
- verticalMerge?: "restart" | "continue";
1254
- children: BlockNode[];
1255
- width?: TableWidth;
1256
- borders?: TableCellBorders;
1257
- shading?: CellShading;
1258
- verticalAlign?: "top" | "center" | "bottom";
1259
- textDirection?: "lrTb" | "tbRl" | "btLr";
1260
- noWrap?: boolean;
1261
- fitText?: boolean;
1262
- margins?: TableCellMargins;
1263
- cnfStyle?: string;
1274
+ readonly propertiesXml?: string;
1275
+ readonly unknownPropertyChildren?: UnknownPropertyChild[];
1276
+ readonly gridSpan?: number;
1277
+ readonly verticalMerge?: "restart" | "continue";
1278
+ readonly children: BlockNode[];
1279
+ readonly width?: TableWidth;
1280
+ readonly borders?: TableCellBorders;
1281
+ readonly shading?: CellShading;
1282
+ readonly verticalAlign?: "top" | "center" | "bottom";
1283
+ readonly textDirection?: "lrTb" | "tbRl" | "btLr";
1284
+ readonly noWrap?: boolean;
1285
+ readonly fitText?: boolean;
1286
+ readonly margins?: TableCellMargins;
1287
+ readonly cnfStyle?: string;
1264
1288
  }
1265
1289
 
1266
1290
  export interface SdtCheckboxState {
1267
- checked: boolean;
1268
- checkedChar?: string;
1269
- uncheckedChar?: string;
1291
+ readonly checked: boolean;
1292
+ readonly checkedChar?: string;
1293
+ readonly uncheckedChar?: string;
1270
1294
  }
1271
1295
 
1272
1296
  export interface SdtDatePickerState {
1273
- fullDate?: string;
1274
- dateFormat?: string;
1275
- lid?: string;
1297
+ readonly fullDate?: string;
1298
+ readonly dateFormat?: string;
1299
+ readonly lid?: string;
1276
1300
  }
1277
1301
 
1278
1302
  export interface SdtDropdownListItem {
1279
- displayText?: string;
1280
- value: string;
1303
+ readonly displayText?: string;
1304
+ readonly value: string;
1281
1305
  }
1282
1306
 
1283
1307
  export interface SdtNode {
1284
- type: "sdt";
1308
+ readonly type: "sdt";
1285
1309
  properties: {
1286
- sdtType?: string;
1287
- alias?: string;
1288
- tag?: string;
1289
- lock?: string;
1290
- propertiesXml?: string;
1291
- checkbox?: SdtCheckboxState;
1292
- datePicker?: SdtDatePickerState;
1293
- dropdownList?: SdtDropdownListItem[];
1294
- comboBox?: SdtDropdownListItem[];
1295
- showingPlcHdr?: boolean;
1310
+ readonly sdtType?: string;
1311
+ readonly alias?: string;
1312
+ readonly tag?: string;
1313
+ readonly lock?: string;
1314
+ readonly propertiesXml?: string;
1315
+ readonly checkbox?: SdtCheckboxState;
1316
+ readonly datePicker?: SdtDatePickerState;
1317
+ readonly dropdownList?: SdtDropdownListItem[];
1318
+ readonly comboBox?: SdtDropdownListItem[];
1319
+ readonly showingPlcHdr?: boolean;
1296
1320
  };
1297
- children: BlockNode[];
1321
+ readonly children: BlockNode[];
1298
1322
  }
1299
1323
 
1300
1324
  export interface CustomXmlNode {
1301
- type: "custom_xml";
1302
- uri?: string;
1303
- element?: string;
1304
- rawXml?: string;
1305
- children: BlockNode[];
1325
+ readonly type: "custom_xml";
1326
+ readonly uri?: string;
1327
+ readonly element?: string;
1328
+ readonly rawXml?: string;
1329
+ readonly children: BlockNode[];
1306
1330
  }
1307
1331
 
1308
1332
  export interface AltChunkNode {
1309
- type: "alt_chunk";
1310
- relationshipId: string;
1333
+ readonly type: "alt_chunk";
1334
+ readonly relationshipId: string;
1311
1335
  }
1312
1336
 
1313
1337
  /**
@@ -1355,53 +1379,53 @@ export type FieldRefreshStatus =
1355
1379
  | "preserve-only";
1356
1380
 
1357
1381
  export interface FieldNode {
1358
- type: "field";
1359
- fieldType: "simple" | "complex";
1360
- instruction: string;
1361
- children: InlineNode[];
1382
+ readonly type: "field";
1383
+ readonly fieldType: "simple" | "complex";
1384
+ readonly instruction: string;
1385
+ readonly children: InlineNode[];
1362
1386
  /** Classified field family. Undefined for legacy snapshots. */
1363
- fieldFamily?: FieldFamily;
1387
+ readonly fieldFamily?: FieldFamily;
1364
1388
  /** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
1365
- fieldTarget?: string;
1389
+ readonly fieldTarget?: string;
1366
1390
  /** Runtime refresh status. Undefined for legacy or preserve-only fields. */
1367
- refreshStatus?: FieldRefreshStatus;
1391
+ readonly refreshStatus?: FieldRefreshStatus;
1368
1392
  /** Parsed field switches. Present only when the instruction contains recognized switches. */
1369
1393
  switches?: {
1370
- hyperlink?: boolean;
1371
- relativePosition?: boolean;
1372
- numericRef?: boolean;
1373
- recursive?: boolean;
1374
- suppressNonDelimiter?: boolean;
1375
- includeNumbering?: boolean;
1376
- includeLevel?: boolean;
1394
+ readonly hyperlink?: boolean;
1395
+ readonly relativePosition?: boolean;
1396
+ readonly numericRef?: boolean;
1397
+ readonly recursive?: boolean;
1398
+ readonly suppressNonDelimiter?: boolean;
1399
+ readonly includeNumbering?: boolean;
1400
+ readonly includeLevel?: boolean;
1377
1401
  };
1378
1402
  /** Legacy form-field metadata from w:ffData (textInput, checkBox, ddList). Round-trip only; no UI for MVP. */
1379
- legacyFormField?: LegacyFormFieldNode;
1403
+ readonly legacyFormField?: LegacyFormFieldNode;
1380
1404
  }
1381
1405
 
1382
1406
  export type LegacyFormFieldKind = "textInput" | "checkBox" | "ddList";
1383
1407
 
1384
1408
  export interface LegacyFormFieldNode {
1385
- kind: LegacyFormFieldKind;
1386
- name?: string;
1387
- enabled?: boolean;
1388
- calcOnExit?: boolean;
1409
+ readonly kind: LegacyFormFieldKind;
1410
+ readonly name?: string;
1411
+ readonly enabled?: boolean;
1412
+ readonly calcOnExit?: boolean;
1389
1413
  textInput?: {
1390
- default?: string;
1391
- maxLength?: number;
1392
- format?: string;
1414
+ readonly default?: string;
1415
+ readonly maxLength?: number;
1416
+ readonly format?: string;
1393
1417
  };
1394
1418
  checkBox?: {
1395
- size?: number;
1396
- default?: boolean;
1397
- checked?: boolean;
1419
+ readonly size?: number;
1420
+ readonly default?: boolean;
1421
+ readonly checked?: boolean;
1398
1422
  };
1399
1423
  ddList?: {
1400
- default?: number;
1401
- listEntry?: string[];
1424
+ readonly default?: number;
1425
+ readonly listEntry?: string[];
1402
1426
  };
1403
1427
  /** Verbatim <w:ffData>…</w:ffData> XML for lossless round-trip. */
1404
- rawXml: string;
1428
+ readonly rawXml: string;
1405
1429
  /**
1406
1430
  * Phase V.a: set to `true` when typed fields have been mutated via
1407
1431
  * `setLegacyFormFieldValue` since parse. On serialize, the `rawXml` is
@@ -1409,7 +1433,7 @@ export interface LegacyFormFieldNode {
1409
1433
  * Un-mutated nodes emit the preserved `rawXml` verbatim for lossless
1410
1434
  * round-trip.
1411
1435
  */
1412
- mutated?: boolean;
1436
+ readonly mutated?: boolean;
1413
1437
  }
1414
1438
 
1415
1439
  // ─── Field registry ─────────────────────────────────────────────────────────
@@ -1424,34 +1448,34 @@ export interface LegacyFormFieldNode {
1424
1448
  */
1425
1449
  export interface FieldRegistry {
1426
1450
  /** Supported field instances that participate in refresh. */
1427
- supported: FieldRegistryEntry[];
1451
+ readonly supported: FieldRegistryEntry[];
1428
1452
  /** Preserve-only field instances cataloged for round-trip safety. */
1429
- preserveOnly: FieldRegistryEntry[];
1453
+ readonly preserveOnly: FieldRegistryEntry[];
1430
1454
  /** First-class TOC regions, including cached field-result rows and generated heading rows. */
1431
- tocRegions?: TocRegion[];
1455
+ readonly tocRegions?: TocRegion[];
1432
1456
  /** Generated TOC structure extracted from heading-driven TOC fields. */
1433
- tocStructure?: TocStructure;
1457
+ readonly tocStructure?: TocStructure;
1434
1458
  }
1435
1459
 
1436
1460
  export interface FieldRegistryEntry {
1437
1461
  /** Stable document-order index of this field (0-based). */
1438
- fieldIndex: number;
1462
+ readonly fieldIndex: number;
1439
1463
  /** Classified field family. */
1440
- fieldFamily: FieldFamily;
1464
+ readonly fieldFamily: FieldFamily;
1441
1465
  /** Whether the field is in the supported refresh slice. */
1442
- supported: boolean;
1466
+ readonly supported: boolean;
1443
1467
  /** Field instruction text. */
1444
- instruction: string;
1468
+ readonly instruction: string;
1445
1469
  /** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
1446
- fieldTarget?: string;
1470
+ readonly fieldTarget?: string;
1447
1471
  /** Current display text extracted from field content. */
1448
- displayText: string;
1472
+ readonly displayText: string;
1449
1473
  /** Paragraph index in document order where this field appears. */
1450
- paragraphIndex: number;
1474
+ readonly paragraphIndex: number;
1451
1475
  /** Runtime refresh status. */
1452
- refreshStatus: FieldRefreshStatus;
1476
+ readonly refreshStatus: FieldRefreshStatus;
1453
1477
  /** Parsed field switches carried from FieldNode.switches. Present only for REF/PAGEREF/NOTEREF/TOC entries with recognized switches. */
1454
- switches?: FieldNode["switches"];
1478
+ readonly switches?: FieldNode["switches"];
1455
1479
  }
1456
1480
 
1457
1481
  /**
@@ -1460,138 +1484,138 @@ export interface FieldRegistryEntry {
1460
1484
  */
1461
1485
  export interface TocStructure {
1462
1486
  /** The raw TOC field instruction (e.g. "TOC \\o \"1-3\" \\h"). */
1463
- instruction: string;
1487
+ readonly instruction: string;
1464
1488
  /** Heading level range the TOC covers. */
1465
- levelRange: { from: number; to: number };
1489
+ readonly levelRange: { from: number; to: number };
1466
1490
  /** Ordered TOC entries derived from heading paragraphs. */
1467
- entries: TocEntry[];
1491
+ readonly entries: TocEntry[];
1468
1492
  /** Whether the TOC content is current with the heading structure. */
1469
- status: "current" | "stale";
1493
+ readonly status: "current" | "stale";
1470
1494
  }
1471
1495
 
1472
1496
  export interface TocInstructionModel {
1473
1497
  /** The raw TOC field instruction (e.g. "TOC \\o \"1-3\" \\h"). */
1474
- raw: string;
1498
+ readonly raw: string;
1475
1499
  /** Heading level range selected by \\o, or defaulted to 1-9. */
1476
- outlineRange: { from: number; to: number };
1500
+ readonly outlineRange: { from: number; to: number };
1477
1501
  /** \\h — result entries should hyperlink to their anchors. */
1478
- hyperlink?: boolean;
1502
+ readonly hyperlink?: boolean;
1479
1503
  /** \\z — hide tab leader and page numbers in web layout. */
1480
- hidePageNumbersInWeb?: boolean;
1504
+ readonly hidePageNumbersInWeb?: boolean;
1481
1505
  /** \\u — include paragraphs with outline levels. */
1482
- useOutlineLevels?: boolean;
1506
+ readonly useOutlineLevels?: boolean;
1483
1507
  /** \\t "Style,Level,..." — style-driven TOC mapping. */
1484
- styleMap?: TocStyleMapEntry[];
1508
+ readonly styleMap?: TocStyleMapEntry[];
1485
1509
  /** \\b bookmark — bound source content to a bookmark. */
1486
- bookmarkName?: string;
1510
+ readonly bookmarkName?: string;
1487
1511
  /** \\c identifier — table-of-figures / sequence identifier. */
1488
- sequenceIdentifier?: string;
1512
+ readonly sequenceIdentifier?: string;
1489
1513
  /** \\f identifier — TC-entry identifier. */
1490
- tcIdentifier?: string;
1514
+ readonly tcIdentifier?: string;
1491
1515
  /** \\p separator — custom entry/page separator. */
1492
- entryPageSeparator?: string;
1516
+ readonly entryPageSeparator?: string;
1493
1517
  /** \\d separator — custom sequence separator. */
1494
- sequenceSeparator?: string;
1518
+ readonly sequenceSeparator?: string;
1495
1519
  /** \\n optional range — omit page numbers. */
1496
- omitPageNumbers?: true | { from: number; to: number };
1520
+ readonly omitPageNumbers?: true | { from: number; to: number };
1497
1521
  /** Switches parsed but not yet semantically implemented. */
1498
- unsupportedSwitches?: string[];
1522
+ readonly unsupportedSwitches?: string[];
1499
1523
  }
1500
1524
 
1501
1525
  export interface TocStyleMapEntry {
1502
- styleName: string;
1503
- level: number;
1526
+ readonly styleName: string;
1527
+ readonly level: number;
1504
1528
  }
1505
1529
 
1506
1530
  export interface TocCachedEntry {
1507
1531
  /** Text extracted from the cached field-result row before the page-number tab. */
1508
- text: string;
1532
+ readonly text: string;
1509
1533
  /** TOC level inferred from TOC1/TOC2/etc. paragraph style. */
1510
- level: number;
1534
+ readonly level: number;
1511
1535
  /** Paragraph index of the cached result row in document order. */
1512
- paragraphIndex: number;
1536
+ readonly paragraphIndex: number;
1513
1537
  /** Style ID of the cached result paragraph, if available. */
1514
- styleId?: string;
1538
+ readonly styleId?: string;
1515
1539
  /** Bookmark/hyperlink anchor used by the cached result row, if present. */
1516
- bookmarkName?: string;
1540
+ readonly bookmarkName?: string;
1517
1541
  /** Page label extracted from the cached field result. */
1518
- pageText?: string;
1542
+ readonly pageText?: string;
1519
1543
  /** Full visible row text, including tabs/page labels. */
1520
- displayText: string;
1544
+ readonly displayText: string;
1521
1545
  }
1522
1546
 
1523
1547
  export interface TocRegion {
1524
1548
  /** Stable document-order id for this imported TOC region. */
1525
- tocId: string;
1549
+ readonly tocId: string;
1526
1550
  /** Source TOC field index in FieldRegistry.supported. */
1527
- sourceFieldIndex: number;
1551
+ readonly sourceFieldIndex: number;
1528
1552
  /** Parsed instruction metadata and switches. */
1529
- instruction: TocInstructionModel;
1553
+ readonly instruction: TocInstructionModel;
1530
1554
  /** Inclusive paragraph range containing the cached/generated result rows. */
1531
- resultRange: { fromParagraphIndex: number; toParagraphIndex: number };
1555
+ readonly resultRange: { fromParagraphIndex: number; toParagraphIndex: number };
1532
1556
  /** Parent container kind where the region was found. */
1533
- parentKind: "body" | "sdt" | "custom_xml" | "table_cell";
1557
+ readonly parentKind: "body" | "sdt" | "custom_xml" | "table_cell";
1534
1558
  /** Structural path to the parent block sequence, for diagnostics. */
1535
- sourcePath: string;
1559
+ readonly sourcePath: string;
1536
1560
  /** Cached rows imported from the DOCX field result. */
1537
- cachedEntries: TocCachedEntry[];
1561
+ readonly cachedEntries: TocCachedEntry[];
1538
1562
  /** Heading-derived rows generated from the canonical document. */
1539
- generatedEntries: TocEntry[];
1563
+ readonly generatedEntries: TocEntry[];
1540
1564
  /** Whether cached entries match the generated heading model. */
1541
- status: "current" | "stale";
1565
+ readonly status: "current" | "stale";
1542
1566
  }
1543
1567
 
1544
1568
  export interface TocEntry {
1545
1569
  /** Heading text. */
1546
- text: string;
1570
+ readonly text: string;
1547
1571
  /** Heading outline level (1-9). */
1548
- level: number;
1572
+ readonly level: number;
1549
1573
  /** Paragraph index of the heading in document order. */
1550
- paragraphIndex: number;
1574
+ readonly paragraphIndex: number;
1551
1575
  /** Style ID of the heading paragraph, if available. */
1552
- styleId?: string;
1576
+ readonly styleId?: string;
1553
1577
  /** Bookmark name anchoring this heading, if present. */
1554
- bookmarkName?: string;
1578
+ readonly bookmarkName?: string;
1555
1579
  }
1556
1580
 
1557
1581
  export interface BookmarkStartNode {
1558
- type: "bookmark_start";
1559
- bookmarkId: string;
1560
- name: string;
1582
+ readonly type: "bookmark_start";
1583
+ readonly bookmarkId: string;
1584
+ readonly name: string;
1561
1585
  }
1562
1586
 
1563
1587
  export interface BookmarkEndNode {
1564
- type: "bookmark_end";
1565
- bookmarkId: string;
1588
+ readonly type: "bookmark_end";
1589
+ readonly bookmarkId: string;
1566
1590
  }
1567
1591
 
1568
1592
  export interface SectionBreakNode {
1569
- type: "section_break";
1570
- sectionPropertiesXml?: string;
1593
+ readonly type: "section_break";
1594
+ readonly sectionPropertiesXml?: string;
1571
1595
  /**
1572
1596
  * @deprecated Legacy field from older snapshots. New exports should use
1573
1597
  * sectionPropertiesXml and only contain raw <w:sectPr> content.
1574
1598
  */
1575
- propertiesXml?: string;
1576
- sectionProperties?: SectionProperties;
1599
+ readonly propertiesXml?: string;
1600
+ readonly sectionProperties?: SectionProperties;
1577
1601
  }
1578
1602
 
1579
1603
  export interface SectionProperties {
1580
- pageSize?: PageSize;
1581
- pageMargins?: PageMargins;
1582
- columns?: ColumnProperties;
1583
- pageNumbering?: PageNumbering;
1584
- lineNumbering?: SectionLineNumbering;
1585
- pageBorders?: SectionPageBorders;
1586
- documentGrid?: SectionDocumentGrid;
1587
- headerReferences?: HeaderFooterReference[];
1588
- footerReferences?: HeaderFooterReference[];
1589
- sectionType?: "continuous" | "nextPage" | "evenPage" | "oddPage" | "nextColumn";
1590
- titlePage?: boolean;
1604
+ readonly pageSize?: PageSize;
1605
+ readonly pageMargins?: PageMargins;
1606
+ readonly columns?: ColumnProperties;
1607
+ readonly pageNumbering?: PageNumbering;
1608
+ readonly lineNumbering?: SectionLineNumbering;
1609
+ readonly pageBorders?: SectionPageBorders;
1610
+ readonly documentGrid?: SectionDocumentGrid;
1611
+ readonly headerReferences?: HeaderFooterReference[];
1612
+ readonly footerReferences?: HeaderFooterReference[];
1613
+ readonly sectionType?: "continuous" | "nextPage" | "evenPage" | "oddPage" | "nextColumn";
1614
+ readonly titlePage?: boolean;
1591
1615
  /** Per-section footnote configuration (ECMA-376 §17.11.12). Overrides file-level defaults. */
1592
- footnotePr?: FootnoteProperties;
1616
+ readonly footnotePr?: FootnoteProperties;
1593
1617
  /** Per-section endnote configuration (ECMA-376 §17.11.4). Overrides file-level defaults. */
1594
- endnotePr?: EndnoteProperties;
1618
+ readonly endnotePr?: EndnoteProperties;
1595
1619
  /**
1596
1620
  * Unmodelled direct children of `<w:sectPr>` captured verbatim for
1597
1621
  * round-trip. Mirrors `CanonicalParagraphFormatting.unknownPropertyChildren`
@@ -1600,7 +1624,7 @@ export interface SectionProperties {
1600
1624
  * `<w15:footnoteColumns>` and Word-internal section knobs through a
1601
1625
  * parse→serialize round-trip when the runtime mutates section properties.
1602
1626
  */
1603
- unknownPropertyChildren?: UnknownPropertyChild[];
1627
+ readonly unknownPropertyChildren?: UnknownPropertyChild[];
1604
1628
  }
1605
1629
 
1606
1630
  /**
@@ -1619,13 +1643,13 @@ export interface SectionProperties {
1619
1643
  * counterpart; `EndnoteProperties` aliases this shape.
1620
1644
  */
1621
1645
  export interface FootnoteProperties {
1622
- pos?: "pageBottom" | "beneathText" | "sectEnd" | "docEnd";
1646
+ readonly pos?: "pageBottom" | "beneathText" | "sectEnd" | "docEnd";
1623
1647
  numFmt?: "decimal" | "upperRoman" | "lowerRoman" | "upperLetter" | "lowerLetter"
1624
1648
  | "ordinal" | "cardinalText" | "ordinalText" | "hex" | "chicago" | "bullet"
1625
1649
  | "ideographDigital" | "japaneseCounting" | "arabicAbjad" | "arabicAlpha"
1626
1650
  | "none";
1627
- numStart?: number;
1628
- numRestart?: "continuous" | "eachSect" | "eachPage";
1651
+ readonly numStart?: number;
1652
+ readonly numRestart?: "continuous" | "eachSect" | "eachPage";
1629
1653
  }
1630
1654
 
1631
1655
  /**
@@ -1636,62 +1660,62 @@ export interface FootnoteProperties {
1636
1660
  export type EndnoteProperties = FootnoteProperties;
1637
1661
 
1638
1662
  export interface PageSize {
1639
- width: number;
1640
- height: number;
1641
- orientation?: "portrait" | "landscape";
1663
+ readonly width: number;
1664
+ readonly height: number;
1665
+ readonly orientation?: "portrait" | "landscape";
1642
1666
  }
1643
1667
 
1644
1668
  export interface PageMargins {
1645
- top: number;
1646
- right: number;
1647
- bottom: number;
1648
- left: number;
1649
- header?: number;
1650
- footer?: number;
1651
- gutter?: number;
1669
+ readonly top: number;
1670
+ readonly right: number;
1671
+ readonly bottom: number;
1672
+ readonly left: number;
1673
+ readonly header?: number;
1674
+ readonly footer?: number;
1675
+ readonly gutter?: number;
1652
1676
  }
1653
1677
 
1654
1678
  export interface ColumnProperties {
1655
- count?: number;
1656
- space?: number;
1657
- equalWidth?: boolean;
1658
- columns?: Array<{ width: number; space?: number }>;
1659
- separator?: boolean;
1679
+ readonly count?: number;
1680
+ readonly space?: number;
1681
+ readonly equalWidth?: boolean;
1682
+ readonly columns?: Array<{ width: number; space?: number }>;
1683
+ readonly separator?: boolean;
1660
1684
  }
1661
1685
 
1662
1686
  export interface PageNumbering {
1663
- format?: string;
1664
- start?: number;
1665
- chapStyle?: string;
1666
- chapSep?: string;
1687
+ readonly format?: string;
1688
+ readonly start?: number;
1689
+ readonly chapStyle?: string;
1690
+ readonly chapSep?: string;
1667
1691
  }
1668
1692
 
1669
1693
  export interface SectionLineNumbering {
1670
- countBy?: number;
1671
- start?: number;
1672
- distance?: number;
1673
- restart?: "newPage" | "newSection" | "continuous";
1694
+ readonly countBy?: number;
1695
+ readonly start?: number;
1696
+ readonly distance?: number;
1697
+ readonly restart?: "newPage" | "newSection" | "continuous";
1674
1698
  }
1675
1699
 
1676
1700
  export interface SectionPageBorders {
1677
- top?: BorderSpec;
1678
- left?: BorderSpec;
1679
- bottom?: BorderSpec;
1680
- right?: BorderSpec;
1681
- offsetFrom?: "page" | "text";
1682
- display?: "allPages" | "firstPage" | "notFirstPage";
1683
- zOrder?: "front" | "back";
1701
+ readonly top?: BorderSpec;
1702
+ readonly left?: BorderSpec;
1703
+ readonly bottom?: BorderSpec;
1704
+ readonly right?: BorderSpec;
1705
+ readonly offsetFrom?: "page" | "text";
1706
+ readonly display?: "allPages" | "firstPage" | "notFirstPage";
1707
+ readonly zOrder?: "front" | "back";
1684
1708
  }
1685
1709
 
1686
1710
  export interface SectionDocumentGrid {
1687
- type?: "default" | "lines" | "linesAndChars" | "snapToChars";
1688
- linePitch?: number;
1689
- charSpace?: number;
1711
+ readonly type?: "default" | "lines" | "linesAndChars" | "snapToChars";
1712
+ readonly linePitch?: number;
1713
+ readonly charSpace?: number;
1690
1714
  }
1691
1715
 
1692
1716
  export interface HeaderFooterReference {
1693
- variant: HeaderFooterVariant;
1694
- relationshipId: string;
1717
+ readonly variant: HeaderFooterVariant;
1718
+ readonly relationshipId: string;
1695
1719
  }
1696
1720
 
1697
1721
  export type InlineNode =
@@ -1719,9 +1743,9 @@ export type InlineNode =
1719
1743
  | OleEmbedNode;
1720
1744
 
1721
1745
  export interface TextNode {
1722
- type: "text";
1723
- text: string;
1724
- marks?: TextMark[];
1746
+ readonly type: "text";
1747
+ readonly text: string;
1748
+ readonly marks?: TextMark[];
1725
1749
  }
1726
1750
 
1727
1751
  export type TextMark =
@@ -1748,11 +1772,11 @@ export type TextMark =
1748
1772
  | { type: "allCaps" };
1749
1773
 
1750
1774
  export interface HardBreakNode {
1751
- type: "hard_break";
1775
+ readonly type: "hard_break";
1752
1776
  }
1753
1777
 
1754
1778
  export interface ColumnBreakNode {
1755
- type: "column_break";
1779
+ readonly type: "column_break";
1756
1780
  }
1757
1781
 
1758
1782
  /**
@@ -1770,54 +1794,54 @@ export interface ColumnBreakNode {
1770
1794
  * section properties; `PageBreakNode` is inline inside a run.
1771
1795
  */
1772
1796
  export interface PageBreakNode {
1773
- type: "page_break";
1797
+ readonly type: "page_break";
1774
1798
  }
1775
1799
 
1776
1800
  export interface TabNode {
1777
- type: "tab";
1801
+ readonly type: "tab";
1778
1802
  }
1779
1803
 
1780
1804
  export interface SymbolNode {
1781
- type: "symbol";
1782
- char: string;
1783
- font?: string;
1784
- marks?: TextMark[];
1805
+ readonly type: "symbol";
1806
+ readonly char: string;
1807
+ readonly font?: string;
1808
+ readonly marks?: TextMark[];
1785
1809
  }
1786
1810
 
1787
1811
  export interface HyperlinkNode {
1788
- type: "hyperlink";
1789
- href: string;
1790
- children: Array<TextNode | HardBreakNode | ColumnBreakNode | PageBreakNode | TabNode | SymbolNode>;
1812
+ readonly type: "hyperlink";
1813
+ readonly href: string;
1814
+ readonly children: Array<TextNode | HardBreakNode | ColumnBreakNode | PageBreakNode | TabNode | SymbolNode>;
1791
1815
  }
1792
1816
 
1793
1817
  export interface ImageNode {
1794
- type: "image";
1795
- mediaId: string;
1796
- altText?: string;
1797
- placementXml?: string;
1798
- display?: "inline" | "floating";
1799
- floating?: FloatingImageProperties;
1818
+ readonly type: "image";
1819
+ readonly mediaId: string;
1820
+ readonly altText?: string;
1821
+ readonly placementXml?: string;
1822
+ readonly display?: "inline" | "floating";
1823
+ readonly floating?: FloatingImageProperties;
1800
1824
  }
1801
1825
 
1802
1826
  export interface FloatingImageProperties {
1803
- horizontalPosition?: FloatingAxisPosition;
1804
- verticalPosition?: FloatingAxisPosition;
1805
- wrap?: "none" | "square" | "tight" | "through" | "topAndBottom";
1806
- behindDoc?: boolean;
1807
- layoutInCell?: boolean;
1808
- allowOverlap?: boolean;
1827
+ readonly horizontalPosition?: FloatingAxisPosition;
1828
+ readonly verticalPosition?: FloatingAxisPosition;
1829
+ readonly wrap?: "none" | "square" | "tight" | "through" | "topAndBottom";
1830
+ readonly behindDoc?: boolean;
1831
+ readonly layoutInCell?: boolean;
1832
+ readonly allowOverlap?: boolean;
1809
1833
  }
1810
1834
 
1811
1835
  export interface FloatingAxisPosition {
1812
- relativeFrom?: string;
1813
- align?: string;
1814
- offset?: number;
1836
+ readonly relativeFrom?: string;
1837
+ readonly align?: string;
1838
+ readonly offset?: number;
1815
1839
  }
1816
1840
 
1817
1841
  export interface OpaqueInlineNode {
1818
- type: "opaque_inline";
1819
- fragmentId: string;
1820
- warningId: string;
1842
+ readonly type: "opaque_inline";
1843
+ readonly fragmentId: string;
1844
+ readonly warningId: string;
1821
1845
  }
1822
1846
 
1823
1847
  // ---- Complex rendering inline nodes (read-only previews) ----
@@ -1828,8 +1852,8 @@ export interface OpaqueInlineNode {
1828
1852
  * mc:AlternateContent it is referenced by previewMediaId.
1829
1853
  */
1830
1854
  export interface ChartPreviewNode {
1831
- type: "chart_preview";
1832
- previewMediaId?: string;
1855
+ readonly type: "chart_preview";
1856
+ readonly previewMediaId?: string;
1833
1857
  /**
1834
1858
  * Typed chart data model parsed from the `c:chartSpace` part, when
1835
1859
  * available. Populated at import time by the Stage 1 chart parser
@@ -1847,7 +1871,7 @@ export interface ChartPreviewNode {
1847
1871
  * `parsedData` in place.
1848
1872
  */
1849
1873
  readonly parsedData?: ChartModel;
1850
- rawXml: string;
1874
+ readonly rawXml: string;
1851
1875
  }
1852
1876
 
1853
1877
  /**
@@ -1855,9 +1879,9 @@ export interface ChartPreviewNode {
1855
1879
  * stored in rawXml for lossless round-trip export.
1856
1880
  */
1857
1881
  export interface SmartArtPreviewNode {
1858
- type: "smartart_preview";
1859
- previewMediaId?: string;
1860
- rawXml: string;
1882
+ readonly type: "smartart_preview";
1883
+ readonly previewMediaId?: string;
1884
+ readonly rawXml: string;
1861
1885
  }
1862
1886
 
1863
1887
  /**
@@ -1872,14 +1896,14 @@ export interface SmartArtPreviewNode {
1872
1896
  * runtime contract was confirmed canonical).
1873
1897
  */
1874
1898
  export interface ShapeNode {
1875
- type: "shape";
1876
- text?: string;
1877
- geometry?: string;
1878
- isTextBox?: boolean;
1899
+ readonly type: "shape";
1900
+ readonly text?: string;
1901
+ readonly geometry?: string;
1902
+ readonly isTextBox?: boolean;
1879
1903
  /** Text box body layout from `wps:bodyPr` / `a:bodyPr`. */
1880
- textBoxBody?: TextBoxBodyProperties;
1904
+ readonly textBoxBody?: TextBoxBodyProperties;
1881
1905
  /** Raw `<w:txbxContent>` XML, preserved for serialization + round-trip. */
1882
- txbxContentXml?: string;
1906
+ readonly txbxContentXml?: string;
1883
1907
  /**
1884
1908
  * Parsed canonical block-level structure from `<w:txbxContent>`,
1885
1909
  * populated when the parse path supplies a `blockParser` callback
@@ -1887,8 +1911,8 @@ export interface ShapeNode {
1887
1911
  * body via `src/io/ooxml/parse-main-document.ts`). Shape + semantics
1888
1912
  * identical to `ShapeContent.txbxBlocks` on the drawing-frame path.
1889
1913
  */
1890
- txbxBlocks?: ReadonlyArray<BlockNode>;
1891
- rawXml: string;
1914
+ readonly txbxBlocks?: BlockNode[];
1915
+ readonly rawXml: string;
1892
1916
  }
1893
1917
 
1894
1918
  /**
@@ -1896,10 +1920,10 @@ export interface ShapeNode {
1896
1920
  * Text is extracted for display. The original drawing XML is preserved in rawXml.
1897
1921
  */
1898
1922
  export interface WordArtNode {
1899
- type: "wordart";
1900
- text: string;
1901
- geometry?: string;
1902
- rawXml: string;
1923
+ readonly type: "wordart";
1924
+ readonly text: string;
1925
+ readonly geometry?: string;
1926
+ readonly rawXml: string;
1903
1927
  }
1904
1928
 
1905
1929
  /**
@@ -1908,10 +1932,10 @@ export interface WordArtNode {
1908
1932
  * in rawXml for lossless round-trip export.
1909
1933
  */
1910
1934
  export interface VmlShapeNode {
1911
- type: "vml_shape";
1912
- text?: string;
1913
- shapeType?: string;
1914
- rawXml: string;
1935
+ readonly type: "vml_shape";
1936
+ readonly text?: string;
1937
+ readonly shapeType?: string;
1938
+ readonly rawXml: string;
1915
1939
  }
1916
1940
 
1917
1941
  /**
@@ -1934,111 +1958,111 @@ export interface VmlShapeNode {
1934
1958
  * through when `<o:OLEObject>` is absent.
1935
1959
  */
1936
1960
  export interface OleEmbedNode {
1937
- type: "ole_embed";
1938
- id: string;
1939
- progId?: string;
1940
- embedType: "oleObject";
1941
- relationshipId: string;
1961
+ readonly type: "ole_embed";
1962
+ readonly id: string;
1963
+ readonly progId?: string;
1964
+ readonly embedType: "oleObject";
1965
+ readonly relationshipId: string;
1942
1966
  metadata: {
1943
- originalFilename?: string;
1944
- classId?: string;
1945
- shapeId?: string;
1967
+ readonly originalFilename?: string;
1968
+ readonly classId?: string;
1969
+ readonly shapeId?: string;
1946
1970
  };
1947
- rawXml: string;
1971
+ readonly rawXml: string;
1948
1972
  }
1949
1973
 
1950
1974
  export interface AnchorGeometry {
1951
- display: "inline" | "floating";
1952
- extent: { widthEmu: number; heightEmu: number };
1953
- wrapMode: "none" | "square" | "tight" | "through" | "topAndBottom";
1975
+ readonly display: "inline" | "floating";
1976
+ readonly extent: { widthEmu: number; heightEmu: number };
1977
+ readonly wrapMode: "none" | "square" | "tight" | "through" | "topAndBottom";
1954
1978
  /**
1955
1979
  * Polygon coords (in OOXML's 21600ths-of-image units) when wrapMode is
1956
1980
  * "tight" or "through" and `wp:wrapPolygon` is present. First point from
1957
1981
  * `wp:start`; subsequent points from sequential `wp:lineTo`. Consumed by
1958
1982
  * Lane 6 P7 float-wrap for proper polygon text flow.
1959
1983
  */
1960
- wrapPolygon?: Array<{ x: number; y: number }>;
1961
- positionH?: { relativeFrom: string; align?: string; offset?: number };
1962
- positionV?: { relativeFrom: string; align?: string; offset?: number };
1963
- distMargins?: { top?: number; bottom?: number; left?: number; right?: number };
1964
- relativeHeight?: number;
1965
- behindDoc?: boolean;
1966
- layoutInCell?: boolean;
1967
- allowOverlap?: boolean;
1968
- simplePos?: boolean;
1984
+ readonly wrapPolygon?: Array<{ x: number; y: number }>;
1985
+ readonly positionH?: { relativeFrom: string; align?: string; offset?: number };
1986
+ readonly positionV?: { relativeFrom: string; align?: string; offset?: number };
1987
+ readonly distMargins?: { top?: number; bottom?: number; left?: number; right?: number };
1988
+ readonly relativeHeight?: number;
1989
+ readonly behindDoc?: boolean;
1990
+ readonly layoutInCell?: boolean;
1991
+ readonly allowOverlap?: boolean;
1992
+ readonly simplePos?: boolean;
1969
1993
  docPr?: {
1970
- id: string;
1971
- name?: string;
1972
- descr?: string;
1994
+ readonly id: string;
1995
+ readonly name?: string;
1996
+ readonly descr?: string;
1973
1997
  /** `wp:docPr hidden="1"` — Word UI hides the frame from the user. */
1974
- hidden?: boolean;
1998
+ readonly hidden?: boolean;
1975
1999
  };
1976
2000
  /**
1977
2001
  * `wp:cNvGraphicFramePr/a:graphicFrameLocks` — object-manipulation locks
1978
2002
  * consumed by Lane 6 object chrome (disable resize handles / move, etc.).
1979
2003
  */
1980
2004
  frameLocks?: {
1981
- noChangeAspect?: boolean;
1982
- noResize?: boolean;
1983
- noMove?: boolean;
1984
- noRot?: boolean;
1985
- noSelect?: boolean;
1986
- noGrp?: boolean;
2005
+ readonly noChangeAspect?: boolean;
2006
+ readonly noResize?: boolean;
2007
+ readonly noMove?: boolean;
2008
+ readonly noRot?: boolean;
2009
+ readonly noSelect?: boolean;
2010
+ readonly noGrp?: boolean;
1987
2011
  };
1988
2012
  }
1989
2013
 
1990
2014
  export interface PictureContent {
1991
- type: "picture";
1992
- blipRef: string;
2015
+ readonly type: "picture";
2016
+ readonly blipRef: string;
1993
2017
  /**
1994
2018
  * Phase 4.4 G4 — true when the image references an external URL via
1995
2019
  * `a:blip r:link` (bytes not in the .docx package) rather than embedded
1996
2020
  * via `r:embed`. External-linked images can't resolve to mediaId;
1997
2021
  * `state: "missing"` in surface projection.
1998
2022
  */
1999
- isLinked?: boolean;
2023
+ readonly isLinked?: boolean;
2000
2024
  /** Resolved media catalog ID (e.g. "media:word/media/image1.png"), populated by parse-drawing. */
2001
- mediaId?: string;
2025
+ readonly mediaId?: string;
2002
2026
  /** Absolute package path for media catalog lookup. */
2003
- packagePartName?: string;
2027
+ readonly packagePartName?: string;
2004
2028
  /** MIME resolved from the OPC media part, when known. */
2005
- contentType?: string;
2029
+ readonly contentType?: string;
2006
2030
  /** DrawingML a:lum brightness/contrast adjustments on the blip. Values are OOXML fixed percentages. */
2007
- lum?: { bright?: number; contrast?: number };
2008
- srcRect?: { top: number; bottom: number; left: number; right: number };
2009
- stretch?: boolean;
2031
+ readonly lum?: { bright?: number; contrast?: number };
2032
+ readonly srcRect?: { top: number; bottom: number; left: number; right: number };
2033
+ readonly stretch?: boolean;
2010
2034
  /**
2011
2035
  * Phase 4.5 G7 — `pic:blipFill/a:tile` (tiled image repeat). Mutually
2012
2036
  * exclusive with `stretch`. tx/ty = offset (EMUs), sx/sy = scale ×1000,
2013
2037
  * flip = "x"|"y"|"xy", algn = alignment token.
2014
2038
  */
2015
2039
  tile?: {
2016
- tx?: number;
2017
- ty?: number;
2018
- sx?: number;
2019
- sy?: number;
2020
- flip?: "x" | "y" | "xy";
2021
- algn?: string;
2040
+ readonly tx?: number;
2041
+ readonly ty?: number;
2042
+ readonly sx?: number;
2043
+ readonly sy?: number;
2044
+ readonly flip?: "x" | "y" | "xy";
2045
+ readonly algn?: string;
2022
2046
  };
2023
- rotation?: number;
2024
- flipH?: boolean;
2025
- flipV?: boolean;
2026
- presetGeom?: string;
2047
+ readonly rotation?: number;
2048
+ readonly flipH?: boolean;
2049
+ readonly flipV?: boolean;
2050
+ readonly presetGeom?: string;
2027
2051
  /** N11.b — DrawingML a:softEdge feather radius in EMU. */
2028
- softEdgeRadius?: number;
2052
+ readonly softEdgeRadius?: number;
2029
2053
  /** N11.b — DrawingML a:outerShdw attributes. `dir` is in 60000ths of a degree; `blurRad`/`dist` in EMU. */
2030
- outerShadow?: { blurRad: number; dist: number; dir: number; color: string; colorType: "srgbClr" | "schemeClr" };
2054
+ readonly outerShadow?: { blurRad: number; dist: number; dir: number; color: string; colorType: "srgbClr" | "schemeClr" };
2031
2055
  /** N11.b — DrawingML a:glow radius in EMU + color. */
2032
- glow?: { radius: number; color: string; colorType: "srgbClr" | "schemeClr" };
2056
+ readonly glow?: { radius: number; color: string; colorType: "srgbClr" | "schemeClr" };
2033
2057
  /** Original w:drawing XML slice, preserved for lossless round-trip serialization. */
2034
- rawXml?: string;
2058
+ readonly rawXml?: string;
2035
2059
  }
2036
2060
 
2037
2061
  export interface ShapeContent {
2038
- type: "shape";
2062
+ readonly type: "shape";
2039
2063
  /** Plain-text fallback extracted from `w:txbxContent` when present. */
2040
- text?: string;
2041
- geometry?: string;
2064
+ readonly text?: string;
2065
+ readonly geometry?: string;
2042
2066
  /**
2043
2067
  * Shape fill — solid, gradient, pattern, or none. srgbClr values on solid +
2044
2068
  * gradient stops + pattern fg/bg are normalized to uppercase hex. schemeClr
@@ -2051,26 +2075,26 @@ export interface ShapeContent {
2051
2075
  | { kind: "solid"; color: string; colorType: "srgbClr" | "schemeClr" }
2052
2076
  | { kind: "none" }
2053
2077
  | {
2054
- kind: "gradient";
2055
- stops: Array<{ pos: number; color: string; colorType: "srgbClr" | "schemeClr" }>;
2078
+ readonly kind: "gradient";
2079
+ readonly stops: Array<{ pos: number; color: string; colorType: "srgbClr" | "schemeClr" }>;
2056
2080
  direction:
2057
2081
  | { kind: "linear"; angle: number; scaled?: boolean }
2058
2082
  | { kind: "path"; path: "circle" | "rect" | "shape" };
2059
- rotWithShape?: boolean;
2083
+ readonly rotWithShape?: boolean;
2060
2084
  }
2061
2085
  | {
2062
- kind: "pattern";
2063
- preset: string;
2064
- fg?: { color: string; colorType: "srgbClr" | "schemeClr" };
2065
- bg?: { color: string; colorType: "srgbClr" | "schemeClr" };
2086
+ readonly kind: "pattern";
2087
+ readonly preset: string;
2088
+ readonly fg?: { color: string; colorType: "srgbClr" | "schemeClr" };
2089
+ readonly bg?: { color: string; colorType: "srgbClr" | "schemeClr" };
2066
2090
  };
2067
- line?: { color?: string; widthEmu?: number; noLine?: boolean };
2091
+ readonly line?: { color?: string; widthEmu?: number; noLine?: boolean };
2068
2092
  /** True when the shape's geometry + txbxContent presence make it a text box. */
2069
- isTextBox?: boolean;
2093
+ readonly isTextBox?: boolean;
2070
2094
  /** Text box body layout from `wps:bodyPr` / `a:bodyPr`. */
2071
- textBoxBody?: TextBoxBodyProperties;
2095
+ readonly textBoxBody?: TextBoxBodyProperties;
2072
2096
  /** Raw w:txbxContent XML, preserved for serialization + lossless round-trip. */
2073
- txbxContentXml?: string;
2097
+ readonly txbxContentXml?: string;
2074
2098
  /**
2075
2099
  * Parsed block-level structure from `w:txbxContent`, populated when a
2076
2100
  * `blockParser` callback is supplied during parse (CO4 F3.3).
@@ -2084,22 +2108,22 @@ export interface ShapeContent {
2084
2108
  * contract — unblocks L03 cascade + L11 render walking `txbxBlocks`
2085
2109
  * without `as unknown as BlockNode[]` casts at the consumer site.
2086
2110
  */
2087
- txbxBlocks?: ReadonlyArray<BlockNode>;
2088
- rawXml: string;
2111
+ readonly txbxBlocks?: BlockNode[];
2112
+ readonly rawXml: string;
2089
2113
  }
2090
2114
 
2091
2115
  export interface TextBoxBodyProperties {
2092
2116
  /** OOXML bodyPr anchor: top, center, or bottom. */
2093
- anchor?: "t" | "ctr" | "b";
2094
- insetLeftEmu?: number;
2095
- insetTopEmu?: number;
2096
- insetRightEmu?: number;
2097
- insetBottomEmu?: number;
2117
+ readonly anchor?: "t" | "ctr" | "b";
2118
+ readonly insetLeftEmu?: number;
2119
+ readonly insetTopEmu?: number;
2120
+ readonly insetRightEmu?: number;
2121
+ readonly insetBottomEmu?: number;
2098
2122
  }
2099
2123
 
2100
2124
  export interface DrawingFrameNode {
2101
- type: "drawing_frame";
2102
- anchor: AnchorGeometry;
2125
+ readonly type: "drawing_frame";
2126
+ readonly anchor: AnchorGeometry;
2103
2127
  content:
2104
2128
  | PictureContent
2105
2129
  | ShapeContent
@@ -2109,20 +2133,20 @@ export interface DrawingFrameNode {
2109
2133
  }
2110
2134
 
2111
2135
  export interface OpaqueBlockNode {
2112
- type: "opaque_block";
2113
- fragmentId: string;
2114
- warningId: string;
2115
- rawXml?: string;
2136
+ readonly type: "opaque_block";
2137
+ readonly fragmentId: string;
2138
+ readonly warningId: string;
2139
+ readonly rawXml?: string;
2116
2140
  }
2117
2141
 
2118
2142
  export interface DocRange {
2119
- from: number;
2120
- to: number;
2143
+ readonly from: number;
2144
+ readonly to: number;
2121
2145
  }
2122
2146
 
2123
2147
  export interface BoundaryAssoc {
2124
- start: -1 | 1;
2125
- end: -1 | 1;
2148
+ readonly start: -1 | 1;
2149
+ readonly end: -1 | 1;
2126
2150
  }
2127
2151
 
2128
2152
  export type CanonicalAnchor =
@@ -2144,70 +2168,70 @@ export type CanonicalAnchor =
2144
2168
  };
2145
2169
 
2146
2170
  export interface ReviewStore {
2147
- comments: Record<string, CommentThread>;
2148
- revisions: Record<string, RevisionRecord>;
2171
+ readonly comments: Record<string, CommentThread>;
2172
+ readonly revisions: Record<string, RevisionRecord>;
2149
2173
  }
2150
2174
 
2151
2175
  export interface CommentThread {
2152
- commentId: string;
2153
- status: "open" | "resolved" | "detached";
2154
- anchor: CanonicalAnchor;
2155
- createdAt: ISO8601DateTime;
2156
- createdBy?: string;
2157
- authorId?: string;
2158
- body?: string;
2159
- entries?: CommentEntry[];
2160
- resolution?: CommentResolution;
2161
- resolvedAt?: ISO8601DateTime;
2162
- warningIds: string[];
2163
- isResolved?: boolean;
2164
- metadata?: CommentThreadMetadata;
2176
+ readonly commentId: string;
2177
+ readonly status: "open" | "resolved" | "detached";
2178
+ readonly anchor: CanonicalAnchor;
2179
+ readonly createdAt: ISO8601DateTime;
2180
+ readonly createdBy?: string;
2181
+ readonly authorId?: string;
2182
+ readonly body?: string;
2183
+ readonly entries?: CommentEntry[];
2184
+ readonly resolution?: CommentResolution;
2185
+ readonly resolvedAt?: ISO8601DateTime;
2186
+ readonly warningIds: string[];
2187
+ readonly isResolved?: boolean;
2188
+ readonly metadata?: CommentThreadMetadata;
2165
2189
  }
2166
2190
 
2167
2191
  export interface CommentEntry {
2168
- entryId: string;
2169
- authorId: string;
2170
- createdAt: ISO8601DateTime;
2171
- body: string;
2172
- metadata?: CommentEntryMetadata;
2192
+ readonly entryId: string;
2193
+ readonly authorId: string;
2194
+ readonly createdAt: ISO8601DateTime;
2195
+ readonly body: string;
2196
+ readonly metadata?: CommentEntryMetadata;
2173
2197
  }
2174
2198
 
2175
2199
  export interface CommentEntryMetadata {
2176
- ooxmlCommentId?: string;
2177
- paraId?: string;
2178
- parentParaId?: string;
2179
- durableId?: string;
2180
- initials?: string;
2200
+ readonly ooxmlCommentId?: string;
2201
+ readonly paraId?: string;
2202
+ readonly parentParaId?: string;
2203
+ readonly durableId?: string;
2204
+ readonly initials?: string;
2181
2205
  }
2182
2206
 
2183
2207
  export interface CommentResolution {
2184
- resolvedAt: ISO8601DateTime;
2185
- resolvedBy: string;
2208
+ readonly resolvedAt: ISO8601DateTime;
2209
+ readonly resolvedBy: string;
2186
2210
  }
2187
2211
 
2188
2212
  export interface CommentThreadMetadata {
2189
- source?: "runtime" | "import";
2190
- rootOoxmlCommentId?: string;
2191
- rootParaId?: string;
2213
+ readonly source?: "runtime" | "import";
2214
+ readonly rootOoxmlCommentId?: string;
2215
+ readonly rootParaId?: string;
2192
2216
  /**
2193
2217
  * Runtime-authored discussion thread for a tracked change. The comment
2194
2218
  * anchor points at the revision range; this metadata keeps the review
2195
2219
  * relation stable after remaps and lets suggestion cards surface replies.
2196
2220
  */
2197
- linkedRevisionId?: string;
2198
- detachedReason?: "incomplete-markers" | "multi-paragraph" | "opaque-region" | "revision-overlap";
2199
- actionabilityNote?: string;
2221
+ readonly linkedRevisionId?: string;
2222
+ readonly detachedReason?: "incomplete-markers" | "multi-paragraph" | "opaque-region" | "revision-overlap";
2223
+ readonly actionabilityNote?: string;
2200
2224
  }
2201
2225
 
2202
2226
  export interface RevisionPropertyChangeData {
2203
- xmlTag: "pPrChange" | "sectPrChange" | "tblPrChange" | "rPrChange";
2204
- beforeXml: string;
2227
+ readonly xmlTag: "pPrChange" | "sectPrChange" | "tblPrChange" | "rPrChange";
2228
+ readonly beforeXml: string;
2205
2229
  }
2206
2230
 
2207
2231
  export interface RevisionMoveData {
2208
- moveId: string;
2209
- direction: "from" | "to";
2210
- linkedRevisionId?: string;
2232
+ readonly moveId: string;
2233
+ readonly direction: "from" | "to";
2234
+ readonly linkedRevisionId?: string;
2211
2235
  }
2212
2236
 
2213
2237
  export type RevisionStoryTargetRecord =
@@ -2228,21 +2252,21 @@ export type RevisionStoryTargetRecord =
2228
2252
  | { kind: "endnote"; noteId: string };
2229
2253
 
2230
2254
  export interface RevisionRecord {
2231
- changeId: string;
2232
- kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
2233
- anchor: CanonicalAnchor;
2234
- authorId?: string;
2235
- createdAt: ISO8601DateTime;
2236
- warningIds?: string[];
2237
- metadata?: RevisionMetadataRecord;
2238
- status: "open" | "accepted" | "rejected" | "detached";
2255
+ readonly changeId: string;
2256
+ readonly kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
2257
+ readonly anchor: CanonicalAnchor;
2258
+ readonly authorId?: string;
2259
+ readonly createdAt: ISO8601DateTime;
2260
+ readonly warningIds?: string[];
2261
+ readonly metadata?: RevisionMetadataRecord;
2262
+ readonly status: "open" | "accepted" | "rejected" | "detached";
2239
2263
  }
2240
2264
 
2241
2265
  export interface RevisionMetadataRecord {
2242
- source?: "runtime" | "import";
2243
- storyTarget?: RevisionStoryTargetRecord;
2244
- preserveOnlyReason?: string;
2245
- suggestionId?: string;
2266
+ readonly source?: "runtime" | "import";
2267
+ readonly storyTarget?: RevisionStoryTargetRecord;
2268
+ readonly preserveOnlyReason?: string;
2269
+ readonly suggestionId?: string;
2246
2270
  semanticKind?:
2247
2271
  | "insertion"
2248
2272
  | "deletion"
@@ -2251,49 +2275,49 @@ export interface RevisionMetadataRecord {
2251
2275
  | "paragraph-property-change"
2252
2276
  | "structural-change"
2253
2277
  | "object-change";
2254
- linkedRevisionIds?: string[];
2255
- predecessorSuggestionId?: string;
2278
+ readonly linkedRevisionIds?: string[];
2279
+ readonly predecessorSuggestionId?: string;
2256
2280
  importedRevisionForm?:
2257
2281
  | "run-insertion"
2258
2282
  | "run-deletion"
2259
2283
  | "paragraph-insertion"
2260
2284
  | "paragraph-deletion";
2261
- originalRevisionType?: string;
2262
- ooxmlRevisionId?: string;
2263
- propertyChangeData?: RevisionPropertyChangeData;
2264
- moveData?: RevisionMoveData;
2285
+ readonly originalRevisionType?: string;
2286
+ readonly ooxmlRevisionId?: string;
2287
+ readonly propertyChangeData?: RevisionPropertyChangeData;
2288
+ readonly moveData?: RevisionMoveData;
2265
2289
  }
2266
2290
 
2267
2291
  export interface PreservationStore {
2268
- opaqueFragments: Record<string, OpaqueFragmentRecord>;
2269
- packageParts: Record<string, PreservedPackagePart>;
2292
+ readonly opaqueFragments: Record<string, OpaqueFragmentRecord>;
2293
+ readonly packageParts: Record<string, PreservedPackagePart>;
2270
2294
  }
2271
2295
 
2272
2296
  export interface OpaqueFragmentRecord {
2273
- fragmentId: string;
2274
- payloadKind: "xml-subtree" | "package-part";
2275
- payloadReference: string;
2276
- featureClass: "preserve-only";
2277
- lastKnownRange: DocRange;
2278
- warningId: string;
2279
- packagePartName?: string;
2280
- relationshipId?: string;
2297
+ readonly fragmentId: string;
2298
+ readonly payloadKind: "xml-subtree" | "package-part";
2299
+ readonly payloadReference: string;
2300
+ readonly featureClass: "preserve-only";
2301
+ readonly lastKnownRange: DocRange;
2302
+ readonly warningId: string;
2303
+ readonly packagePartName?: string;
2304
+ readonly relationshipId?: string;
2281
2305
  }
2282
2306
 
2283
2307
  export interface PreservedPackagePart {
2284
- packagePartName: string;
2285
- contentType: string;
2286
- relationshipIds: string[];
2308
+ readonly packagePartName: string;
2309
+ readonly contentType: string;
2310
+ readonly relationshipIds: string[];
2287
2311
  }
2288
2312
 
2289
2313
  export interface DiagnosticStore {
2290
- warnings: DiagnosticWarningEntry[];
2291
- errors: DiagnosticErrorEntry[];
2314
+ readonly warnings: DiagnosticWarningEntry[];
2315
+ readonly errors: DiagnosticErrorEntry[];
2292
2316
  }
2293
2317
 
2294
2318
  export interface DiagnosticWarningEntry {
2295
- diagnosticId: string;
2296
- warningId: string;
2319
+ readonly diagnosticId: string;
2320
+ readonly warningId: string;
2297
2321
  source:
2298
2322
  | "import"
2299
2323
  | "runtime"
@@ -2301,11 +2325,11 @@ export interface DiagnosticWarningEntry {
2301
2325
  | "preservation"
2302
2326
  | "validation"
2303
2327
  | "export";
2304
- message: string;
2328
+ readonly message: string;
2305
2329
  }
2306
2330
 
2307
2331
  export interface DiagnosticErrorEntry {
2308
- diagnosticId: string;
2332
+ readonly diagnosticId: string;
2309
2333
  code:
2310
2334
  | "load_failed"
2311
2335
  | "import_failed"
@@ -2314,10 +2338,10 @@ export interface DiagnosticErrorEntry {
2314
2338
  | "validation_failed"
2315
2339
  | "datastore_failed"
2316
2340
  | "internal_invariant";
2317
- message: string;
2318
- isFatal: boolean;
2319
- source: "import" | "runtime" | "validation" | "datastore" | "host" | "export";
2320
- details?: unknown;
2341
+ readonly message: string;
2342
+ readonly isFatal: boolean;
2343
+ readonly source: "import" | "runtime" | "validation" | "datastore" | "host" | "export";
2344
+ readonly details?: unknown;
2321
2345
  }
2322
2346
 
2323
2347
  export function createCanonicalDocument(
@@ -2408,6 +2432,9 @@ export function repairCanonicalDocumentEnvelope(
2408
2432
  });
2409
2433
  const record = isPlainRecord(value) ? value : {};
2410
2434
  const metadata = isPlainRecord(record.metadata) ? record.metadata : {};
2435
+ const metadataRest = { ...metadata };
2436
+ delete metadataRest.customProperties;
2437
+ delete metadataRest.appProperties;
2411
2438
  const customProperties = isPlainRecord(metadata.customProperties)
2412
2439
  ? (Object.fromEntries(
2413
2440
  Object.entries(metadata.customProperties).filter(
@@ -2415,6 +2442,15 @@ export function repairCanonicalDocumentEnvelope(
2415
2442
  ),
2416
2443
  ) as Record<string, string>)
2417
2444
  : {};
2445
+ const appProperties = isPlainRecord(metadata.appProperties)
2446
+ ? (Object.fromEntries(
2447
+ Object.entries(metadata.appProperties).filter(
2448
+ ([, entry]) =>
2449
+ typeof entry === "string" ||
2450
+ (typeof entry === "number" && Number.isFinite(entry) && entry >= 0),
2451
+ ),
2452
+ ) as DocumentAppProperties)
2453
+ : undefined;
2418
2454
 
2419
2455
  const envelope: Mutable<CanonicalDocument> = {
2420
2456
  ...base,
@@ -2430,7 +2466,8 @@ export function repairCanonicalDocumentEnvelope(
2430
2466
  : base.updatedAt,
2431
2467
  metadata: {
2432
2468
  ...base.metadata,
2433
- ...metadata,
2469
+ ...metadataRest,
2470
+ ...(appProperties !== undefined ? { appProperties } : {}),
2434
2471
  customProperties,
2435
2472
  } as CanonicalDocument["metadata"],
2436
2473
  styles:
@@ -2614,6 +2651,74 @@ function validateMetadata(
2614
2651
  return;
2615
2652
  }
2616
2653
 
2654
+ for (const field of [
2655
+ "title",
2656
+ "subject",
2657
+ "description",
2658
+ "creator",
2659
+ "language",
2660
+ "category",
2661
+ "lastModifiedBy",
2662
+ "contentStatus",
2663
+ "revision",
2664
+ "version",
2665
+ "createdUtc",
2666
+ "modifiedUtc",
2667
+ "importMode",
2668
+ ]) {
2669
+ if (record[field] !== undefined && typeof record[field] !== "string") {
2670
+ issues.push({
2671
+ path: `${path}.${field}`,
2672
+ message: "metadata string fields must be strings when present.",
2673
+ });
2674
+ }
2675
+ }
2676
+
2677
+ if (
2678
+ record.keywords !== undefined &&
2679
+ (!Array.isArray(record.keywords) ||
2680
+ record.keywords.some((entry) => typeof entry !== "string"))
2681
+ ) {
2682
+ issues.push({
2683
+ path: `${path}.keywords`,
2684
+ message: "metadata keywords must be an array of strings when present.",
2685
+ });
2686
+ }
2687
+
2688
+ if (record.appProperties !== undefined) {
2689
+ const appProperties = asPlainObject(record.appProperties, `${path}.appProperties`, issues);
2690
+ if (appProperties) {
2691
+ for (const field of ["application", "appVersion", "template", "company", "manager"]) {
2692
+ if (appProperties[field] !== undefined && typeof appProperties[field] !== "string") {
2693
+ issues.push({
2694
+ path: `${path}.appProperties.${field}`,
2695
+ message: "appProperties string fields must be strings when present.",
2696
+ });
2697
+ }
2698
+ }
2699
+ for (const field of [
2700
+ "pages",
2701
+ "words",
2702
+ "characters",
2703
+ "charactersWithSpaces",
2704
+ "totalTime",
2705
+ "docSecurity",
2706
+ ]) {
2707
+ if (
2708
+ appProperties[field] !== undefined &&
2709
+ (typeof appProperties[field] !== "number" ||
2710
+ !Number.isFinite(appProperties[field]) ||
2711
+ appProperties[field] < 0)
2712
+ ) {
2713
+ issues.push({
2714
+ path: `${path}.appProperties.${field}`,
2715
+ message: "appProperties numeric fields must be finite non-negative numbers when present.",
2716
+ });
2717
+ }
2718
+ }
2719
+ }
2720
+ }
2721
+
2617
2722
  const customProperties = asPlainObject(record.customProperties, `${path}.customProperties`, issues);
2618
2723
  if (!customProperties) {
2619
2724
  return;