@beyondwork/docx-react-component 1.0.106 → 1.0.109

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
@@ -44,12 +44,15 @@ import type {
44
44
  GeometryIndexRegion,
45
45
  GeometryIndexSlice,
46
46
  GeometryObjectHandleEntry,
47
+ GeometryPageFrameCompleteness,
48
+ GeometryPageFrameCompletenessCounts,
47
49
  GeometryPrecision,
48
50
  GeometryPrecisionCounts,
49
51
  GeometryRect,
50
52
  GeometryRehydrationStatus,
51
53
  GeometryReplacementEnvelopeEntry,
52
54
  GeometrySourceIdentity,
55
+ GeometryTableContinuation,
53
56
  SemanticDisplayEntry,
54
57
  } from "./geometry-types.ts";
55
58
  import { buildObjectHandleRectsFromRect } from "./object-handles.ts";
@@ -63,14 +66,18 @@ export function createUnavailableGeometryCoverage(): GeometryIndexCoverage {
63
66
  return {
64
67
  status: "unavailable",
65
68
  pageCount: 0,
69
+ pageFrameCompleteness: createPageFrameCompletenessCounts(),
66
70
  regionCount: 0,
67
71
  sliceCount: 0,
68
72
  lineCount: 0,
69
73
  anchorCount: 0,
70
74
  hitTargetCount: 0,
71
75
  semanticEntryCount: 0,
76
+ pageLocalFieldLedgerCount: 0,
72
77
  replacementEnvelopeCount: 0,
73
78
  objectHandleCount: 0,
79
+ layoutDivergenceObjectCount: 0,
80
+ splitRowCarryCount: 0,
74
81
  precision: createPrecisionCounts(),
75
82
  };
76
83
  }
@@ -93,8 +100,15 @@ export function projectGeometryIndexFromFrame(
93
100
  const objectHandleEntries = new Map<string, MutableObjectHandleEntry>();
94
101
  const projectedBlocksByStory = new Map<string, ProjectedScopeBlock[]>();
95
102
  const precision = createPrecisionCounts();
103
+ const pageFrameCompleteness = createPageFrameCompletenessCounts();
104
+ let pageLocalFieldLedgerCount = 0;
105
+ let layoutDivergenceObjectCount = 0;
106
+ let splitRowCarryCount = 0;
96
107
 
97
108
  for (const page of frame.pages) {
109
+ const pageMetadata = pageFrameMetadata(page);
110
+ pageFrameCompleteness[pageMetadata.frameCompleteness] += 1;
111
+ layoutDivergenceObjectCount += pageMetadata.layoutDivergenceObjectIds.length;
98
112
  const regionEntries = collectRegionEntries(page);
99
113
  const regionIds: string[] = [];
100
114
 
@@ -107,6 +121,7 @@ export function projectGeometryIndexFromFrame(
107
121
  const sliceIds: string[] = [];
108
122
 
109
123
  for (const block of region.blocks) {
124
+ splitRowCarryCount += countSplitRowCarry(block);
110
125
  const sliceId = makeSliceId(regionId, block.fragment.fragmentId);
111
126
  const sliceIdentity = identities?.sliceIdentity(
112
127
  storyKey,
@@ -142,7 +157,7 @@ export function projectGeometryIndexFromFrame(
142
157
  fragmentId: anchor.fragmentId,
143
158
  lineId,
144
159
  runtimeOffset: anchor.runtimeOffset,
145
- rect: toGeometryRect(anchor.frame),
160
+ rect: { ...toGeometryRect(anchor.frame), precision: "exact" },
146
161
  precision: "exact",
147
162
  ...(anchorIdentity ? { sourceIdentity: anchorIdentity } : {}),
148
163
  });
@@ -158,7 +173,7 @@ export function projectGeometryIndexFromFrame(
158
173
  blockId: block.fragment.blockId,
159
174
  fragmentId: block.fragment.fragmentId,
160
175
  lineIndex: line.line.lineIndex,
161
- rect: toGeometryRect(line.frame),
176
+ rect: { ...toGeometryRect(line.frame), precision: "exact" },
162
177
  anchorIds,
163
178
  });
164
179
  recordPrecision(precision, "exact");
@@ -171,7 +186,7 @@ export function projectGeometryIndexFromFrame(
171
186
  blockId: block.fragment.blockId,
172
187
  fragmentId: block.fragment.fragmentId,
173
188
  lineIndex: line.line.lineIndex,
174
- rect: toGeometryRect(line.frame),
189
+ rect: { ...toGeometryRect(line.frame), precision: "exact" },
175
190
  precision: "exact",
176
191
  });
177
192
  recordPrecision(precision, "exact");
@@ -189,6 +204,7 @@ export function projectGeometryIndexFromFrame(
189
204
  rect: toGeometryRect(line.frame),
190
205
  status: "realized",
191
206
  precision: "exact",
207
+ frameCompleteness: pageMetadata.frameCompleteness,
192
208
  });
193
209
  recordPrecision(precision, "exact");
194
210
  }
@@ -237,6 +253,9 @@ export function projectGeometryIndexFromFrame(
237
253
  entries: semanticEntries,
238
254
  projectedBlocksByStory,
239
255
  precision,
256
+ frameCompleteness: pageMetadata.frameCompleteness,
257
+ layoutDivergenceIds: pageMetadata.layoutDivergenceIds,
258
+ layoutDivergenceObjectIds: pageMetadata.layoutDivergenceObjectIds,
240
259
  });
241
260
  }
242
261
 
