@beyondwork/docx-react-component 1.0.102 → 1.0.104

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 (57) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +63 -1
  3. package/src/api/v3/_runtime-handle.ts +2 -0
  4. package/src/api/v3/ai/outline.ts +2 -7
  5. package/src/api/v3/runtime/geometry.ts +79 -0
  6. package/src/core/commands/formatting-commands.ts +8 -7
  7. package/src/core/commands/paragraph-layout-commands.ts +11 -10
  8. package/src/core/commands/section-layout-commands.ts +7 -6
  9. package/src/core/commands/style-commands.ts +3 -2
  10. package/src/io/normalize/normalize-text.ts +6 -5
  11. package/src/io/ooxml/parse-anchor.ts +15 -15
  12. package/src/io/ooxml/parse-drawing.ts +103 -5
  13. package/src/io/ooxml/parse-fields.ts +43 -21
  14. package/src/io/ooxml/parse-font-table.ts +2 -1
  15. package/src/io/ooxml/parse-footnotes.ts +3 -2
  16. package/src/io/ooxml/parse-headers-footers.ts +7 -6
  17. package/src/io/ooxml/parse-main-document.ts +41 -40
  18. package/src/io/ooxml/parse-numbering.ts +3 -2
  19. package/src/io/ooxml/parse-object.ts +6 -6
  20. package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
  21. package/src/io/ooxml/parse-picture.ts +16 -16
  22. package/src/io/ooxml/parse-run-formatting.ts +11 -10
  23. package/src/io/ooxml/parse-settings.ts +2 -1
  24. package/src/io/ooxml/parse-shapes.ts +148 -17
  25. package/src/io/ooxml/parse-styles.ts +16 -16
  26. package/src/io/ooxml/parse-theme.ts +5 -4
  27. package/src/model/canonical-document.ts +869 -836
  28. package/src/model/canonical-layout-inputs.ts +979 -0
  29. package/src/model/layout/index.ts +6 -0
  30. package/src/model/layout/page-graph-types.ts +61 -0
  31. package/src/model/layout/runtime-page-graph-types.ts +10 -0
  32. package/src/runtime/collab/runtime-collab-sync.ts +3 -3
  33. package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
  34. package/src/runtime/document-runtime.ts +30 -14
  35. package/src/runtime/event-refresh-hints.ts +3 -0
  36. package/src/runtime/formatting/document-lookup.ts +3 -2
  37. package/src/runtime/formatting/formatting-context.ts +176 -34
  38. package/src/runtime/formatting/index.ts +20 -0
  39. package/src/runtime/formatting/layout-inputs.ts +320 -0
  40. package/src/runtime/formatting/numbering/geometry.ts +13 -12
  41. package/src/runtime/formatting/style-cascade.ts +2 -1
  42. package/src/runtime/formatting/table-style-resolver.ts +8 -7
  43. package/src/runtime/geometry/caret-geometry.ts +82 -10
  44. package/src/runtime/geometry/geometry-facet.ts +36 -0
  45. package/src/runtime/geometry/geometry-index.ts +891 -0
  46. package/src/runtime/geometry/geometry-types.ts +221 -1
  47. package/src/runtime/geometry/index.ts +26 -0
  48. package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
  49. package/src/runtime/geometry/replacement-envelope.ts +41 -2
  50. package/src/runtime/layout/layout-engine-version.ts +16 -1
  51. package/src/runtime/layout/page-graph.ts +191 -1
  52. package/src/runtime/prerender/graph-canonicalize.ts +30 -0
  53. package/src/runtime/surface-projection.ts +74 -39
  54. package/src/runtime/workflow/coordinator.ts +57 -11
  55. package/src/session/import/normalize.ts +2 -1
  56. package/src/session/import/source-package-evidence.ts +612 -1
  57. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.102",
