@beyondwork/docx-react-component 1.0.106 → 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 (190) 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 +2 -1
  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-time.ts +5 -0
  11. package/src/api/v3/ai/_pe2-evidence.ts +38 -0
  12. package/src/api/v3/ai/attach.ts +7 -2
  13. package/src/api/v3/ai/replacement.ts +101 -18
  14. package/src/api/v3/ai/resolve.ts +2 -2
  15. package/src/api/v3/ai/review.ts +177 -3
  16. package/src/api/v3/index.ts +1 -0
  17. package/src/api/v3/runtime/collab.ts +462 -0
  18. package/src/api/v3/runtime/document.ts +503 -20
  19. package/src/api/v3/runtime/geometry.ts +97 -0
  20. package/src/api/v3/runtime/layout.ts +744 -0
  21. package/src/api/v3/runtime/perf-probe.ts +14 -0
  22. package/src/api/v3/runtime/viewport.ts +9 -8
  23. package/src/api/v3/ui/_types.ts +149 -55
  24. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  25. package/src/api/v3/ui/debug.ts +115 -2
  26. package/src/api/v3/ui/index.ts +13 -0
  27. package/src/api/v3/ui/overlays.ts +0 -8
  28. package/src/api/v3/ui/surface.ts +56 -0
  29. package/src/api/v3/ui/viewport.ts +22 -9
  30. package/src/core/commands/image-commands.ts +1 -0
  31. package/src/core/commands/index.ts +6 -0
  32. package/src/core/schema/text-schema.ts +43 -5
  33. package/src/core/selection/mapping.ts +8 -1
  34. package/src/core/selection/review-anchors.ts +5 -1
  35. package/src/core/state/text-transaction.ts +8 -2
  36. package/src/io/export/serialize-revisions.ts +149 -1
  37. package/src/io/normalize/normalize-text.ts +6 -0
  38. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  39. package/src/io/ooxml/parse-fields.ts +24 -2
  40. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  41. package/src/io/ooxml/parse-main-document.ts +153 -9
  42. package/src/io/ooxml/parse-numbering.ts +20 -0
  43. package/src/io/ooxml/parse-revisions.ts +19 -8
  44. package/src/io/opc/package-reader.ts +98 -8
  45. package/src/model/anchor.ts +4 -3
  46. package/src/model/canonical-document.ts +220 -2
  47. package/src/model/canonical-hash.ts +221 -0
  48. package/src/model/canonical-layout-inputs.ts +245 -6
  49. package/src/model/layout/index.ts +1 -0
  50. package/src/model/layout/page-graph-types.ts +118 -1
  51. package/src/model/review/revision-types.ts +14 -3
  52. package/src/preservation/store.ts +20 -4
  53. package/src/review/README.md +1 -1
  54. package/src/review/store/revision-actions.ts +14 -2
  55. package/src/runtime/collab/event-types.ts +67 -1
  56. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  57. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  58. package/src/runtime/document-heading-outline.ts +147 -0
  59. package/src/runtime/document-navigation.ts +8 -243
  60. package/src/runtime/document-runtime.ts +240 -97
  61. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  62. package/src/runtime/formatting/layout-inputs.ts +38 -5
  63. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  64. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  65. package/src/runtime/geometry/caret-geometry.ts +5 -6
  66. package/src/runtime/geometry/geometry-facet.ts +60 -10
  67. package/src/runtime/geometry/geometry-index.ts +591 -20
  68. package/src/runtime/geometry/geometry-types.ts +59 -0
  69. package/src/runtime/geometry/hit-test.ts +11 -1
  70. package/src/runtime/geometry/overlay-rects.ts +5 -3
  71. package/src/runtime/geometry/project-anchors.ts +1 -1
  72. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  73. package/src/runtime/layout/index.ts +6 -0
  74. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  75. package/src/runtime/layout/layout-engine-version.ts +181 -16
  76. package/src/runtime/layout/layout-facet-types.ts +6 -0
  77. package/src/runtime/layout/page-graph.ts +21 -4
  78. package/src/runtime/layout/paginated-layout-engine.ts +139 -15
  79. package/src/runtime/layout/project-block-fragments.ts +265 -7
  80. package/src/runtime/layout/public-facet.ts +78 -24
  81. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  82. package/src/runtime/layout/table-row-split.ts +92 -35
  83. package/src/runtime/prerender/cache-envelope.ts +2 -2
  84. package/src/runtime/prerender/cache-key.ts +5 -4
  85. package/src/runtime/prerender/customxml-cache.ts +0 -1
  86. package/src/runtime/render/render-kernel.ts +1 -1
  87. package/src/runtime/revision-runtime.ts +112 -10
  88. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  89. package/src/runtime/scopes/action-validation.ts +22 -2
  90. package/src/runtime/scopes/capabilities.ts +316 -0
  91. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  92. package/src/runtime/scopes/compiler-service.ts +108 -4
  93. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  94. package/src/runtime/scopes/create-issue.ts +5 -5
  95. package/src/runtime/scopes/evidence.ts +91 -0
  96. package/src/runtime/scopes/formatting/apply.ts +2 -0
  97. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  98. package/src/runtime/scopes/index.ts +54 -0
  99. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  100. package/src/runtime/scopes/layout-evidence.ts +374 -0
  101. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  102. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  103. package/src/runtime/scopes/replacement/apply.ts +97 -34
  104. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  105. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  106. package/src/runtime/scopes/visualization.ts +28 -0
  107. package/src/runtime/surface-projection.ts +44 -5
  108. package/src/runtime/telemetry/perf-probe.ts +216 -0
  109. package/src/runtime/virtualized-rendering.ts +36 -1
  110. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  111. package/src/runtime/workflow/coordinator.ts +39 -11
  112. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  113. package/src/runtime/workflow/index.ts +3 -0
  114. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  115. package/src/runtime/workflow/overlay-lanes.ts +168 -10
  116. package/src/runtime/workflow/overlay-store.ts +2 -2
  117. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  118. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  119. package/src/session/_sync-legacy.ts +17 -27
  120. package/src/session/import/loader.ts +6 -4
  121. package/src/session/import/source-package-evidence.ts +186 -2
  122. package/src/session/index.ts +5 -6
  123. package/src/session/session.ts +30 -56
  124. package/src/session/types.ts +8 -13
  125. package/src/shell/session-bootstrap.ts +155 -81
  126. package/src/ui/WordReviewEditor.tsx +520 -12
  127. package/src/ui/editor-shell-view.tsx +14 -4
  128. package/src/ui/editor-surface-controller.tsx +5 -3
  129. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  130. package/src/ui/presence-overlay-lane.ts +0 -1
  131. package/src/ui/ui-controller-factory.ts +7 -0
  132. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  133. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  134. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  135. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  136. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  137. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  138. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  139. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  140. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  141. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  142. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  143. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  144. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  145. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  146. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  147. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  148. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  149. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  150. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  151. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  152. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  153. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  154. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  155. package/src/ui-tailwind/debug/README.md +4 -1
  156. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  157. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  158. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  159. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  160. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  161. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  162. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  163. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  164. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  165. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  166. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  167. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  168. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  169. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  170. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  171. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  172. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  173. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  174. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  175. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  176. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  177. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  178. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  179. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  180. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  181. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  182. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  183. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  184. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  185. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  186. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  187. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  188. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  189. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  190. 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
  }
