@beyondwork/docx-react-component 1.0.105 → 1.0.108

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 (193) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +10 -2
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-reference.ts +28 -0
  11. package/src/api/v3/ai/_audit-time.ts +5 -0
  12. package/src/api/v3/ai/_pe2-evidence.ts +310 -6
  13. package/src/api/v3/ai/attach.ts +29 -4
  14. package/src/api/v3/ai/bundle.ts +6 -2
  15. package/src/api/v3/ai/inspect.ts +6 -2
  16. package/src/api/v3/ai/replacement.ts +112 -18
  17. package/src/api/v3/ai/resolve.ts +2 -2
  18. package/src/api/v3/ai/review.ts +177 -3
  19. package/src/api/v3/index.ts +8 -0
  20. package/src/api/v3/runtime/collab.ts +462 -0
  21. package/src/api/v3/runtime/document.ts +503 -20
  22. package/src/api/v3/runtime/geometry.ts +97 -0
  23. package/src/api/v3/runtime/layout.ts +744 -0
  24. package/src/api/v3/runtime/perf-probe.ts +14 -0
  25. package/src/api/v3/runtime/viewport.ts +9 -8
  26. package/src/api/v3/ui/_types.ts +202 -55
  27. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  28. package/src/api/v3/ui/debug.ts +115 -2
  29. package/src/api/v3/ui/index.ts +17 -0
  30. package/src/api/v3/ui/overlays.ts +0 -8
  31. package/src/api/v3/ui/surface.ts +56 -0
  32. package/src/api/v3/ui/viewport.ts +119 -9
  33. package/src/core/commands/image-commands.ts +1 -0
  34. package/src/core/commands/index.ts +6 -0
  35. package/src/core/schema/text-schema.ts +43 -5
  36. package/src/core/selection/mapping.ts +8 -1
  37. package/src/core/selection/review-anchors.ts +5 -1
  38. package/src/core/state/text-transaction.ts +8 -2
  39. package/src/io/export/serialize-revisions.ts +149 -1
  40. package/src/io/normalize/normalize-text.ts +6 -0
  41. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  42. package/src/io/ooxml/parse-fields.ts +24 -2
  43. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  44. package/src/io/ooxml/parse-main-document.ts +153 -9
  45. package/src/io/ooxml/parse-numbering.ts +20 -0
  46. package/src/io/ooxml/parse-revisions.ts +19 -8
  47. package/src/io/opc/package-reader.ts +98 -8
  48. package/src/model/anchor.ts +4 -3
  49. package/src/model/canonical-document.ts +220 -2
  50. package/src/model/canonical-hash.ts +221 -0
  51. package/src/model/canonical-layout-inputs.ts +245 -6
  52. package/src/model/layout/index.ts +1 -0
  53. package/src/model/layout/page-graph-types.ts +147 -1
  54. package/src/model/review/revision-types.ts +14 -3
  55. package/src/preservation/store.ts +20 -4
  56. package/src/review/README.md +1 -1
  57. package/src/review/store/revision-actions.ts +14 -2
  58. package/src/runtime/collab/event-types.ts +67 -1
  59. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  60. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  61. package/src/runtime/document-heading-outline.ts +147 -0
  62. package/src/runtime/document-navigation.ts +8 -243
  63. package/src/runtime/document-runtime.ts +279 -115
  64. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  65. package/src/runtime/formatting/layout-inputs.ts +38 -5
  66. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  67. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  68. package/src/runtime/geometry/caret-geometry.ts +5 -6
  69. package/src/runtime/geometry/geometry-facet.ts +60 -10
  70. package/src/runtime/geometry/geometry-index.ts +661 -16
  71. package/src/runtime/geometry/geometry-types.ts +59 -0
  72. package/src/runtime/geometry/hit-test.ts +11 -1
  73. package/src/runtime/geometry/overlay-rects.ts +5 -3
  74. package/src/runtime/geometry/project-anchors.ts +1 -1
  75. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  76. package/src/runtime/layout/index.ts +6 -0
  77. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  78. package/src/runtime/layout/layout-engine-version.ts +188 -16
  79. package/src/runtime/layout/layout-facet-types.ts +6 -0
  80. package/src/runtime/layout/page-graph.ts +23 -4
  81. package/src/runtime/layout/paginated-layout-engine.ts +149 -15
  82. package/src/runtime/layout/project-block-fragments.ts +351 -14
  83. package/src/runtime/layout/public-facet.ts +162 -24
  84. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  85. package/src/runtime/layout/table-row-split.ts +92 -35
  86. package/src/runtime/prerender/cache-envelope.ts +2 -2
  87. package/src/runtime/prerender/cache-key.ts +5 -4
  88. package/src/runtime/prerender/customxml-cache.ts +0 -1
  89. package/src/runtime/render/render-kernel.ts +1 -1
  90. package/src/runtime/revision-runtime.ts +112 -10
  91. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  92. package/src/runtime/scopes/action-validation.ts +22 -2
  93. package/src/runtime/scopes/capabilities.ts +316 -0
  94. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  95. package/src/runtime/scopes/compiler-service.ts +108 -4
  96. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  97. package/src/runtime/scopes/create-issue.ts +5 -5
  98. package/src/runtime/scopes/evidence.ts +91 -0
  99. package/src/runtime/scopes/formatting/apply.ts +2 -0
  100. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  101. package/src/runtime/scopes/index.ts +54 -0
  102. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  103. package/src/runtime/scopes/layout-evidence.ts +374 -0
  104. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  105. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  106. package/src/runtime/scopes/replacement/apply.ts +97 -34
  107. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  108. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  109. package/src/runtime/scopes/visualization.ts +28 -0
  110. package/src/runtime/surface-projection.ts +44 -5
  111. package/src/runtime/telemetry/perf-probe.ts +216 -0
  112. package/src/runtime/virtualized-rendering.ts +36 -1
  113. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  114. package/src/runtime/workflow/coordinator.ts +39 -11
  115. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  116. package/src/runtime/workflow/index.ts +4 -0
  117. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  118. package/src/runtime/workflow/overlay-lanes.ts +386 -0
  119. package/src/runtime/workflow/overlay-store.ts +2 -2
  120. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  121. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  122. package/src/session/_sync-legacy.ts +17 -27
  123. package/src/session/import/loader.ts +6 -4
  124. package/src/session/import/source-package-evidence.ts +186 -2
  125. package/src/session/index.ts +5 -6
  126. package/src/session/session.ts +30 -56
  127. package/src/session/types.ts +8 -13
  128. package/src/shell/session-bootstrap.ts +155 -81
  129. package/src/ui/WordReviewEditor.tsx +520 -12
  130. package/src/ui/editor-shell-view.tsx +14 -4
  131. package/src/ui/editor-surface-controller.tsx +5 -3
  132. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  133. package/src/ui/presence-overlay-lane.ts +130 -0
  134. package/src/ui/ui-controller-factory.ts +17 -0
  135. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  136. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  137. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  138. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  139. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  140. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  141. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  142. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  143. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  144. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  145. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  146. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  147. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  148. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  149. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  150. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  151. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  152. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  153. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  154. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  155. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  156. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  157. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  158. package/src/ui-tailwind/debug/README.md +4 -1
  159. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  160. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  161. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  162. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  163. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  164. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  165. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  166. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  167. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  168. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  169. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  170. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  171. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  172. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  173. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  174. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  175. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  176. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  177. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  178. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  179. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  180. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  181. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  182. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  183. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  184. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  185. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  186. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  187. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  188. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  189. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  190. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  191. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  192. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  193. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -24,14 +24,22 @@ import type {
24
24
  EditorSurfaceSnapshot,
25
25
  SurfaceBlockSnapshot,
26
26
  } from "../../api/public-types";