@@ -257,12 +276,31 @@ export function projectGeometryIndexFromFrame(
257
276
  pageIndex: page.page.pageIndex,
258
277
  rect: toGeometryRect(page.frame),
259
278
  regionIds,
279
+ frameCompleteness: pageMetadata.frameCompleteness,
280
+ ...(pageMetadata.displayPageNumber !== undefined
281
+ ? { displayPageNumber: pageMetadata.displayPageNumber }
282
+ : {}),
283
+ ...(pageMetadata.layoutDivergenceIds.length > 0
284
+ ? { layoutDivergenceIds: pageMetadata.layoutDivergenceIds }
285
+ : {}),
286
+ ...(pageMetadata.layoutDivergenceObjectIds.length > 0
287
+ ? { layoutDivergenceObjectIds: pageMetadata.layoutDivergenceObjectIds }
288
+ : {}),
260
289
  });
261
290
  recordPrecision(precision, "exact");
291
+ pageLocalFieldLedgerCount += appendPageLocalFieldLedgerSemanticEntries({
292
+ page,
293
+ entries: semanticEntries,
294
+ precision,
295
+ frameCompleteness: pageMetadata.frameCompleteness,
296
+ layoutDivergenceIds: pageMetadata.layoutDivergenceIds,
297
+ layoutDivergenceObjectIds: pageMetadata.layoutDivergenceObjectIds,
298
+ });
262
299
  appendPageLocalObjectHandleEntries({
263
300
  page,
264
301
  entries: objectHandleEntries,
265
302
  precision,
303
+ divergenceIdsByObjectId: pageMetadata.divergenceIdsByObjectId,
266
304
  });
267
305
  }
268
306
 