@@ -150,6 +166,8 @@ export function projectSurfaceBlocksToPageFragments(
150
166
  heightTwips,
151
167
  widthTwips,
152
168
  paginationRole: "whole",
169
+ fieldRegionsByParagraphIndex,
170
+ blockPath,
153
171
  }),
154
172
  ...(columnIndex !== undefined ? { columnIndex } : {}),
155
173
  };
@@ -232,6 +250,8 @@ function emitSlicedParagraph(
232
250
  block: SurfaceBlockSnapshot,
233
251
  slices: readonly ParagraphLineSlice[],
234
252
  emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
253
+ fieldRegionsByParagraphIndex: ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]> = new Map(),
254
+ blockPath?: string,
235
255
  ): void {
236
256
  for (let i = 0; i < slices.length; i += 1) {
237
257
  const slice = slices[i]!;
@@ -254,6 +274,8 @@ function emitSlicedParagraph(
254
274
  heightTwips,
255
275
  widthTwips: slice.widthTwips,
256
276
  paginationRole: slice.lineRange.from > 0 ? "continuation" : "slice",
277
+ fieldRegionsByParagraphIndex,
278
+ blockPath,
257
279
  }),
258
280
  };
259
281
  emit(slice.pageIndex, fragment);
@@ -297,6 +319,8 @@ function emitSlicedTable(
297
319
  block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
298
320
  slices: readonly TableRowSlice[],
299
321
  emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
322
+ fieldRegionsByParagraphIndex: ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]> = new Map(),
323
+ blockPath?: string,
300
324
  ): void {
301
325
  for (let i = 0; i < slices.length; i += 1) {
302
326
  const slice = slices[i]!;
@@ -312,12 +336,14 @@ function emitSlicedTable(
312
336
  ...deriveStyleMetadata(block),
313
337
  kind: "table-slice",
314
338
  tableRowRange: slice.rowRange,
315
- continuation: buildTableContinuationCursor(block, slice, i, slices.length),
339
+ continuation: buildTableContinuationCursor(block, slices, i),
316
340
  layoutObject: buildFragmentLayoutObject({
317
341
  block,
318
342
  fragmentId: `fragment-${block.blockId}-rowslice-${i}`,
319
343
  heightTwips,
320
344
  paginationRole: slice.rowRange.from > 0 ? "continuation" : "slice",
345
+ fieldRegionsByParagraphIndex,
346
+ blockPath,
321
347
  }),
322
348
  ...(slice.columnIndex !== undefined ? { columnIndex: slice.columnIndex } : {}),
323
349
  };
@@ -327,14 +353,15 @@ function emitSlicedTable(
327
353
 
328
354
  function buildTableContinuationCursor(
329
355
  block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
330
- slice: TableRowSlice,
356
+ slices: readonly TableRowSlice[],
331
357
  sequenceIndex: number,
332
- sliceCount: number,
333
358
  ): NonNullable<RuntimeBlockFragment["continuation"]> {
359
+ const slice = slices[sequenceIndex]!;
360
+ const splitRowCarry = collectSplitRowCarryForPage(slices, sequenceIndex);
334
361
  return {
335
362
  kind: "table",
336
363
  sequenceIndex,
337
- sliceCount,
364
+ sliceCount: slices.length,
338
365
  rowRange: slice.rowRange,
339
366
  continuesFromPreviousPage: slice.rowRange.from > 0,
340
367
  continuesToNextPage: slice.rowRange.to < slice.rowRange.totalRows,
@@ -342,6 +369,7 @@ function buildTableContinuationCursor(
342
369
  slice.rowRange.from > 0
343
370
  ? collectRepeatedHeaderRowIndexes(block, slice.rowRange.from)
344
371
  : [],
372
+ ...(splitRowCarry.length > 0 ? { splitRowCarry } : {}),
345
373
  verticalMergeCarry: collectVerticalMergeCarry(block, slice.rowRange.from),
346
374
  };
347
375
  }
@@ -423,9 +451,18 @@ function buildFragmentLayoutObject(input: {
423
451
  heightTwips: number;
424
452
  widthTwips?: number;
425
453
  paginationRole: NonNullable<RuntimeBlockFragment["layoutObject"]>["paginationRole"];
454
+ fieldRegionsByParagraphIndex?: ReadonlyMap<number, readonly CanonicalFieldRegionIdentity[]>;
455
+ blockPath?: string;
426
456
  }): NonNullable<RuntimeBlockFragment["layoutObject"]> {
427
457
  const fieldFamilies = collectFieldFamilies(input.block);
428
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
+ );
429
466
  return {
430
467
  objectId: `${kind}:${input.fragmentId}`,
431
468
  kind,
@@ -438,6 +475,9 @@ function buildFragmentLayoutObject(input: {
438
475
  : {}),
439
476
  },
440
477
  ...(fieldFamilies.length > 0 ? { fieldFamilies } : {}),
478
+ ...(fieldRegions.length > 0 ? { fieldRegions } : {}),
479
+ ...(numbering ? { numbering } : {}),
480
+ ...(numberingRows.length > 0 ? { numberingRows } : {}),
441
481
  };
442
482
  }
443
483
 
@@ -470,6 +510,224 @@ function collectFieldFamilies(block: SurfaceBlockSnapshot): string[] {
470
510
  return families;
471
511
  }
472
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
+
473
731
  function findPageIndexForOffset(
474
732
  pages: readonly DocumentPageSnapshot[],
475
733
  offset: number,
@@ -91,6 +91,7 @@ import { resolvePageFieldDisplayText } from "./resolve-page-fields.ts";
91
91
  // `runtime.workflow.*`; the layout facet supplies the page graph
92
92
  // as input, not the rail output.
93
93
  import { collectLineBoxesForRegion } from "../geometry/project-fragments.ts";
94
+ import { emitLayoutGuardWarning } from "../diagnostics/layout-guard-warning.ts";
94
95
  // `recordPerfSample` no longer imported — used only by the removed
95
96
  // `hitTest` wrapper to emit `chrome.hit_test` samples. Geometry facet
96
97
  // can add its own instrumentation at call site if needed.
@@ -112,26 +113,11 @@ export type { ScopeRailSegment };
112
113
  // Module-level warning emitter (D1 silent-success probes)
113
114
  // ---------------------------------------------------------------------------
114
115
 
115
- type LayoutWarningEmitter = (type: string, guard: string, inputs: Record<string, unknown>) => void;
116
- let _activeLayoutWarningEmitter: LayoutWarningEmitter | undefined;
117
-
118
- export function setActiveLayoutWarningEmitter(fn: LayoutWarningEmitter | undefined): void {
119
- _activeLayoutWarningEmitter = fn;
120
- }
121
-
122
- /**
123
- * Exported so debug-infra D1 probes at layout-semantic guards that have
124
- * physically moved into `src/runtime/geometry/**` (Slice 2a+ of refactor/05)
125
- * can continue to emit through the same `setActiveLayoutWarningEmitter`
126
- * pipeline as the remaining in-facet probes. The emit surface is unchanged;
127
- * only the call-site location moved.
128
- */
129
- export function emitLayoutGuardWarning(
130
- guard: string,
131
- inputs: Record<string, unknown>,
132
- ): void {
133
- _activeLayoutWarningEmitter?.(`layout.guard.return-empty`, guard, inputs);
134
- }
116
+ export {
117
+ emitLayoutGuardWarning,
118
+ setActiveLayoutWarningEmitter,
119
+ type LayoutWarningEmitter,
120
+ } from "../diagnostics/layout-guard-warning.ts";
135
121
 
136
122
  // ---------------------------------------------------------------------------
137
123
  // Public read model types (shape-stable, cloned at the facet boundary)
@@ -152,6 +138,7 @@ export interface PublicLayoutDivergence {
152
138
  message: string;
153
139
  regionKinds?: readonly RuntimePageRegion["kind"][];
154
140
  fragmentIds?: readonly string[];
141
+ objectIds?: readonly string[];
155
142
  }
156
143
 
157
144
  export interface PublicPageFrame {
@@ -160,6 +147,7 @@ export interface PublicPageFrame {
160
147
  pageIndex: number;
161
148
  sectionIndex: number;
162
149
  displayPageNumber: number;
150
+ completeness: RuntimePageFrame["completeness"];
163
151
  physicalBoundsTwips: PublicTwipsRect;
164
152
  divergenceIds: readonly string[];
165
153
  signature: string;
@@ -180,20 +168,29 @@ export interface PublicPageLocalStoryInstance {
180
168
  variant: "default" | "first" | "even" | "odd";
181
169
  relationshipId: string;
182
170
  sectionIndex?: number;
171
+ resolvedFields: readonly PublicResolvedStoryField[];
183
172
  anchoredObjects: readonly PublicStoryAnchoredObject[];
184
173
  measuredFrameHeightTwips: number;
185
174
  signature: string;
186
175
  }
187
176
 
177
+ export interface PublicResolvedStoryField {
178
+ fieldId: string;
179
+ family: string;
180
+ displayText: string;
181
+ }
182
+
188
183
  export interface PublicStoryAnchoredObject {
189
184
  objectId: string;
190
185
  sourceType: RuntimeStoryAnchoredObject["sourceType"];
191
186
  display: RuntimeStoryAnchoredObject["display"];
187
+ wrapMode?: RuntimeStoryAnchoredObject["wrapMode"];
192
188
  extentTwips?: {
193
189
  widthTwips: number;
194
190
  heightTwips: number;
195
191
  };
196
192
  relationshipIds?: readonly string[];
193
+ mediaIds?: readonly string[];
197
194
  preserveOnly: boolean;
198
195
  divergenceIds: readonly string[];
199
196
  }
@@ -1503,6 +1500,7 @@ function toPublicPageFrame(frame: RuntimePageFrame): PublicPageFrame {
1503
1500
  pageIndex: frame.pageIndex,
1504
1501
  sectionIndex: frame.sectionIndex,
1505
1502
  displayPageNumber: frame.displayPageNumber,
1503
+ completeness: frame.completeness,
1506
1504
  physicalBoundsTwips: toPublicTwipsRect(frame.physicalBoundsTwips),
1507
1505
  divergenceIds: [...frame.divergenceIds],
1508
1506
  signature: frame.signature,
@@ -1523,12 +1521,23 @@ function toPublicPageLocalStory(
1523
1521
  variant: story.variant,
1524
1522
  relationshipId: story.relationshipId,
1525
1523
  ...(story.sectionIndex !== undefined ? { sectionIndex: story.sectionIndex } : {}),
1524
+ resolvedFields: story.resolvedFields.map(toPublicResolvedStoryField),
1526
1525
  anchoredObjects: story.anchoredObjects.map(toPublicStoryAnchoredObject),
1527
1526
  measuredFrameHeightTwips: story.measuredFrameHeightTwips,
1528
1527
  signature: story.signature,
1529
1528
  };
1530
1529
  }
1531
1530
 
1531
+ function toPublicResolvedStoryField(
1532
+ field: RuntimePageFrame["pageLocalStories"][number]["resolvedFields"][number],
1533
+ ): PublicResolvedStoryField {
1534
+ return {
1535
+ fieldId: field.fieldId,
1536
+ family: field.family,
1537
+ displayText: field.displayText,
1538
+ };
1539
+ }
1540
+
1532
1541
  function toPublicStoryAnchoredObject(
1533
1542
  object: RuntimeStoryAnchoredObject,
1534
1543
  ): PublicStoryAnchoredObject {
@@ -1536,6 +1545,7 @@ function toPublicStoryAnchoredObject(
1536
1545
  objectId: object.objectId,
1537
1546
  sourceType: object.sourceType,
1538
1547
  display: object.display,
1548
+ ...(object.wrapMode !== undefined ? { wrapMode: object.wrapMode } : {}),
1539
1549
  ...(object.extentTwips
1540
1550
  ? {
1541
1551
  extentTwips: {
@@ -1545,6 +1555,7 @@ function toPublicStoryAnchoredObject(
1545
1555
  }
1546
1556
  : {}),
1547
1557
  ...(object.relationshipIds ? { relationshipIds: [...object.relationshipIds] } : {}),
1558
+ ...(object.mediaIds ? { mediaIds: [...object.mediaIds] } : {}),
1548
1559
  preserveOnly: object.preserveOnly,
1549
1560
  divergenceIds: [...object.divergenceIds],
1550
1561
  };
@@ -1561,6 +1572,7 @@ function toPublicLayoutDivergence(
1561
1572
  message: divergence.message,
1562
1573
  ...(divergence.regionKinds !== undefined ? { regionKinds: [...divergence.regionKinds] } : {}),
1563
1574
  ...(divergence.fragmentIds !== undefined ? { fragmentIds: [...divergence.fragmentIds] } : {}),
1575
+ ...(divergence.objectIds !== undefined ? { objectIds: [...divergence.objectIds] } : {}),
1564
1576
  };
1565
1577
  }
1566
1578
 
@@ -1644,6 +1656,9 @@ function cloneContinuationCursor(
1644
1656
  ...cursor,
1645
1657
  rowRange: { ...cursor.rowRange },
1646
1658
  repeatedHeaderRowIndexes: [...cursor.repeatedHeaderRowIndexes],
1659
+ ...(cursor.splitRowCarry
1660
+ ? { splitRowCarry: cursor.splitRowCarry.map((carry) => ({ ...carry })) }
1661
+ : {}),
1647
1662
  verticalMergeCarry: cursor.verticalMergeCarry.map((carry) => ({ ...carry })),
1648
1663
  };
1649
1664
  }
@@ -1657,6 +1672,47 @@ function cloneFragmentLayoutObject(
1657
1672
  ...(layoutObject.fieldFamilies !== undefined
1658
1673
  ? { fieldFamilies: [...layoutObject.fieldFamilies] }
1659
1674
  : {}),
1675
+ ...(layoutObject.fieldRegions !== undefined
1676
+ ? {
1677
+ fieldRegions: layoutObject.fieldRegions.map((region) => ({
1678
+ ...region,
1679
+ ...(region.sourceRef !== undefined ? { sourceRef: { ...region.sourceRef } } : {}),
1680
+ ...(region.fieldEvidence !== undefined
1681
+ ? {
1682
+ fieldEvidence: {
1683
+ ...region.fieldEvidence,
1684
+ ...(region.fieldEvidence.sourceRef !== undefined
1685
+ ? { sourceRef: { ...region.fieldEvidence.sourceRef } }
1686
+ : {}),
1687
+ },
1688
+ }
1689
+ : {}),
1690
+ })),
1691
+ }
1692
+ : {}),
1693
+ ...(layoutObject.numbering !== undefined
1694
+ ? { numbering: cloneNumberingLayoutFacts(layoutObject.numbering) }
1695
+ : {}),
1696
+ ...(layoutObject.numberingRows !== undefined
1697
+ ? { numberingRows: layoutObject.numberingRows.map(cloneNumberingLayoutFacts) }
1698
+ : {}),
1699
+ };
1700
+ }
1701
+
1702
+ function cloneNumberingLayoutFacts(
1703
+ numbering: NonNullable<NonNullable<RuntimeBlockFragment["layoutObject"]>["numbering"]>,
1704
+ ): NonNullable<NonNullable<RuntimeBlockFragment["layoutObject"]>["numbering"]> {
1705
+ return {
1706
+ ...numbering,
1707
+ ...(numbering.markerLane !== undefined
1708
+ ? { markerLane: { ...numbering.markerLane } }
1709
+ : {}),
1710
+ ...(numbering.textColumn !== undefined
1711
+ ? { textColumn: { ...numbering.textColumn } }
1712
+ : {}),
1713
+ ...(numbering.tabStops !== undefined
1714
+ ? { tabStops: numbering.tabStops.map((tab) => ({ ...tab })) }
1715
+ : {}),
1660
1716
  };
1661
1717
  }
1662
1718
 
@@ -1908,10 +1964,8 @@ function findPageForOffsetAndStory(
1908
1964
  // `src/runtime/geometry/project-anchors.ts` in refactor/05 Slice 2b. The
1909
1965
  // facet's `getLineBoxes` / `getScopeRailSegments` / `getAllScopeRailSegments`
1910
1966
  // / `getAllScopeCardModels` methods above now import from there. The D1
1911
- // `emitLayoutGuardWarning` probes (including the
1912
- // `collectScopeRailSegmentsForQuery` null-input probe) are ported alongside
1913
- // via the exported `emitLayoutGuardWarning` symbol — they continue to
1914
- // fire through `setActiveLayoutWarningEmitter`.
1967
+ // `emitLayoutGuardWarning` probes continue to fire through the shared
1968
+ // diagnostics helper behind `setActiveLayoutWarningEmitter`.
1915
1969
  //
1916
1970
  // Hit-test (`resolveHitTest`, `hitTestRegion`, `containsPoint`) and
1917
1971
  // anchor-rect resolution (`resolveAnchorRects`) were moved in Slice 2a.