27
+ import type {
28
+ CanonicalFieldRegionIdentity,
29
+ } from "../../model/canonical-layout-inputs.ts";
27
30
  import type { RuntimeBlockFragment } from "./page-graph.ts";
28
- import type { RuntimeLineBox } from "../../model/layout/page-graph-types.ts";
31
+ import type {
32
+ RuntimeFieldRegionLayoutFacts,
33
+ RuntimeLineBox,
34
+ RuntimeNumberingLayoutFacts,
35
+ } from "../../model/layout/page-graph-types.ts";
29
36
  import type {
30
37
  BlockSplits,
31
38
  FragmentMeasurement,
32
39
  ParagraphLineSlice,
33
40
  TableRowSlice,
34
41
  } from "./paginated-layout-engine.ts";
42
+ import { collectSplitRowCarryForPage } from "./table-row-continuation-contract.ts";
35
43
 
36
44
  type FragmentWithoutPageId = Omit<RuntimeBlockFragment, "pageId">;
37
45
 
@@ -41,9 +49,11 @@ export function projectSurfaceBlocksToPageFragments(
41
49
  splits?: BlockSplits,
42
50
  columnByBlockIdByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, number>>,
43
51
  fragmentMeasurementsByPageIndex?: ReadonlyMap<number, ReadonlyMap<string, FragmentMeasurement>>,
52
+ fieldRegions: readonly CanonicalFieldRegionIdentity[] = [],
44
53
  ): Map<number, FragmentWithoutPageId[]> {
45
54
  const byPage = new Map<number, FragmentWithoutPageId[]>();
46
55
  const perPageCounter = new Map<number, number>();
56
+ const fieldRegionsByParagraphIndex = buildFieldRegionsByParagraphIndex(fieldRegions);
47
57
 
48
58
  const pushFragment = (
49
59
  pageIndex: number,
@@ -74,7 +84,9 @@ export function projectSurfaceBlocksToPageFragments(
74
84
  fragmentMeasurementsByPageIndex?.get(pageIndex)?.get(blockId)?.heightTwips ??
75
85
  fallback;
76
86
 
77
- for (const block of surface.blocks) {
87
+ for (let blockIndex = 0; blockIndex < surface.blocks.length; blockIndex += 1) {
88
+ const block = surface.blocks[blockIndex]!;
89
+ const blockPath = `main/block[${blockIndex}]`;
78
90
  // R3: table split across pages — emit one fragment per row slice.
79
91
  // Consumers read `tableRowRange` and prepend header rows when from > 0.
80
92
  if (block.kind === "table") {
@@ -93,9 +105,11 @@ export function projectSurfaceBlocksToPageFragments(
93
105
  ? { columnIndex: fragment.columnIndex }
94
106
  : columnIndex !== undefined
95
107
  ? { columnIndex }
96
- : {}),
108
+ : {}),
97
109
  });
98
110
  },
111
+ fieldRegionsByParagraphIndex,
112
+ blockPath,
99
113
  );
100
114
  continue;
101
115
  }