@@ -277,14 +315,18 @@ export function projectGeometryIndexFromFrame(
277
315
  const coverage: GeometryIndexCoverage = {
278
316
  status: "realized",
279
317
  pageCount: pages.length,
318
+ pageFrameCompleteness,
280
319
  regionCount: regions.length,
281
320
  sliceCount: slices.length,
282
321
  lineCount: lines.length,
283
322
  anchorCount: anchors.length,
284
323
  hitTargetCount: hitTargets.length,
285
324
  semanticEntryCount: semanticEntries.length,
325
+ pageLocalFieldLedgerCount,
286
326
  replacementEnvelopeCount: replacementEnvelopes.length,
287
327
  objectHandleCount: objectHandles.length,
328
+ layoutDivergenceObjectCount,
329
+ splitRowCarryCount,
288
330
  precision,
289
331
  };
290
332
 
@@ -312,6 +354,7 @@ export function summarizeGeometryCoverageFromFrame(
312
354
  if (!frame) return createUnavailableGeometryCoverage();
313
355
 
314
356
  const precision = createPrecisionCounts();
357
+ const pageFrameCompleteness = createPageFrameCompletenessCounts();
315
358
  let pageCount = 0;
316
359
  let regionCount = 0;
317
360
  let sliceCount = 0;
@@ -319,14 +362,25 @@ export function summarizeGeometryCoverageFromFrame(
319
362
  let anchorCount = 0;
320
363
  let hitTargetCount = 0;
321
364
  let semanticEntryCount = 0;
365
+ let pageLocalFieldLedgerCount = 0;
366
+ let layoutDivergenceObjectCount = 0;
367
+ let splitRowCarryCount = 0;
322
368
 
323
369
  for (const page of frame.pages) {
370
+ const pageMetadata = pageFrameMetadata(page);
324
371
  pageCount += 1;
372
+ pageFrameCompleteness[pageMetadata.frameCompleteness] += 1;
373
+ layoutDivergenceObjectCount += pageMetadata.layoutDivergenceObjectIds.length;
325
374
  recordPrecision(precision, "exact");
375
+ const pageLocalFieldCount = countPageLocalFieldLedgers(page);
376
+ pageLocalFieldLedgerCount += pageLocalFieldCount;
377
+ semanticEntryCount += pageLocalFieldCount;
378
+ precision["within-tolerance"] += pageLocalFieldCount;
326
379
  for (const [region] of collectRegionEntries(page)) {
327
380
  regionCount += 1;
328
381
  recordPrecision(precision, "exact");
329
382
  for (const block of region.blocks) {
383
+ splitRowCarryCount += countSplitRowCarry(block);
330
384
  sliceCount += 1;
331
385
  recordPrecision(precision, "exact");
332
386
  for (const line of block.lines) {
@@ -355,18 +409,127 @@ export function summarizeGeometryCoverageFromFrame(
355
409
  return {
356
410
  status: "realized",
357
411
  pageCount,
412
+ pageFrameCompleteness,
358
413
  regionCount,
359
414
  sliceCount,
360
415
  lineCount,
361
416
  anchorCount,
362
417
  hitTargetCount,
363
418
  semanticEntryCount,
419
+ pageLocalFieldLedgerCount,
364
420
  replacementEnvelopeCount: 0,
365
421
  objectHandleCount: 0,
422
+ layoutDivergenceObjectCount,
423
+ splitRowCarryCount,
366
424
  precision,
367
425
  };
368
426
  }
369
427
 
428
+ interface PageFrameMetadata {
429
+ frameCompleteness: GeometryPageFrameCompleteness;
430
+ displayPageNumber?: number;
431
+ layoutDivergenceIds: readonly string[];
432
+ layoutDivergenceObjectIds: readonly string[];
433
+ divergenceIdsByObjectId: ReadonlyMap<string, readonly string[]>;
434
+ }
435
+
436
+ function pageFrameMetadata(page: RenderPage): PageFrameMetadata {
437
+ const frame = page.page.frame;
438
+ const divergenceIds = frame?.divergenceIds ? [...frame.divergenceIds] : [];
439
+ const divergenceIdsByObjectId = new Map<string, string[]>();
440
+ for (const divergence of page.page.divergences ?? []) {
441
+ for (const objectId of divergence.objectIds ?? []) {
442
+ const list = divergenceIdsByObjectId.get(objectId);
443
+ if (list) {
444
+ appendUnique(list, divergence.divergenceId);
445
+ } else {
446
+ divergenceIdsByObjectId.set(objectId, [divergence.divergenceId]);
447
+ }
448
+ }
449
+ }
450
+ const objectIds = Array.from(divergenceIdsByObjectId.keys()).sort();
451
+ return {
452
+ frameCompleteness: frame?.completeness ?? "absent",
453
+ ...(frame?.displayPageNumber !== undefined
454
+ ? { displayPageNumber: frame.displayPageNumber }
455
+ : {}),
456
+ layoutDivergenceIds: divergenceIds,
457
+ layoutDivergenceObjectIds: objectIds,
458
+ divergenceIdsByObjectId,
459
+ };
460
+ }
461
+
462
+ function appendPageLocalFieldLedgerSemanticEntries(input: {
463
+ page: RenderPage;
464
+ entries: SemanticDisplayEntry[];
465
+ precision: GeometryPrecisionCounts;
466
+ frameCompleteness: GeometryPageFrameCompleteness;
467
+ layoutDivergenceIds: readonly string[];
468
+ layoutDivergenceObjectIds: readonly string[];
469
+ }): number {
470
+ const {
471
+ page,
472
+ entries,
473
+ precision,
474
+ frameCompleteness,
475
+ layoutDivergenceIds,
476
+ layoutDivergenceObjectIds,
477
+ } = input;
478
+ let count = 0;
479
+ for (const story of page.page.frame?.pageLocalStories ?? []) {
480
+ if (story.resolvedFields.length === 0) continue;
481
+ const region =
482
+ story.kind === "header" ? page.regions.header : page.regions.footer;
483
+ if (!region) continue;
484
+ for (const field of story.resolvedFields) {
485
+ entries.push({
486
+ entryId: `semantic:page-local-field-ledger:${page.page.pageId}:${story.instanceId}:${field.fieldId}`,
487
+ kind: "page-local-field-ledger",
488
+ pageId: page.page.pageId,
489
+ pageIndex: page.page.pageIndex,
490
+ regionKind: region.region.kind,
491
+ pageLocalStoryId: story.instanceId,
492
+ pageLocalStoryKind: story.kind,
493
+ pageLocalStoryVariant: story.variant,
494
+ resolvedFieldId: field.fieldId,
495
+ resolvedFieldFamily: field.family,
496
+ resolvedFieldDisplayText: field.displayText,
497
+ frameCompleteness,
498
+ ...(layoutDivergenceIds.length > 0 ? { layoutDivergenceIds } : {}),
499
+ ...(layoutDivergenceObjectIds.length > 0
500
+ ? { layoutDivergenceObjectIds }
501
+ : {}),
502
+ rect: {
503
+ ...toGeometryRect(region.frame),
504
+ precision: "within-tolerance",
505
+ },
506
+ status: "realized",
507
+ precision: "within-tolerance",
508
+ });
509
+ recordPrecision(precision, "within-tolerance");
510
+ count += 1;
511
+ }
512
+ }
513
+ return count;
514
+ }
515
+
516
+ function countPageLocalFieldLedgers(page: RenderPage): number {
517
+ let count = 0;
518
+ for (const story of page.page.frame?.pageLocalStories ?? []) {
519
+ const region =
520
+ story.kind === "header" ? page.regions.header : page.regions.footer;
521
+ if (!region) continue;
522
+ count += story.resolvedFields.length;
523
+ }
524
+ return count;
525
+ }
526
+
527
+ function countSplitRowCarry(block: RenderBlock): number {
528
+ return block.fragment.continuation?.kind === "table"
529
+ ? block.fragment.continuation.splitRowCarry?.length ?? 0
530
+ : 0;
531
+ }
532
+
370
533
  function appendBlockSemanticEntries(input: {
371
534
  page: RenderPage;
372
535
  region: RenderStoryRegion;
@@ -378,6 +541,9 @@ function appendBlockSemanticEntries(input: {
378
541
  entries: SemanticDisplayEntry[];
379
542
  projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
380
543
  precision: GeometryPrecisionCounts;
544
+ frameCompleteness: GeometryPageFrameCompleteness;
545
+ layoutDivergenceIds: readonly string[];
546
+ layoutDivergenceObjectIds: readonly string[];
381
547
  }): void {
382
548
  const {
383
549
  page,
@@ -390,11 +556,18 @@ function appendBlockSemanticEntries(input: {
390
556
  entries,
391
557
  projectedBlocksByStory,
392
558
  precision,
559
+ frameCompleteness,
560
+ layoutDivergenceIds,
561
+ layoutDivergenceObjectIds,
393
562
  } = input;
394
563
  const tableIdentity = identities?.tableIdentity(
395
564
  storyKey,
396
565
  block.fragment.blockId,
397
566
  );
567
+ const sliceIdentity = identities?.sliceIdentity(
568
+ storyKey,
569
+ block.fragment.blockId,
570
+ );
398
571
  const base = {
399
572
  pageId: page.page.pageId,
400
573
  pageIndex: page.page.pageIndex,
@@ -405,7 +578,19 @@ function appendBlockSemanticEntries(input: {
405
578
  fragmentId: block.fragment.fragmentId,
406
579
  } as const;
407
580
 
581
+ appendFragmentLayoutObjectSemanticEntries({
582
+ base,
583
+ block,
584
+ sourceIdentity: sliceIdentity,
585
+ entries,
586
+ precision,
587
+ frameCompleteness,
588
+ layoutDivergenceIds,
589
+ layoutDivergenceObjectIds,
590
+ });
591
+
408
592
  if (block.kind === "table") {
593
+ const tableContinuation = tableContinuationMetadata(block);
409
594
  entries.push({
410
595
  ...base,
411
596
  entryId: `semantic:table-frame:${sliceId}`,
@@ -413,6 +598,12 @@ function appendBlockSemanticEntries(input: {
413
598
  rect: toGeometryRect(block.frame),
414
599
  status: "realized",
415
600
  precision: "exact",
601
+ frameCompleteness,
602
+ ...(layoutDivergenceIds.length > 0 ? { layoutDivergenceIds } : {}),
603
+ ...(layoutDivergenceObjectIds.length > 0
604
+ ? { layoutDivergenceObjectIds }
605
+ : {}),
606
+ ...(tableContinuation ? { tableContinuation } : {}),
416
607
  ...(tableIdentity
417
608
  ? { sourceIdentity: tableSourceIdentity(tableIdentity) }
418
609
  : {}),
@@ -421,6 +612,7 @@ function appendBlockSemanticEntries(input: {
421
612
 
422
613
  const plan = block.tablePlan;
423
614
  if (!plan) return;
615
+ const visibleRows = resolveVisibleTableRows(block);
424
616
  if (tableIdentity) {
425
617
  recordTableCellScopeBlocks({
426
618
  table: tableIdentity,
@@ -429,20 +621,23 @@ function appendBlockSemanticEntries(input: {
429
621
  storyKey,
430
622
  identities,
431
623
  projectedBlocksByStory,
624
+ visibleRows,
432
625
  });
433
626
  }
434
- const rowCount = resolveTableRowCount(block);
627
+ const visibleRowCount = visibleRows.length;
435
628
  const rowHeightPx =
436
- rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
437
- for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
629
+ visibleRowCount > 0
630
+ ? block.frame.heightPx / visibleRowCount
631
+ : block.frame.heightPx;
632
+ for (const row of visibleRows) {
438
633
  entries.push({
439
634
  ...base,
440
- entryId: `semantic:table-row:${sliceId}:${rowIndex}`,
635
+ entryId: `semantic:table-row:${sliceId}:${row.rowIndex}`,
441
636
  kind: "table-row",
442
- rowIndex,
637
+ rowIndex: row.rowIndex,
443
638
  rect: {
444
639
  leftPx: block.frame.leftPx,
445
- topPx: block.frame.topPx + rowIndex * rowHeightPx,
640
+ topPx: block.frame.topPx + row.visualIndex * rowHeightPx,
446
641
  widthPx: block.frame.widthPx,
447
642
  heightPx: rowHeightPx,
448
643
  space: "frame",
@@ -450,15 +645,27 @@ function appendBlockSemanticEntries(input: {
450
645
  },
451
646
  status: "realized",
452
647
  precision: "within-tolerance",
648
+ frameCompleteness,
649
+ ...tableRowContinuationMetadata(block, row.rowIndex),
453
650
  ...(tableIdentity
454
- ? { sourceIdentity: tableRowSourceIdentity(tableIdentity, rowIndex) }
651
+ ? {
652
+ sourceIdentity: tableRowSourceIdentity(
653
+ tableIdentity,
654
+ row.rowIndex,
655
+ ),
656
+ }
455
657
  : {}),
456
658
  });
457
659
  recordPrecision(precision, "within-tolerance");
458
660
  }
459
661
 
460
662
  for (const cell of plan.bandClasses.cells) {
461
- const rect = pageFrameCellRect(block, cell.rowIndex, cell.columnIndex);
663
+ const rect = pageFrameCellRect(
664
+ block,
665
+ cell.rowIndex,
666
+ cell.columnIndex,
667
+ visibleRows,
668
+ );
462
669
  if (!rect) continue;
463
670
  entries.push({
464
671
  ...base,
@@ -472,6 +679,8 @@ function appendBlockSemanticEntries(input: {
472
679
  },
473
680
  status: "realized",
474
681
  precision: "within-tolerance",
682
+ frameCompleteness,
683
+ ...tableRowContinuationMetadata(block, cell.rowIndex),
475
684
  ...(tableIdentity
476
685
  ? {
477
686
  sourceIdentity: tableCellSourceIdentity(
@@ -498,6 +707,7 @@ function appendBlockSemanticEntries(input: {
498
707
  },
499
708
  status: "realized",
500
709
  precision: "heuristic",
710
+ frameCompleteness,
501
711
  });
502
712
  recordPrecision(precision, "heuristic");
503
713
  return;
@@ -511,6 +721,7 @@ function appendBlockSemanticEntries(input: {
511
721
  rect: toGeometryRect(block.frame),
512
722
  status: "realized",
513
723
  precision: "exact",
724
+ frameCompleteness,
514
725
  });
515
726
  recordPrecision(precision, "exact");
516
727
  return;
@@ -524,18 +735,189 @@ function appendBlockSemanticEntries(input: {
524
735
  rect: toGeometryRect(block.frame),
525
736
  status: "realized",
526
737
  precision: "exact",
738
+ frameCompleteness,
527
739
  });
528
740
  recordPrecision(precision, "exact");
529
741
  }
530
742
  }
531
743
 
744
+ function appendFragmentLayoutObjectSemanticEntries(input: {
745
+ base: {
746
+ pageId: string;
747
+ pageIndex: number;
748
+ regionId: string;
749
+ regionKind: RenderStoryRegion["region"]["kind"];
750
+ sliceId: string;
751
+ blockId: string;
752
+ fragmentId: string;
753
+ };
754
+ block: RenderBlock;
755
+ sourceIdentity: GeometrySourceIdentity | undefined;
756
+ entries: SemanticDisplayEntry[];
757
+ precision: GeometryPrecisionCounts;
758
+ frameCompleteness: GeometryPageFrameCompleteness;
759
+ layoutDivergenceIds: readonly string[];
760
+ layoutDivergenceObjectIds: readonly string[];
761
+ }): void {
762
+ const {
763
+ base,
764
+ block,
765
+ sourceIdentity,
766
+ entries,
767
+ precision,
768
+ frameCompleteness,
769
+ layoutDivergenceIds,
770
+ layoutDivergenceObjectIds,
771
+ } = input;
772
+ const layoutObject = block.fragment.layoutObject;
773
+ if (!layoutObject) return;
774
+
775
+ if (layoutObject.kind === "field-region") {
776
+ entries.push({
777
+ ...base,
778
+ entryId: `semantic:field-region:${base.sliceId}`,
779
+ kind: "field-region",
780
+ layoutObjectId: layoutObject.objectId,
781
+ ...(layoutObject.fieldFamilies !== undefined
782
+ ? { fieldFamilies: [...layoutObject.fieldFamilies] }
783
+ : {}),
784
+ rect: {
785
+ ...toGeometryRect(block.frame),
786
+ precision: "within-tolerance",
787
+ },
788
+ status: "realized",
789
+ precision: "within-tolerance",
790
+ frameCompleteness,
791
+ ...(layoutDivergenceIds.length > 0 ? { layoutDivergenceIds } : {}),
792
+ ...(layoutDivergenceObjectIds.length > 0
793
+ ? { layoutDivergenceObjectIds }
794
+ : {}),
795
+ ...(sourceIdentity ? { sourceIdentity } : {}),
796
+ });
797
+ recordPrecision(precision, "within-tolerance");
798
+ return;
799
+ }
800
+
801
+ if (layoutObject.kind === "numbered-paragraph") {
802
+ const markerProjection = resolveNumberingMarkerProjection(block);
803
+ entries.push({
804
+ ...base,
805
+ entryId: `semantic:numbering-marker:${base.sliceId}`,
806
+ kind: "numbering-marker",
807
+ layoutObjectId: layoutObject.objectId,
808
+ rect: markerProjection.rect,
809
+ status: markerProjection.status,
810
+ precision: markerProjection.precision,
811
+ frameCompleteness,
812
+ ...(sourceIdentity ? { sourceIdentity } : {}),
813
+ });
814
+ recordPrecision(precision, markerProjection.precision);
815
+ }
816
+ }
817
+
818
+ function resolveNumberingMarkerProjection(block: RenderBlock): {
819
+ rect: GeometryRect;
820
+ precision: GeometryPrecision;
821
+ status: GeometryRehydrationStatus;
822
+ } {
823
+ const metadata = resolveNumberingMarkerProjectionMetadata(block);
824
+ const blockFrame = block.frame;
825
+ if (metadata.markerLane && metadata.measuredWidthTwips !== undefined) {
826
+ const markerLane = metadata.markerLane;
827
+ const pxPerTwip = blockFrame.widthPx / metadata.measuredWidthTwips;
828
+ const blockLeftPx = blockFrame.leftPx;
829
+ const blockRightPx = blockFrame.leftPx + Math.max(0, blockFrame.widthPx);
830
+ const rawLeftPx = blockFrame.leftPx + markerLane.startTwips * pxPerTwip;
831
+ const rawRightPx = rawLeftPx + markerLane.widthTwips * pxPerTwip;
832
+ const leftPx = clamp(rawLeftPx, blockLeftPx, blockRightPx);
833
+ const rightPx = clamp(rawRightPx, leftPx, blockRightPx);
834
+ return {
835
+ rect: {
836
+ leftPx,
837
+ topPx: blockFrame.topPx,
838
+ widthPx: rightPx - leftPx,
839
+ heightPx: blockFrame.heightPx,
840
+ space: "frame",
841
+ precision: metadata.precision,
842
+ },
843
+ precision: metadata.precision,
844
+ status: metadata.status,
845
+ };
846
+ }
847
+
848
+ const widthPx = Math.min(
849
+ Math.max(0, blockFrame.heightPx),
850
+ Math.max(0, blockFrame.widthPx),
851
+ );
852
+ return {
853
+ rect: {
854
+ leftPx: blockFrame.leftPx,
855
+ topPx: blockFrame.topPx,
856
+ widthPx,
857
+ heightPx: blockFrame.heightPx,
858
+ space: "frame",
859
+ precision: metadata.precision,
860
+ },
861
+ precision: metadata.precision,
862
+ status: metadata.status,
863
+ };
864
+ }
865
+
866
+ function resolveNumberingMarkerProjectionMetadata(block: RenderBlock): {
867
+ precision: GeometryPrecision;
868
+ status: GeometryRehydrationStatus;
869
+ markerLane?: {
870
+ readonly startTwips: number;
871
+ readonly widthTwips: number;
872
+ };
873
+ measuredWidthTwips?: number;
874
+ } {
875
+ const blockFrame = block.frame;
876
+ const layoutObject = block.fragment.layoutObject;
877
+ const markerLane =
878
+ layoutObject?.kind === "numbered-paragraph"
879
+ ? layoutObject.numbering?.markerLane
880
+ : undefined;
881
+ const measuredWidthTwips = layoutObject?.measuredExtentTwips.widthTwips;
882
+ if (
883
+ markerLane &&
884
+ markerLane.widthTwips > 0 &&
885
+ measuredWidthTwips !== undefined &&
886
+ measuredWidthTwips > 0 &&
887
+ Number.isFinite(blockFrame.widthPx)
888
+ ) {
889
+ return {
890
+ precision: "within-tolerance",
891
+ status: "realized",
892
+ markerLane,
893
+ measuredWidthTwips,
894
+ };
895
+ }
896
+ return {
897
+ precision: "heuristic",
898
+ status: "requires-rehydration",
899
+ };
900
+ }
901
+
532
902
  function countBlockSemanticEntries(block: RenderBlock): GeometryPrecisionCounts {
533
903
  const counts = createPrecisionCounts();
904
+ const layoutObject = block.fragment.layoutObject;
905
+ if (layoutObject?.kind === "field-region") {
906
+ counts["within-tolerance"] += 1;
907
+ } else if (layoutObject?.kind === "numbered-paragraph") {
908
+ counts[resolveNumberingMarkerProjectionMetadata(block).precision] += 1;
909
+ }
534
910
  if (block.kind === "table") {
535
911
  counts.exact += 1;
536
912
  if (block.tablePlan) {
537
- counts["within-tolerance"] += resolveTableRowCount(block);
538
- counts["within-tolerance"] += block.tablePlan.bandClasses.cells.length;
913
+ const visibleRows = resolveVisibleTableRows(block);
914
+ const visibleRowIndexes = new Set(
915
+ visibleRows.map((row) => row.rowIndex),
916
+ );
917
+ counts["within-tolerance"] += visibleRows.length;
918
+ counts["within-tolerance"] += block.tablePlan.bandClasses.cells.filter(
919
+ (cell) => visibleRowIndexes.has(cell.rowIndex),
920
+ ).length;
539
921
  }
540
922
  } else if (block.kind === "image-float") {
541
923
  counts.heuristic += 1;
@@ -558,13 +940,134 @@ function resolveTableRowCount(block: RenderBlock): number {
558
940
  );
559
941
  }
560
942
 
943
+ interface VisibleTableRow {
944
+ rowIndex: number;
945
+ visualIndex: number;
946
+ }
947
+
948
+ function tableContinuationMetadata(
949
+ block: RenderBlock,
950
+ ): GeometryTableContinuation | null {
951
+ const continuation =
952
+ block.fragment.continuation?.kind === "table"
953
+ ? block.fragment.continuation
954
+ : null;
955
+ const rowRange = block.fragment.tableRowRange ?? continuation?.rowRange;
956
+ if (!continuation && !rowRange) return null;
957
+
958
+ return {
959
+ ...(continuation
960
+ ? {
961
+ sequenceIndex: continuation.sequenceIndex,
962
+ sliceCount: continuation.sliceCount,
963
+ continuesFromPreviousPage: continuation.continuesFromPreviousPage,
964
+ continuesToNextPage: continuation.continuesToNextPage,
965
+ }
966
+ : {}),
967
+ ...(rowRange ? { rowRange: { ...rowRange } } : {}),
968
+ ...(continuation?.repeatedHeaderRowIndexes.length
969
+ ? {
970
+ repeatedHeaderRowIndexes: [
971
+ ...continuation.repeatedHeaderRowIndexes,
972
+ ],
973
+ }
974
+ : {}),
975
+ ...(continuation?.splitRowCarry?.length
976
+ ? {
977
+ splitRowCarry: continuation.splitRowCarry.map((carry) => ({
978
+ ...carry,
979
+ })),
980
+ }
981
+ : {}),
982
+ ...(continuation?.verticalMergeCarry.length
983
+ ? {
984
+ verticalMergeCarry: continuation.verticalMergeCarry.map((carry) => ({
985
+ ...carry,
986
+ })),
987
+ }
988
+ : {}),
989
+ };
990
+ }
991
+
992
+ function tableRowContinuationMetadata(
993
+ block: RenderBlock,
994
+ rowIndex: number,
995
+ ): Pick<SemanticDisplayEntry, "tableContinuation"> {
996
+ const tableContinuation = tableContinuationMetadata(block);
997
+ if (!tableContinuation) return {};
998
+ const repeatedHeaderRowIndexes =
999
+ tableContinuation.repeatedHeaderRowIndexes?.includes(rowIndex) === true
1000
+ ? [rowIndex]
1001
+ : undefined;
1002
+ const splitRowCarry = tableContinuation.splitRowCarry?.filter(
1003
+ (carry) => carry.rowIndex === rowIndex,
1004
+ );
1005
+
1006
+ if (
1007
+ repeatedHeaderRowIndexes === undefined &&
1008
+ (!splitRowCarry || splitRowCarry.length === 0)
1009
+ ) {
1010
+ return {};
1011
+ }
1012
+
1013
+ return {
1014
+ tableContinuation: {
1015
+ ...(repeatedHeaderRowIndexes ? { repeatedHeaderRowIndexes } : {}),
1016
+ ...(splitRowCarry && splitRowCarry.length > 0
1017
+ ? { splitRowCarry: splitRowCarry.map((carry) => ({ ...carry })) }
1018
+ : {}),
1019
+ },
1020
+ };
1021
+ }
1022
+
1023
+ function resolveVisibleTableRows(block: RenderBlock): readonly VisibleTableRow[] {
1024
+ const rowCount = resolveTableRowCount(block);
1025
+ if (rowCount <= 0) return [];
1026
+ const continuation =
1027
+ block.fragment.continuation?.kind === "table"
1028
+ ? block.fragment.continuation
1029
+ : null;
1030
+ const rowRange = block.fragment.tableRowRange ?? continuation?.rowRange;
1031
+ if (!rowRange) {
1032
+ return Array.from({ length: rowCount }, (_, rowIndex) => ({
1033
+ rowIndex,
1034
+ visualIndex: rowIndex,
1035
+ }));
1036
+ }
1037
+
1038
+ const start = Math.max(0, Math.min(rowCount, rowRange.from));
1039
+ const end = Math.max(start, Math.min(rowCount, rowRange.to));
1040
+ const rowIndexes: number[] = [];
1041
+ const seenRowIndexes = new Set<number>();
1042
+ const addRow = (rowIndex: number): void => {
1043
+ if (rowIndex < 0 || rowIndex >= rowCount) return;
1044
+ if (seenRowIndexes.has(rowIndex)) return;
1045
+ seenRowIndexes.add(rowIndex);
1046
+ rowIndexes.push(rowIndex);
1047
+ };
1048
+
1049
+ for (const rowIndex of continuation?.repeatedHeaderRowIndexes ?? []) {
1050
+ addRow(rowIndex);
1051
+ }
1052
+ for (let rowIndex = start; rowIndex < end; rowIndex += 1) {
1053
+ addRow(rowIndex);
1054
+ }
1055
+
1056
+ return rowIndexes.map((rowIndex, visualIndex) => ({ rowIndex, visualIndex }));
1057
+ }
1058
+
561
1059
  function pageFrameCellRect(
562
1060
  block: RenderBlock,
563
1061
  rowIndex: number,
564
1062
  columnIndex: number,
1063
+ visibleRows: readonly VisibleTableRow[] = resolveVisibleTableRows(block),
565
1064
  ): RenderFrameRect | null {
566
1065
  const plan = block.tablePlan;
567
1066
  if (!plan || plan.columnsTwips.length === 0) return null;
1067
+ const visualRowIndex = visibleRows.findIndex(
1068
+ (row) => row.rowIndex === rowIndex,
1069
+ );
1070
+ if (visualRowIndex < 0) return null;
568
1071
  const columnCount = plan.columnsTwips.length;
569
1072
  const totalWidthTwips = plan.columnsTwips.reduce(
570
1073
  (sum, value) => sum + Math.max(0, value),
@@ -591,12 +1094,12 @@ function pageFrameCellRect(
591
1094
  for (let i = columnIndex; i < columnIndex + columnSpan; i += 1) {
592
1095
  widthPx += (plan.columnsTwips[i] ?? 0) * pxPerTwip;
593
1096
  }
594
- const rowCount = resolveTableRowCount(block);
1097
+ const rowCount = visibleRows.length;
595
1098
  const rowHeightPx =
596
1099
  rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
597
1100
  return {
598
1101
  leftPx,
599
- topPx: block.frame.topPx + rowIndex * rowHeightPx,
1102
+ topPx: block.frame.topPx + visualRowIndex * rowHeightPx,
600
1103
  widthPx,
601
1104
  heightPx: rowHeightPx,
602
1105
  };
@@ -711,6 +1214,7 @@ interface MutableObjectHandleEntry {
711
1214
  rects: GeometryRect[];
712
1215
  status: GeometryRehydrationStatus;
713
1216
  precision: GeometryPrecision;
1217
+ layoutDivergenceIds?: string[];
714
1218
  sourceIdentity?: GeometrySourceIdentity;
715
1219
  }
716
1220
 
@@ -744,6 +1248,11 @@ function appendCanonicalObjectHandleEntries(input: {
744
1248
  if (existing) {
745
1249
  appendUnique(existing.pageIds, page.page.pageId);
746
1250
  existing.rects.push(...handleRects);
1251
+ appendDivergenceIdsForObject(
1252
+ existing,
1253
+ input.page,
1254
+ anchor.objectKey,
1255
+ );
747
1256
  if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
748
1257
  existing.precision = "heuristic";
749
1258
  existing.status = "requires-rehydration";
@@ -758,6 +1267,7 @@ function appendCanonicalObjectHandleEntries(input: {
758
1267
  rects: [...handleRects],
759
1268
  status,
760
1269
  precision: entryPrecision,
1270
+ ...layoutDivergenceIdsForObject(input.page, anchor.objectKey),
761
1271
  sourceIdentity: anchorSourceIdentity(
762
1272
  anchor,
763
1273
  exactObjectRect ? "direct" : "block-scoped",
@@ -771,9 +1281,14 @@ function appendPageLocalObjectHandleEntries(input: {
771
1281
  page: RenderPage;
772
1282
  entries: Map<string, MutableObjectHandleEntry>;
773
1283
  precision: GeometryPrecisionCounts;
1284
+ divergenceIdsByObjectId: ReadonlyMap<string, readonly string[]>;
774
1285
  }): void {
775
- const { page, entries, precision } = input;
1286
+ const { page, entries, precision, divergenceIdsByObjectId } = input;
776
1287
  const stories = page.page.frame?.pageLocalStories ?? [];
1288
+ const pxPerTwip =
1289
+ page.page.layout.pageWidth > 0
1290
+ ? page.frame.widthPx / page.page.layout.pageWidth
1291
+ : 1;
777
1292
  for (const story of stories) {
778
1293
  const regionFrame =
779
1294
  story.kind === "header" ? page.regions.header?.frame : page.regions.footer?.frame;
@@ -782,9 +1297,7 @@ function appendPageLocalObjectHandleEntries(input: {
782
1297
  const objectFrame = pageLocalObjectFrame(
783
1298
  regionFrame,
784
1299
  object.extentTwips,
785
- page.page.layout.pageWidth > 0
786
- ? page.frame.widthPx / page.page.layout.pageWidth
787
- : 1,
1300
+ pxPerTwip,
788
1301
  );
789
1302
  const handleRects = buildObjectHandleRectsFromRect(objectFrame, "heuristic");
790
1303
  const sourceIdentity: GeometrySourceIdentity = {
@@ -798,6 +1311,7 @@ function appendPageLocalObjectHandleEntries(input: {
798
1311
  if (existing) {
799
1312
  appendUnique(existing.pageIds, page.page.pageId);
800
1313
  existing.rects.push(...handleRects);
1314
+ appendDivergenceIds(existing, divergenceIdsByObjectId.get(object.objectId));
801
1315
  if (existing.precision !== "heuristic") {
802
1316
  existing.precision = "heuristic";
803
1317
  existing.status = "requires-rehydration";
@@ -811,6 +1325,7 @@ function appendPageLocalObjectHandleEntries(input: {
811
1325
  rects: [...handleRects],
812
1326
  status: "requires-rehydration",
813
1327
  precision: "heuristic",
1328
+ ...mutableLayoutDivergenceIds(divergenceIdsByObjectId.get(object.objectId)),
814
1329
  sourceIdentity,
815
1330
  });
816
1331
  recordPrecision(precision, "heuristic");
@@ -845,6 +1360,9 @@ function finalizeObjectHandleEntries(
845
1360
  rects: entry.rects,
846
1361
  status: entry.status,
847
1362
  precision: entry.precision,
1363
+ ...(entry.layoutDivergenceIds && entry.layoutDivergenceIds.length > 0
1364
+ ? { layoutDivergenceIds: [...entry.layoutDivergenceIds] }
1365
+ : {}),
848
1366
  ...(entry.sourceIdentity ? { sourceIdentity: entry.sourceIdentity } : {}),
849
1367
  }));
850
1368
  }
@@ -900,6 +1418,7 @@ function recordTableCellScopeBlocks(input: {
900
1418
  storyKey: string;
901
1419
  identities: GeometryIdentityLookup | null;
902
1420
  projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
1421
+ visibleRows: readonly VisibleTableRow[];
903
1422
  }): void {
904
1423
  const {
905
1424
  table,
@@ -908,12 +1427,18 @@ function recordTableCellScopeBlocks(input: {
908
1427
  storyKey,
909
1428
  identities,
910
1429
  projectedBlocksByStory,
1430
+ visibleRows,
911
1431
  } = input;
912
1432
  if (!identities) return;
913
1433
 
914
1434
  for (const row of table.rows) {
915
1435
  for (const cell of row.cells) {
916
- const rect = pageFrameCellRect(block, row.rowIndex, cell.gridColumnStart);
1436
+ const rect = pageFrameCellRect(
1437
+ block,
1438
+ row.rowIndex,
1439
+ cell.gridColumnStart,
1440
+ visibleRows,
1441
+ );
917
1442
  if (!rect) continue;
918
1443
  for (let blockIndex = 0; blockIndex < cell.blockCount; blockIndex += 1) {
919
1444
  const blockPath =
@@ -1302,6 +1827,10 @@ function toGeometryRect(rect: RenderFrameRect): GeometryRect {
1302
1827
  };
1303
1828
  }
1304
1829
 
1830
+ function clamp(value: number, min: number, max: number): number {
1831
+ return Math.min(Math.max(value, min), max);
1832
+ }
1833
+
1305
1834
  function makeRegionId(
1306
1835
  page: RenderPage,
1307
1836
  region: RenderStoryRegion,
@@ -1334,6 +1863,48 @@ function createPrecisionCounts(): GeometryPrecisionCounts {
1334
1863
  };
1335
1864
  }
1336
1865
 
1866
+ function createPageFrameCompletenessCounts(): GeometryPageFrameCompletenessCounts {
1867
+ return {
1868
+ complete: 0,
1869
+ partial: 0,
1870
+ absent: 0,
1871
+ };
1872
+ }
1873
+
1874
+ function layoutDivergenceIdsForObject(
1875
+ page: RenderPage,
1876
+ objectId: string,
1877
+ ): { readonly layoutDivergenceIds?: string[] } {
1878
+ const ids = page.page.divergences
1879
+ ?.filter((divergence) => divergence.objectIds?.includes(objectId) === true)
1880
+ .map((divergence) => divergence.divergenceId);
1881
+ return ids && ids.length > 0 ? { layoutDivergenceIds: ids } : {};
1882
+ }
1883
+
1884
+ function mutableLayoutDivergenceIds(
1885
+ ids: readonly string[] | undefined,
1886
+ ): { readonly layoutDivergenceIds?: string[] } {
1887
+ return ids && ids.length > 0 ? { layoutDivergenceIds: [...ids] } : {};
1888
+ }
1889
+
1890
+ function appendDivergenceIdsForObject(
1891
+ entry: MutableObjectHandleEntry,
1892
+ page: RenderPage,
1893
+ objectId: string,
1894
+ ): void {
1895
+ const ids = layoutDivergenceIdsForObject(page, objectId).layoutDivergenceIds;
1896
+ appendDivergenceIds(entry, ids);
1897
+ }
1898
+
1899
+ function appendDivergenceIds(
1900
+ entry: MutableObjectHandleEntry,
1901
+ ids: readonly string[] | undefined,
1902
+ ): void {
1903
+ if (!ids || ids.length === 0) return;
1904
+ if (!entry.layoutDivergenceIds) entry.layoutDivergenceIds = [];
1905
+ for (const id of ids) appendUnique(entry.layoutDivergenceIds, id);
1906
+ }
1907
+
1337
1908
  function recordPrecision(
1338
1909
  counts: GeometryPrecisionCounts,
1339
1910
  precision: keyof GeometryPrecisionCounts,