4
+ "version": "1.0.104",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "type": "module",
7
7
  "sideEffects": [
@@ -1223,6 +1223,23 @@ export interface SurfacePictureEffects {
1223
1223
  glow?: { radius: number; color: string; colorType: "srgbClr" | "schemeClr" };
1224
1224
  }
1225
1225
 
1226
+ export interface SurfacePreserveOnlyObjectSizing {
1227
+ sourceId?: string;
1228
+ display: "inline" | "floating" | "unknown";
1229
+ extentEmu?: { widthEmu: number; heightEmu: number };
1230
+ fallbackHint:
1231
+ | "drawing-inline"
1232
+ | "drawing-floating"
1233
+ | "chart"
1234
+ | "smartart"
1235
+ | "shape"
1236
+ | "wordart"
1237
+ | "vml-shape"
1238
+ | "ole-object"
1239
+ | "opaque-object";
1240
+ relationshipIds?: string[];
1241
+ }
1242
+
1226
1243
  export type SurfaceInlineSegment =
1227
1244
  | {
1228
1245
  segmentId: string;
@@ -1338,6 +1355,20 @@ export type SurfaceInlineSegment =
1338
1355
  * can render `ChartSurface` instead of the fallback bitmap or badge.
1339
1356
  */
1340
1357
  parsedChartId?: string;
1358
+ /**
1359
+ * PE2 preserve-only object handoff. Present for unsupported drawing,
1360
+ * chart, SmartArt, WordArt, VML, and opaque-object previews when import
1361
+ * could recover source ids, relationship ids, fallback hints, or
1362
+ * extents. Layout consumers may size placeholders from this metadata;
1363
+ * it is not a layout computation.
1364
+ */
1365
+ preserveOnlyObject?: SurfacePreserveOnlyObjectSizing;
1366
+ /**
1367
+ * Drawing anchor geometry for preserve-only objects that originated in a
1368
+ * `drawing_frame`. Mirrors image/shape anchor semantics so L04 can join
1369
+ * the object to source/canonical/layout rows without re-parsing OOXML.
1370
+ */
1371
+ anchor?: SurfaceDrawingAnchor;
1341
1372
  state: "locked-preserve-only";
1342
1373
  }
1343
1374
  | {
@@ -1403,6 +1434,7 @@ export type SurfaceInlineSegment =
1403
1434
  isTextBox?: boolean;
1404
1435
  /** Text box body layout from `wps:bodyPr` / `a:bodyPr`. */
1405
1436
  textBoxBody?: SurfaceTextBoxBodyProperties;
1437
+ preserveOnlyObject?: SurfacePreserveOnlyObjectSizing;
1406
1438
  /** First-paragraph plain-text preview when `isTextBox` is true. */
1407
1439
  txbxText?: string;
1408
1440
  /** Run-level marks from the first text-box text run, when available. */
@@ -3609,6 +3641,11 @@ export interface WordReviewEditorPasteEvent {
3609
3641
  source: "paste" | "drop";
3610
3642
  }
3611
3643
 
3644
+ export interface WorkflowEventOrigin {
3645
+ readonly source: "api" | "runtime" | "collab" | string;
3646
+ readonly at?: string;
3647
+ }
3648
+
3612
3649
  export type WordReviewEditorEvent =
3613
3650
  | {
3614
3651
  type: "ready";
@@ -3724,16 +3761,38 @@ export type WordReviewEditorEvent =
3724
3761
  type: "workflow_overlay_changed";
3725
3762
  documentId: string;
3726
3763
  snapshot: WorkflowScopeSnapshot;
3764
+ origin?: WorkflowEventOrigin;
3727
3765
  }
3728
3766
  | {
3729
3767
  type: "workflow_active_work_item_changed";
3730
3768
  documentId: string;
3731
3769
  activeWorkItemId: string | null;
3770
+ origin?: WorkflowEventOrigin;
3732
3771
  }
3733
3772
  | {
3734
3773
  type: "workflow_metadata_changed";
3735
3774
  documentId: string;
3736
3775
  snapshot: WorkflowMetadataSnapshot;
3776
+ origin?: WorkflowEventOrigin;
3777
+ }
3778
+ | {
3779
+ type: "workflow_shared_state_changed";
3780
+ documentId: string;
3781
+ state: SharedWorkflowState | null;
3782
+ origin: WorkflowEventOrigin;
3783
+ }
3784
+ | {
3785
+ type: "workflow_visibility_policy_changed";
3786
+ documentId: string;
3787
+ kind?: OverlayKind;
3788
+ policy?: OverlayVisibilityPolicy | null;
3789
+ origin: WorkflowEventOrigin;
3790
+ }
3791
+ | {
3792
+ type: "workflow_markup_mode_policy_changed";
3793
+ documentId: string;
3794
+ policy: WorkflowMarkupModePolicy | null;
3795
+ origin: WorkflowEventOrigin;
3737
3796
  }
3738
3797
  | {
3739
3798
  type: "host_annotation_overlay_changed";
@@ -5010,7 +5069,10 @@ export interface WordReviewEditorRef {
5010
5069
  * `replaceText` calls to block with `workflow_comment_only`.
5011
5070
  */
5012
5071
  getWorkflowOverlay(): WorkflowOverlay | null;
5013
- setSharedWorkflowState(state: SharedWorkflowState | null): void;
5072
+ setSharedWorkflowState(
5073
+ state: SharedWorkflowState | null,
5074
+ origin?: WorkflowEventOrigin,
5075
+ ): void;
5014
5076
  getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
5015
5077
  getInteractionGuardSnapshot(): InteractionGuardSnapshot;
5016
5078
  getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
@@ -48,6 +48,7 @@ export type RuntimeApiHandle = Pick<
48
48
  | "getCompatibilityReport"
49
49
  | "getWarnings"
50
50
  | "getRenderSnapshot"
51
+ | "getDocumentNavigationSnapshot"
51
52
  // Canonical document read (ai.inspect + ai.bundle + ai.resolve families,
52
53
  // added in refactor/08 Slice 2/3 graduations)
53
54
  | "getCanonicalDocument"
@@ -155,6 +156,7 @@ export const RUNTIME_API_HANDLE_SHAPE_CHECK: Record<keyof RuntimeApiHandle, true
155
156
  getCompatibilityReport: true,
156
157
  getWarnings: true,
157
158
  getRenderSnapshot: true,
159
+ getDocumentNavigationSnapshot: true,
158
160
  getCanonicalDocument: true,
159
161
  findAllText: true,
160
162
  replaceText: true,
@@ -19,7 +19,6 @@ import type {
19
19
  DocumentOutlineHeadingSnapshot,
20
20
  DocumentOutlineSnapshot,
21
21
  } from "../../public-types.ts";
22
- import { createDocumentNavigationSnapshot } from "../../../runtime/document-navigation.ts";
23
22
  import { createDocumentOutlineSnapshot } from "../../../runtime/document-outline.ts";
24
23
  import {
25
24
  computeBlockPositions,
@@ -68,7 +67,7 @@ export const getDocumentOutlineMetadata: ApiV3FnMetadata = {
68
67
  stateClass: "A-canonical",
69
68
  persistsTo: "canonical",
70
69
  rwdReference:
71
- "§AI API § ai.getDocumentOutline. Composes createDocumentNavigationSnapshot (L04/07) with createDocumentOutlineSnapshot (L07) to surface the heading tree. Read-only; no audit emission.",
70
+ "§AI API § ai.getDocumentOutline. Composes runtime.getDocumentNavigationSnapshot() (L04 graph-derived navigation) with createDocumentOutlineSnapshot (L07) to surface the heading tree. Read-only; no audit emission.",
72
71
  };
73
72
 
74
73
  export function createOutlineFamily(runtime: RuntimeApiHandle) {
@@ -82,11 +81,7 @@ export function createOutlineFamily(runtime: RuntimeApiHandle) {
82
81
  const snapshot = runtime.getRenderSnapshot();
83
82
  const document = runtime.getCanonicalDocument();
84
83
  const selectionHead = snapshot.selection.head;
85
- const navigation = createDocumentNavigationSnapshot(
86
- document,
87
- selectionHead,
88
- snapshot.activeStory,
89
- );
84
+ const navigation = runtime.getDocumentNavigationSnapshot();
90
85
  const outline = createDocumentOutlineSnapshot({
91
86
  navigation,
92
87
  activeStory: snapshot.activeStory,
@@ -15,6 +15,26 @@ import type { RuntimeApiHandle } from "../_runtime-handle.ts";
15
15
  import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
16
16
  import { mockPayload } from "../_mocks.ts";
17
17
  import type { MockPayload } from "../_layer-metadata.ts";
18
+ import type {
19
+ GeometryIndex,
20
+ GeometryIndexCoverage,
21
+ } from "../../../runtime/geometry/index.ts";
22
+
23
+ function createUnavailableGeometryCoverage(): GeometryIndexCoverage {
24
+ return {
25
+ status: "unavailable",
26
+ pageCount: 0,
27
+ regionCount: 0,
28
+ sliceCount: 0,
29
+ lineCount: 0,
30
+ anchorCount: 0,
31
+ hitTargetCount: 0,
32
+ semanticEntryCount: 0,
33
+ replacementEnvelopeCount: 0,
34
+ objectHandleCount: 0,
35
+ precision: { exact: 0, "within-tolerance": 0, heuristic: 0 },
36
+ };
37
+ }
18
38
 
19
39
  export interface BlockRectEntry {
20
40
  readonly blockId: string;
@@ -57,6 +77,50 @@ export interface HitTestResult {
57
77
  readonly __mock?: true;
58
78
  }
59
79
 
80
+ /* ================================================================== */
81
+ /* PE2 geometry index + coverage */
82
+ /* ================================================================== */
83
+
84
+ export const getGeometryIndexMetadata: ApiV3FnMetadata = {
85
+ name: "runtime.geometry.getGeometryIndex",
86
+ status: "live-with-adapter",
87
+ sourceLayer: "geometry-projection",
88
+ liveEvidence: {
89
+ runnerTest: "test/api/v3/geometry-uses-geometry-handle.test.ts",
90
+ commit: "refactor-07-pe2-geometry-index-read-surface",
91
+ },
92
+ uxIntent: { uiVisible: false, expectsUxResponse: "none" },
93
+ agentMetadata: {
94
+ readOrMutate: "read",
95
+ boundedScope: "document",
96
+ auditCategory: "geometry-read",
97
+ },
98
+ stateClass: "A-canonical",
99
+ persistsTo: "canonical",
100
+ rwdReference:
101
+ "§Runtime API § runtime.geometry.getGeometryIndex. PE2 read surface over GeometryFacet.getGeometryIndex(): renderer-neutral pages, regions, slices, lines, anchors, hit targets, semantic entries, replacement envelopes, object handles, and coverage. Returns null when no geometry facet or render frame is warm. Promotes to live when L05 projects directly from L04 page slices instead of the render-frame adapter.",
102
+ };
103
+
104
+ export const getGeometryCoverageMetadata: ApiV3FnMetadata = {
105
+ name: "runtime.geometry.getGeometryCoverage",
106
+ status: "live-with-adapter",
107
+ sourceLayer: "geometry-projection",
108
+ liveEvidence: {
109
+ runnerTest: "test/api/v3/geometry-uses-geometry-handle.test.ts",
110
+ commit: "refactor-07-pe2-geometry-index-read-surface",
111
+ },
112
+ uxIntent: { uiVisible: false, expectsUxResponse: "none" },
113
+ agentMetadata: {
114
+ readOrMutate: "read",
115
+ boundedScope: "document",
116
+ auditCategory: "geometry-read",
117
+ },
118
+ stateClass: "A-canonical",
119
+ persistsTo: "canonical",
120
+ rwdReference:
121
+ "§Runtime API § runtime.geometry.getGeometryCoverage. PE2 lightweight status read over GeometryFacet.getGeometryCoverage(). Always returns a coverage object; unwired/pre-paint states report status:'unavailable' with zero counts so agents and evidence runners can distinguish no geometry from empty geometry.",
122
+ };
123
+
60
124
  /* ================================================================== */
61
125
  /* getCaret — Slice-4 caret geometry (Slice X3 of §4 of coord-05) */
62
126
  /* ================================================================== */
@@ -224,6 +288,21 @@ export function createGeometryFamily(runtime: RuntimeApiHandle) {
224
288
  const geometry = runtime.geometry;
225
289
 
226
290
  return {
291
+ getGeometryIndex(): GeometryIndex | null {
292
+ // @endStateApi — live-with-adapter. Exposes the Layer-05 PE2
293
+ // geometry index through the v3 runtime seam. The current L05
294
+ // index is projected from the render frame; callers still get
295
+ // renderer-neutral value objects and no DOM/PM/runtime instances.
296
+ return geometry?.getGeometryIndex() ?? null;
297
+ },
298
+
299
+ getGeometryCoverage(): GeometryIndexCoverage {
300
+ // @endStateApi — live-with-adapter. Coverage is intentionally
301
+ // total: an unwired/pre-paint handle reports an unavailable zero
302
+ // summary instead of null so evidence code can branch on status.
303
+ return geometry?.getGeometryCoverage() ?? createUnavailableGeometryCoverage();
304
+ },
305
+
227
306
  getBlockRects(blockIds: readonly string[]): ReadonlyArray<BlockRectEntry & Partial<MockPayload>> {
228
307
  // @endStateApi — live-with-adapter. Routes directly through the
229
308
  // Layer-05 GeometryFacet's `getBlockRects(blockId)`, which walks
@@ -37,6 +37,7 @@ import type {
37
37
  ParagraphNode,
38
38
  TextMark,
39
39
  TextNode,
40
+ Mutable,
40
41
  } from "../../model/canonical-document.ts";
41
42
 
42
43
  // ---------------------------------------------------------------------------
@@ -659,7 +660,7 @@ export function applyTextMarkOperationToDocumentRange(
659
660
  updateMarks,
660
661
  );
661
662
  if (transformed.changed) {
662
- block.children = transformed.nodes;
663
+ (block as Mutable<typeof block>).children = transformed.nodes;
663
664
  changed = true;
664
665
  }
665
666
  }
@@ -862,7 +863,7 @@ function resolveMarkUpdater(
862
863
  }
863
864
 
864
865
  function applyAlignment(
865
- paragraph: ParagraphNode,
866
+ paragraph: Mutable<ParagraphNode>,
866
867
  alignment: FormattingAlignment,
867
868
  ): boolean {
868
869
  const nextAlignment = alignment === "justify" ? "both" : alignment;
@@ -880,7 +881,7 @@ function applyAlignment(
880
881
  * must clone first if the source is shared. Returns `false` when no change
881
882
  * occurred (already at the 0 / 8 bound, or no-op).
882
883
  */
883
- export function applyIndentation(paragraph: ParagraphNode, delta: -1 | 1): boolean {
884
+ export function applyIndentation(paragraph: Mutable<ParagraphNode>, delta: -1 | 1): boolean {
884
885
  if (paragraph.numbering) {
885
886
  const nextLevel = clamp(paragraph.numbering.level + delta, 0, 8);
886
887
  if (nextLevel === paragraph.numbering.level) {
@@ -904,10 +905,10 @@ export function applyIndentation(paragraph: ParagraphNode, delta: -1 | 1): boole
904
905
  ...(paragraph.indentation ?? {}),
905
906
  };
906
907
  if (nextLeft > 0) {
907
- nextIndentation.left = nextLeft;
908
+ (nextIndentation as Mutable<typeof nextIndentation>).left = nextLeft;
908
909
  paragraph.indentation = nextIndentation;
909
910
  } else if (paragraph.indentation) {
910
- delete nextIndentation.left;
911
+ delete (nextIndentation as Mutable<typeof nextIndentation>).left;
911
912
  paragraph.indentation =
912
913
  Object.keys(nextIndentation).length > 0 ? nextIndentation : undefined;
913
914
  }
@@ -1042,7 +1043,7 @@ function getConsistentValue<TItem, TValue>(
1042
1043
  function visitParagraphBindings(
1043
1044
  blocks: BlockNode[],
1044
1045
  surfaceBlocks: SurfaceBlockSnapshot[],
1045
- visitor: (paragraph: ParagraphNode, surface: ParagraphSurfaceBlock) => void,
1046
+ visitor: (paragraph: Mutable<ParagraphNode>, surface: ParagraphSurfaceBlock) => void,
1046
1047
  ): void {
1047
1048
  for (let index = 0; index < Math.min(blocks.length, surfaceBlocks.length); index += 1) {
1048
1049
  const block = blocks[index];
@@ -1146,7 +1147,7 @@ function transformInlineNodes(
1146
1147
  }
1147
1148
 
1148
1149
  function transformTextNode(
1149
- node: TextNode,
1150
+ node: Mutable<TextNode>,
1150
1151
  start: number,
1151
1152
  selectionFrom: number,
1152
1153
  selectionTo: number,
@@ -11,6 +11,7 @@ import type {
11
11
  TableCellNode,
12
12
  TableNode,
13
13
  TableRowNode,
14
+ Mutable,
14
15
  } from "../../model/canonical-document.ts";
15
16
 
16
17
  export interface ParagraphLayoutCommandContext {
@@ -26,7 +27,7 @@ export interface ParagraphLayoutMutationResult {
26
27
  export function setActiveParagraphIndentation(
27
28
  document: CanonicalDocumentEnvelope,
28
29
  snapshot: RuntimeRenderSnapshot,
29
- indentation: ParagraphIndentation,
30
+ indentation: Mutable<ParagraphIndentation>,
30
31
  _context: ParagraphLayoutCommandContext,
31
32
  ): ParagraphLayoutMutationResult {
32
33
  return mutateActiveParagraph(document, snapshot, (paragraph) => {
@@ -66,7 +67,7 @@ export function setActiveParagraphTabStops(
66
67
  function mutateActiveParagraph(
67
68
  document: CanonicalDocumentEnvelope,
68
69
  snapshot: RuntimeRenderSnapshot,
69
- mutate: (paragraph: ParagraphNode) => boolean,
70
+ mutate: (paragraph: Mutable<ParagraphNode>) => boolean,
70
71
  ): ParagraphLayoutMutationResult {
71
72
  const surface = snapshot.surface;
72
73
  if (!surface) {
@@ -190,27 +191,27 @@ function collectCanonicalParagraphs(
190
191
  return output;
191
192
  }
192
193
 
193
- function collectParagraphsFromTable(table: TableNode, output: ParagraphNode[]): void {
194
+ function collectParagraphsFromTable(table: Mutable<TableNode>, output: ParagraphNode[]): void {
194
195
  for (const row of table.rows) {
195
196
  collectParagraphsFromRow(row, output);
196
197
  }
197
198
  }
198
199
 
199
- function collectParagraphsFromRow(row: TableRowNode, output: ParagraphNode[]): void {
200
+ function collectParagraphsFromRow(row: Mutable<TableRowNode>, output: ParagraphNode[]): void {
200
201
  for (const cell of row.cells) {
201
202
  collectParagraphsFromCell(cell, output);
202
203
  }
203
204
  }
204
205
 
205
- function collectParagraphsFromCell(cell: TableCellNode, output: ParagraphNode[]): void {
206
+ function collectParagraphsFromCell(cell: Mutable<TableCellNode>, output: ParagraphNode[]): void {
206
207
  collectCanonicalParagraphs(cell.children, output);
207
208
  }
208
209
 
209
210
  function mergeIndentationPatch(
210
211
  current: ParagraphIndentation | undefined,
211
- patch: ParagraphIndentation,
212
+ patch: Mutable<ParagraphIndentation>,
212
213
  ): ParagraphIndentation | undefined {
213
- const merged: ParagraphIndentation = {
214
+ const merged: Mutable<ParagraphIndentation> = {
214
215
  ...(current ?? {}),
215
216
  };
216
217
 
@@ -237,9 +238,9 @@ function mergeIndentationPatch(
237
238
  }
238
239
 
239
240
  function normalizeIndentation(
240
- indentation: ParagraphIndentation,
241
+ indentation: Mutable<ParagraphIndentation>,
241
242
  ): ParagraphIndentation | undefined {
242
- const normalized: ParagraphIndentation = {};
243
+ const normalized: Mutable<ParagraphIndentation> = {};
243
244
  if (indentation.left !== undefined && indentation.left > 0) {
244
245
  normalized.left = Math.round(indentation.left);
245
246
  }
@@ -250,7 +251,7 @@ function normalizeIndentation(
250
251
  normalized.firstLine = Math.round(indentation.firstLine);
251
252
  }
252
253
  if (indentation.hanging !== undefined && indentation.hanging > 0) {
253
- normalized.hanging = Math.round(indentation.hanging);
254
+ (normalized as Mutable<typeof normalized>).hanging = Math.round(indentation.hanging);
254
255
  delete normalized.firstLine;
255
256
  }
256
257
  return Object.keys(normalized).length > 0 ? normalized : undefined;
@@ -21,6 +21,7 @@ import type {
21
21
  PageSize,
22
22
  SectionBreakNode,
23
23
  SectionProperties,
24
+ Mutable,
24
25
  } from "../../model/canonical-document.ts";
25
26
  import type {
26
27
  MarginPresetDefinition,
@@ -145,7 +146,7 @@ export function insertSectionBreak(
145
146
 
146
147
  const sectionTarget = resolveSectionTarget(cloned, surface.blocks, position);
147
148
  const inheritedProperties = cloneSectionProperties(sectionTarget?.properties);
148
- const sectionBreak: SectionBreakNode = {
149
+ const sectionBreak: Mutable<SectionBreakNode> = {
149
150
  type: "section_break",
150
151
  sectionProperties: {
151
152
  ...inheritedProperties,
@@ -178,7 +179,7 @@ export function insertSectionBreakAfterSectionIndex(
178
179
  const inheritedProperties = cloneSectionProperties(
179
180
  getSectionPropertiesAtIndex(cloned, sectionIndex),
180
181
  );
181
- const sectionBreak: SectionBreakNode = {
182
+ const sectionBreak: Mutable<SectionBreakNode> = {
182
183
  type: "section_break",
183
184
  sectionProperties: {
184
185
  ...inheritedProperties,
@@ -364,9 +365,9 @@ export function setSectionPageNumberingAtSectionIndex(
364
365
 
365
366
  const nextProperties = cloneSectionProperties(target.properties);
366
367
  if (pageNumbering === null) {
367
- delete nextProperties.pageNumbering;
368
+ delete (nextProperties as Mutable<typeof nextProperties>).pageNumbering;
368
369
  } else {
369
- nextProperties.pageNumbering = {
370
+ (nextProperties as Mutable<typeof nextProperties>).pageNumbering = {
370
371
  ...(target.properties?.pageNumbering ?? {}),
371
372
  ...pageNumbering,
372
373
  };
@@ -683,10 +684,10 @@ function findNearestSectionBreak(
683
684
  }
684
685
 
685
686
  function applySectionLayoutPatch(
686
- existing: SectionProperties,
687
+ existing: Mutable<SectionProperties>,
687
688
  patch: SectionLayoutPatch,
688
689
  ): SectionProperties {
689
- const result: SectionProperties = { ...existing };
690
+ const result: Mutable<SectionProperties> = { ...existing };
690
691
 
691
692
  if (patch.pageSize) {
692
693
  result.pageSize = {
@@ -8,6 +8,7 @@ import type {
8
8
  DocumentRootNode,
9
9
  ParagraphNode,
10
10
  TableNode,
11
+ Mutable,
11
12
  } from "../../model/canonical-document.ts";
12
13
 
13
14
  type CanonicalDocumentEnvelope = PersistedEditorSnapshot["canonicalDocument"];
@@ -161,7 +162,7 @@ function isValidTableStyleId(
161
162
  function visitParagraphBindings(
162
163
  blocks: BlockNode[],
163
164
  surfaceBlocks: SurfaceBlockSnapshot[],
164
- visitor: (paragraph: ParagraphNode, surface: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>) => void,
165
+ visitor: (paragraph: Mutable<ParagraphNode>, surface: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>) => void,
165
166
  ): void {
166
167
  for (let index = 0; index < Math.min(blocks.length, surfaceBlocks.length); index += 1) {
167
168
  const block = blocks[index];
@@ -203,7 +204,7 @@ function visitParagraphBindings(
203
204
  function visitTableBindings(
204
205
  blocks: BlockNode[],
205
206
  surfaceBlocks: SurfaceBlockSnapshot[],
206
- visitor: (table: TableNode, surface: Extract<SurfaceBlockSnapshot, { kind: "table" }>) => void,
207
+ visitor: (table: Mutable<TableNode>, surface: Extract<SurfaceBlockSnapshot, { kind: "table" }>) => void,
207
208
  ): void {
208
209
  for (let index = 0; index < Math.min(blocks.length, surfaceBlocks.length); index += 1) {
209
210
  const block = blocks[index];
@@ -18,6 +18,7 @@ import type {
18
18
  TextMark,
19
19
  TextNode,
20
20
  SdtNode,
21
+ Mutable,
21
22
  } from "../../model/canonical-document.ts";
22
23
  import type {
23
24
  ParsedAltChunkNode,
@@ -108,7 +109,7 @@ export function normalizeParsedTextDocument(
108
109
  }
109
110
  }
110
111
 
111
- const content: DocumentRootNode = { type: "doc", children };
112
+ const content: Mutable<DocumentRootNode> = { type: "doc", children };
112
113
 
113
114
  return {
114
115
  content,
@@ -180,7 +181,7 @@ export async function normalizeParsedTextDocumentAsync(
180
181
  }
181
182
  }
182
183
 
183
- const content: DocumentRootNode = { type: "doc", children };
184
+ const content: Mutable<DocumentRootNode> = { type: "doc", children };
184
185
 
185
186
  return {
186
187
  content,
@@ -432,7 +433,7 @@ function normalizeInlineChildren(
432
433
 
433
434
  const previous = normalized[normalized.length - 1];
434
435
  if (previous?.type === "text" && sameMarks(previous.marks, node.marks)) {
435
- previous.text += node.text;
436
+ (previous as Mutable<typeof previous>).text += node.text;
436
437
  } else {
437
438
  normalized.push({
438
439
  type: "text",
@@ -752,7 +753,7 @@ function normalizeHyperlink(node: ParsedHyperlinkNode): {
752
753
  }
753
754
  const previous = children[children.length - 1];
754
755
  if (previous?.type === "text" && sameMarks(previous.marks, child.marks)) {
755
- previous.text += child.text;
756
+ (previous as Mutable<typeof previous>).text += child.text;
756
757
  } else {
757
758
  children.push({
758
759
  type: "text",
@@ -835,7 +836,7 @@ function recordOpaqueFragment(
835
836
  const rangeStart = state.cursor;
836
837
  const rangeEnd = state.cursor + 1;
837
838
 
838
- const record: OpaqueFragmentRecord = {
839
+ const record: Mutable<OpaqueFragmentRecord> = {
839
840
  fragmentId,
840
841
  payloadKind: "xml-subtree",
841
842
  payloadReference: rawXml,
@@ -1,4 +1,4 @@
1
- import type { AnchorGeometry } from "../../model/canonical-document.ts";
1
+ import type { AnchorGeometry, Mutable } from "../../model/canonical-document.ts";
2
2
  import {
3
3
  type XmlElementNode,
4
4
  findFirstChild,
@@ -46,11 +46,11 @@ export function parseAnchorGeometry(container: XmlElementNode): AnchorGeometry {
46
46
  const dp: NonNullable<AnchorGeometry["docPr"]> = {
47
47
  id: docPrEl.attributes.id ?? "",
48
48
  };
49
- if (docPrEl.attributes.name) dp.name = docPrEl.attributes.name;
50
- if (docPrEl.attributes.descr) dp.descr = docPrEl.attributes.descr;
49
+ if (docPrEl.attributes.name) (dp as Mutable<typeof dp>).name = docPrEl.attributes.name;
50
+ if (docPrEl.attributes.descr) (dp as Mutable<typeof dp>).descr = docPrEl.attributes.descr;
51
51
  // Phase 4.6 G8 — wp:docPr hidden flag
52
52
  const hidden = readBoolAttr(docPrEl, "hidden");
53
- if (hidden !== undefined) dp.hidden = hidden;
53
+ if (hidden !== undefined) (dp as Mutable<typeof dp>).hidden = hidden;
54
54
  return dp;
55
55
  })()
56
56
  : undefined;
@@ -62,21 +62,21 @@ export function parseAnchorGeometry(container: XmlElementNode): AnchorGeometry {
62
62
  // Phase 4.2 G1 — wrapPolygon coords for wrapTight / wrapThrough.
63
63
  const wrapPolygon = readWrapPolygon(container);
64
64
 
65
- const geometry: AnchorGeometry = {
65
+ const geometry: Mutable<AnchorGeometry> = {
66
66
  display,
67
67
  extent,
68
68
  wrapMode,
69
69
  };
70
70
 
71
- if (wrapPolygon) geometry.wrapPolygon = wrapPolygon;
72
- if (positionHEl) geometry.positionH = readAxisPosition(positionHEl);
73
- if (positionVEl) geometry.positionV = readAxisPosition(positionVEl);
74
- if (distMargins) geometry.distMargins = distMargins;
75
- if (relativeHeight !== undefined) geometry.relativeHeight = relativeHeight;
76
- if (behindDoc !== undefined) geometry.behindDoc = behindDoc;
77
- if (layoutInCell !== undefined) geometry.layoutInCell = layoutInCell;
78
- if (allowOverlap !== undefined) geometry.allowOverlap = allowOverlap;
79
- if (simplePos !== undefined) geometry.simplePos = simplePos;
71
+ if (wrapPolygon) (geometry as Mutable<typeof geometry>).wrapPolygon = wrapPolygon;
72
+ if (positionHEl) (geometry as Mutable<typeof geometry>).positionH = readAxisPosition(positionHEl);
73
+ if (positionVEl) (geometry as Mutable<typeof geometry>).positionV = readAxisPosition(positionVEl);
74
+ if (distMargins) (geometry as Mutable<typeof geometry>).distMargins = distMargins;
75
+ if (relativeHeight !== undefined) (geometry as Mutable<typeof geometry>).relativeHeight = relativeHeight;
76
+ if (behindDoc !== undefined) (geometry as Mutable<typeof geometry>).behindDoc = behindDoc;
77
+ if (layoutInCell !== undefined) (geometry as Mutable<typeof geometry>).layoutInCell = layoutInCell;
78
+ if (allowOverlap !== undefined) (geometry as Mutable<typeof geometry>).allowOverlap = allowOverlap;
79
+ if (simplePos !== undefined) (geometry as Mutable<typeof geometry>).simplePos = simplePos;
80
80
  if (docPr) geometry.docPr = docPr;
81
81
  if (frameLocks) geometry.frameLocks = frameLocks;
82
82
 
@@ -136,7 +136,7 @@ function readFrameLocks(
136
136
  const keys = ["noChangeAspect", "noResize", "noMove", "noRot", "noSelect", "noGrp"] as const;
137
137
  for (const key of keys) {
138
138
  const v = readBoolAttr(locks, key);
139
- if (v !== undefined) result[key] = v;
139
+ if (v !== undefined) (result as Mutable<typeof result>)[key] = v;
140
140
  }
141
141
  return Object.keys(result).length > 0 ? result : undefined;
142
142
  }