@@ -116,6 +130,8 @@ export function projectSurfaceBlocksToPageFragments(
116
130
  ...(columnIndex !== undefined ? { columnIndex } : {}),
117
131
  });
118
132
  },
133
+ fieldRegionsByParagraphIndex,
134
+ blockPath,
119
135
  );
120
136
  continue;
121
137
  }
@@ -126,6 +142,14 @@ export function projectSurfaceBlocksToPageFragments(
126
142
  if (pageIndex === null) continue;
127
143
 
128
144
  const columnIndex = columnIndexFor(pageIndex, block.blockId);
145
+ const heightTwips = measuredHeightFor(
146
+ pageIndex,
147
+ block.blockId,
148
+ estimateBlockHeightFromSpan(block),
149
+ );
150
+ const widthTwips = fragmentMeasurementsByPageIndex
151
+ ?.get(pageIndex)
152
+ ?.get(block.blockId)?.widthTwips;
129
153
  const fragment: FragmentWithoutPageId = {
130
154
  fragmentId: `fragment-${block.blockId}`,
131
155
  blockId: block.blockId,
@@ -133,13 +157,18 @@ export function projectSurfaceBlocksToPageFragments(
133
157
  regionKind: "body",
134
158
  from: block.from,
135
159
  to: block.to,
136
- heightTwips: measuredHeightFor(
137
- pageIndex,
138
- block.blockId,
139
- estimateBlockHeightFromSpan(block),
140
- ),
160
+ heightTwips,
141
161
  ...deriveStyleMetadata(block),
142
162
  kind: "whole",
163
+ layoutObject: buildFragmentLayoutObject({
164
+ block,
165
+ fragmentId: `fragment-${block.blockId}`,
166
+ heightTwips,
167
+ widthTwips,
168
+ paginationRole: "whole",
169
+ fieldRegionsByParagraphIndex,
170
+ blockPath,
171
+ }),
143
172
  ...(columnIndex !== undefined ? { columnIndex } : {}),
144
173
  };
145
174
 
@@ -221,9 +250,12 @@ function emitSlicedParagraph(
221
250
  block: SurfaceBlockSnapshot,
222
251
  slices: readonly ParagraphLineSlice[],
223
252
  emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
253
+ fieldRegionsByParagraphIndex: ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]> = new Map(),
254
+ blockPath?: string,
224
255
  ): void {
225
256
  for (let i = 0; i < slices.length; i += 1) {
226
257
  const slice = slices[i]!;
258
+ const heightTwips = slice.heightTwips ?? estimateSliceHeightFromLines(slice.lineRange);
227
259
  const fragment: FragmentWithoutPageId = {
228
260
  fragmentId: `fragment-${block.blockId}-slice-${i}`,
229
261
  blockId: block.blockId,
@@ -231,11 +263,20 @@ function emitSlicedParagraph(
231
263
  regionKind: "body",
232
264
  from: block.from,
233
265
  to: block.to,
234
- heightTwips: slice.heightTwips ?? estimateSliceHeightFromLines(slice.lineRange),
266
+ heightTwips,
235
267
  ...deriveStyleMetadata(block),
236
268
  kind: "paragraph-slice",
237
269
  paragraphLineRange: slice.lineRange,
238
270
  continuation: buildParagraphContinuationCursor(slice, i, slices.length),
271
+ layoutObject: buildFragmentLayoutObject({
272
+ block,
273
+ fragmentId: `fragment-${block.blockId}-slice-${i}`,
274
+ heightTwips,
275
+ widthTwips: slice.widthTwips,
276
+ paginationRole: slice.lineRange.from > 0 ? "continuation" : "slice",
277
+ fieldRegionsByParagraphIndex,
278
+ blockPath,
279
+ }),
239
280
  };
240
281
  emit(slice.pageIndex, fragment);
241
282
  }
@@ -278,9 +319,12 @@ function emitSlicedTable(
278
319
  block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
279
320
  slices: readonly TableRowSlice[],
280
321
  emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
322
+ fieldRegionsByParagraphIndex: ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]> = new Map(),
323
+ blockPath?: string,
281
324
  ): void {
282
325
  for (let i = 0; i < slices.length; i += 1) {
283
326
  const slice = slices[i]!;
327
+ const heightTwips = slice.heightTwips ?? estimateSliceHeightFromRows(slice.rowRange);
284
328
  const fragment: FragmentWithoutPageId = {
285
329
  fragmentId: `fragment-${block.blockId}-rowslice-${i}`,
286
330
  blockId: block.blockId,
@@ -288,11 +332,19 @@ function emitSlicedTable(
288
332
  regionKind: "body",
289
333
  from: block.from,
290
334
  to: block.to,
291
- heightTwips: slice.heightTwips ?? estimateSliceHeightFromRows(slice.rowRange),
335
+ heightTwips,
292
336
  ...deriveStyleMetadata(block),
293
337
  kind: "table-slice",
294
338
  tableRowRange: slice.rowRange,
295
- continuation: buildTableContinuationCursor(block, slice, i, slices.length),
339
+ continuation: buildTableContinuationCursor(block, slices, i),
340
+ layoutObject: buildFragmentLayoutObject({
341
+ block,
342
+ fragmentId: `fragment-${block.blockId}-rowslice-${i}`,
343
+ heightTwips,
344
+ paginationRole: slice.rowRange.from > 0 ? "continuation" : "slice",
345
+ fieldRegionsByParagraphIndex,
346
+ blockPath,
347
+ }),
296
348
  ...(slice.columnIndex !== undefined ? { columnIndex: slice.columnIndex } : {}),
297
349
  };
298
350
  emit(slice.pageIndex, fragment);
@@ -301,14 +353,15 @@ function emitSlicedTable(
301
353
 
302
354
  function buildTableContinuationCursor(
303
355
  block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
304
- slice: TableRowSlice,
356
+ slices: readonly TableRowSlice[],
305
357
  sequenceIndex: number,
306
- sliceCount: number,
307
358
  ): NonNullable<RuntimeBlockFragment["continuation"]> {
359
+ const slice = slices[sequenceIndex]!;
360
+ const splitRowCarry = collectSplitRowCarryForPage(slices, sequenceIndex);
308
361
  return {
309
362
  kind: "table",
310
363
  sequenceIndex,
311
- sliceCount,
364
+ sliceCount: slices.length,
312
365
  rowRange: slice.rowRange,
313
366
  continuesFromPreviousPage: slice.rowRange.from > 0,
314
367
  continuesToNextPage: slice.rowRange.to < slice.rowRange.totalRows,
@@ -316,6 +369,7 @@ function buildTableContinuationCursor(
316
369
  slice.rowRange.from > 0
317
370
  ? collectRepeatedHeaderRowIndexes(block, slice.rowRange.from)
318
371
  : [],
372
+ ...(splitRowCarry.length > 0 ? { splitRowCarry } : {}),
319
373
  verticalMergeCarry: collectVerticalMergeCarry(block, slice.rowRange.from),
320
374
  };
321
375
  }
@@ -391,6 +445,289 @@ function estimateSliceHeightFromRows(rowRange: {
391
445
  return rows * 360; // ~1 line + padding per row, approximate
392
446
  }
393
447
 
448
+ function buildFragmentLayoutObject(input: {
449
+ block: SurfaceBlockSnapshot;
450
+ fragmentId: string;
451
+ heightTwips: number;
452
+ widthTwips?: number;
453
+ paginationRole: NonNullable<RuntimeBlockFragment["layoutObject"]>["paginationRole"];
454
+ fieldRegionsByParagraphIndex?: ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]>;
455
+ blockPath?: string;
456
+ }): NonNullable<RuntimeBlockFragment["layoutObject"]> {
457
+ const fieldFamilies = collectFieldFamilies(input.block);
458
+ const kind = resolveFragmentLayoutObjectKind(input.block, fieldFamilies);
459
+ const numberingRows = collectNumberingLayoutFactsForBlock(input.block, input.blockPath);
460
+ const numbering = input.block.kind === "paragraph" ? numberingRows[0] : undefined;
461
+ const fieldRegions = collectFieldRegionLayoutFacts(
462
+ input.block,
463
+ input.fragmentId,
464
+ input.fieldRegionsByParagraphIndex,
465
+ );
466
+ return {
467
+ objectId: `${kind}:${input.fragmentId}`,
468
+ kind,
469
+ sourceBlockId: input.block.blockId,
470
+ paginationRole: input.paginationRole,
471
+ measuredExtentTwips: {
472
+ heightTwips: Math.max(0, input.heightTwips),
473
+ ...(input.widthTwips !== undefined
474
+ ? { widthTwips: Math.max(0, input.widthTwips) }
475
+ : {}),
476
+ },
477
+ ...(fieldFamilies.length > 0 ? { fieldFamilies } : {}),
478
+ ...(fieldRegions.length > 0 ? { fieldRegions } : {}),
479
+ ...(numbering ? { numbering } : {}),
480
+ ...(numberingRows.length > 0 ? { numberingRows } : {}),
481
+ };
482
+ }
483
+
484
+ function resolveFragmentLayoutObjectKind(
485
+ block: SurfaceBlockSnapshot,
486
+ fieldFamilies: readonly string[],
487
+ ): NonNullable<RuntimeBlockFragment["layoutObject"]>["kind"] {
488
+ switch (block.kind) {
489
+ case "paragraph":
490
+ if (fieldFamilies.length > 0) return "field-region";
491
+ return block.numbering ? "numbered-paragraph" : "paragraph";
492
+ case "table":
493
+ return "table";
494
+ case "sdt_block":
495
+ return "sdt-block";
496
+ case "opaque_block":
497
+ return "opaque-block";
498
+ }
499
+ }
500
+
501
+ function collectFieldFamilies(block: SurfaceBlockSnapshot): string[] {
502
+ if (block.kind !== "paragraph") return [];
503
+ const families: string[] = [];
504
+ const seen = new Set<string>();
505
+ for (const segment of block.segments) {
506
+ if (segment.kind !== "field_ref" || seen.has(segment.fieldFamily)) continue;
507
+ seen.add(segment.fieldFamily);
508
+ families.push(segment.fieldFamily);
509
+ }
510
+ return families;
511
+ }
512
+
513
+ function buildFieldRegionsByParagraphIndex(
514
+ regions: readonly CanonicalFieldRegionIdentity[],
515
+ ): Map<number, CanonicalFieldRegionIdentity[]> {
516
+ const byParagraph = new Map<number, CanonicalFieldRegionIdentity[]>();
517
+ for (const region of regions) {
518
+ const bucket = byParagraph.get(region.paragraphIndex);
519
+ if (bucket) bucket.push(region);
520
+ else byParagraph.set(region.paragraphIndex, [region]);
521
+ }
522
+ return byParagraph;
523
+ }
524
+
525
+ function collectFieldRegionLayoutFacts(
526
+ block: SurfaceBlockSnapshot,
527
+ fragmentId: string,
528
+ fieldRegionsByParagraphIndex:
529
+ | ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]>
530
+ | undefined,
531
+ ): RuntimeFieldRegionLayoutFacts[] {
532
+ if (!fieldRegionsByParagraphIndex || fieldRegionsByParagraphIndex.size === 0) {
533
+ return [];
534
+ }
535
+
536
+ const paragraphIndexes = collectParagraphIndexesForBlock(block);
537
+ if (paragraphIndexes.length === 0) return [];
538
+
539
+ const facts: RuntimeFieldRegionLayoutFacts[] = [];
540
+ const seen = new Set<string>();
541
+ for (const paragraphIndex of paragraphIndexes) {
542
+ for (const region of fieldRegionsByParagraphIndex.get(paragraphIndex) ?? []) {
543
+ const fieldRegionId = `field-region:${region.canonicalFieldId}`;
544
+ const key = `${fieldRegionId}:${fragmentId}`;
545
+ if (seen.has(key)) continue;
546
+ seen.add(key);
547
+ facts.push(toRuntimeFieldRegionLayoutFacts(region, fieldRegionId));
548
+ }
549
+ }
550
+ return facts;
551
+ }
552
+
553
+ function collectParagraphIndexesForBlock(block: SurfaceBlockSnapshot): number[] {
554
+ const indexes: number[] = [];
555
+ visitParagraphBlocks(block, (paragraph) => {
556
+ const index = parseParagraphBlockIndex(paragraph.blockId);
557
+ if (index !== undefined) indexes.push(index);
558
+ });
559
+ return indexes;
560
+ }
561
+
562
+ function visitParagraphBlocks(
563
+ block: SurfaceBlockSnapshot,
564
+ visit: (
565
+ paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
566
+ context: { readonly path?: string },
567
+ ) => void,
568
+ path?: string,
569
+ ): void {
570
+ switch (block.kind) {
571
+ case "paragraph":
572
+ visit(block, path !== undefined ? { path } : {});
573
+ return;
574
+ case "table":
575
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
576
+ const row = block.rows[rowIndex]!;
577
+ for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex += 1) {
578
+ const cell = row.cells[cellIndex]!;
579
+ for (let childIndex = 0; childIndex < cell.content.length; childIndex += 1) {
580
+ const child = cell.content[childIndex]!;
581
+ visitParagraphBlocks(
582
+ child,
583
+ visit,
584
+ path !== undefined
585
+ ? `${path}/row[${rowIndex}]/cell[${cellIndex}]/block[${childIndex}]`
586
+ : undefined,
587
+ );
588
+ }
589
+ }
590
+ }
591
+ return;
592
+ case "sdt_block":
593
+ for (let childIndex = 0; childIndex < block.children.length; childIndex += 1) {
594
+ const child = block.children[childIndex]!;
595
+ visitParagraphBlocks(
596
+ child,
597
+ visit,
598
+ path !== undefined ? `${path}/block[${childIndex}]` : undefined,
599
+ );
600
+ }
601
+ return;
602
+ case "opaque_block":
603
+ return;
604
+ }
605
+ }
606
+
607
+ function parseParagraphBlockIndex(blockId: string): number | undefined {
608
+ const match = /^paragraph-(\d+)$/.exec(blockId);
609
+ return match ? Number(match[1]) : undefined;
610
+ }
611
+
612
+ function toRuntimeFieldRegionLayoutFacts(
613
+ region: CanonicalFieldRegionIdentity,
614
+ fieldRegionId: string,
615
+ ): RuntimeFieldRegionLayoutFacts {
616
+ return {
617
+ fieldRegionId,
618
+ regionId: region.regionId,
619
+ canonicalFieldId: region.canonicalFieldId,
620
+ regionKind: region.kind,
621
+ storyKey: region.storyKey,
622
+ fieldIndex: region.fieldIndex,
623
+ paragraphIndex: region.paragraphIndex,
624
+ fieldFamily: region.fieldFamily,
625
+ refreshStatus: region.refreshStatus,
626
+ ...(region.sourceRef !== undefined ? { sourceRef: { ...region.sourceRef } } : {}),
627
+ ...(region.fieldEvidence !== undefined
628
+ ? {
629
+ fieldEvidence: {
630
+ ...region.fieldEvidence,
631
+ ...(region.fieldEvidence.sourceRef !== undefined
632
+ ? { sourceRef: { ...region.fieldEvidence.sourceRef } }
633
+ : {}),
634
+ },
635
+ }
636
+ : {}),
637
+ };
638
+ }
639
+
640
+ function collectNumberingLayoutFacts(
641
+ block: SurfaceBlockSnapshot,
642
+ ): RuntimeNumberingLayoutFacts | undefined {
643
+ if (block.kind !== "paragraph") return undefined;
644
+ if (!block.numbering && !block.resolvedNumbering && block.numberingPrefix === undefined) {
645
+ return undefined;
646
+ }
647
+
648
+ const markerLane = block.resolvedNumbering?.geometry.markerLane;
649
+ const textColumn = block.resolvedNumbering?.geometry.textColumn;
650
+ const tabStops = block.resolvedNumbering?.geometry.tabStops;
651
+ return {
652
+ ...(block.numbering?.numberingInstanceId !== undefined
653
+ ? { numberingInstanceId: block.numbering.numberingInstanceId }
654
+ : {}),
655
+ ...(block.numbering?.level !== undefined ? { level: block.numbering.level } : {}),
656
+ ...(block.resolvedNumbering?.format !== undefined
657
+ ? { format: block.resolvedNumbering.format }
658
+ : {}),
659
+ ...(block.numberingPrefix !== undefined ? { markerText: block.numberingPrefix } : {}),
660
+ ...(block.numberingSuffix !== undefined ? { markerSuffix: block.numberingSuffix } : {}),
661
+ ...(block.resolvedNumbering?.geometry.markerJustification !== undefined
662
+ ? { markerJustification: block.resolvedNumbering.geometry.markerJustification }
663
+ : {}),
664
+ ...(markerLane
665
+ ? {
666
+ markerLane: {
667
+ startTwips: markerLane.start,
668
+ widthTwips: markerLane.width,
669
+ textStartTwips: markerLane.textStart,
670
+ },
671
+ }
672
+ : {}),
673
+ ...(textColumn
674
+ ? {
675
+ textColumn: {
676
+ startTwips: textColumn.start,
677
+ ...(textColumn.right !== undefined ? { rightTwips: textColumn.right } : {}),
678
+ ...(textColumn.firstLine !== undefined
679
+ ? { firstLineTwips: textColumn.firstLine }
680
+ : {}),
681
+ ...(textColumn.hanging !== undefined ? { hangingTwips: textColumn.hanging } : {}),
682
+ },
683
+ }
684
+ : {}),
685
+ ...(tabStops && tabStops.length > 0
686
+ ? {
687
+ tabStops: tabStops.map((tab) => ({
688
+ positionTwips: tab.pos,
689
+ ...(tab.val !== undefined ? { align: tab.val } : {}),
690
+ ...(tab.leader !== undefined ? { leader: tab.leader } : {}),
691
+ })),
692
+ }
693
+ : {}),
694
+ };
695
+ }
696
+
697
+ function collectNumberingLayoutFactsForBlock(
698
+ block: SurfaceBlockSnapshot,
699
+ blockPath?: string,
700
+ ): RuntimeNumberingLayoutFacts[] {
701
+ const rows: RuntimeNumberingLayoutFacts[] = [];
702
+ const seen = new Set<string>();
703
+ visitParagraphBlocks(block, (paragraph, context) => {
704
+ const numbering = collectNumberingLayoutFacts(paragraph);
705
+ if (!numbering) return;
706
+ const paragraphIndex = parseParagraphBlockIndex(paragraph.blockId);
707
+ const numberingKey =
708
+ context.path !== undefined ? `main:${context.path}:numbering` : undefined;
709
+ const numberingLayoutId =
710
+ numberingKey ??
711
+ [
712
+ "numbering",
713
+ paragraph.blockId,
714
+ numbering.numberingInstanceId ?? "unknown-instance",
715
+ numbering.level ?? "unknown-level",
716
+ ].join(":");
717
+ if (seen.has(numberingLayoutId)) return;
718
+ seen.add(numberingLayoutId);
719
+ rows.push({
720
+ numberingLayoutId,
721
+ ...(numberingKey !== undefined ? { numberingKey } : {}),
722
+ ...(context.path !== undefined ? { sourceBlockPath: context.path } : {}),
723
+ sourceBlockId: paragraph.blockId,
724
+ ...(paragraphIndex !== undefined ? { paragraphIndex } : {}),
725
+ ...numbering,
726
+ });
727
+ }, blockPath);
728
+ return rows;
729
+ }
730
+
394
731
  function findPageIndexForOffset(
395
732
  pages: readonly DocumentPageSnapshot[],
396
733
  offset: number,