@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
@@ -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,8 +276,32 @@ 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
+ });
299
+ appendPageLocalObjectHandleEntries({
300
+ page,
301
+ entries: objectHandleEntries,
302
+ precision,
303
+ divergenceIdsByObjectId: pageMetadata.divergenceIdsByObjectId,
304
+ });
262
305
  }
263
306
 
264
307
  appendScopeReplacementEnvelopeEntries({
@@ -272,14 +315,18 @@ export function projectGeometryIndexFromFrame(
272
315
  const coverage: GeometryIndexCoverage = {
273
316
  status: "realized",
274
317
  pageCount: pages.length,
318
+ pageFrameCompleteness,
275
319
  regionCount: regions.length,
276
320
  sliceCount: slices.length,
277
321
  lineCount: lines.length,
278
322
  anchorCount: anchors.length,
279
323
  hitTargetCount: hitTargets.length,
280
324
  semanticEntryCount: semanticEntries.length,
325
+ pageLocalFieldLedgerCount,
281
326
  replacementEnvelopeCount: replacementEnvelopes.length,
282
327
  objectHandleCount: objectHandles.length,
328
+ layoutDivergenceObjectCount,
329
+ splitRowCarryCount,
283
330
  precision,
284
331
  };
285
332
 
@@ -307,6 +354,7 @@ export function summarizeGeometryCoverageFromFrame(
307
354
  if (!frame) return createUnavailableGeometryCoverage();
308
355
 
309
356
  const precision = createPrecisionCounts();
357
+ const pageFrameCompleteness = createPageFrameCompletenessCounts();
310
358
  let pageCount = 0;
311
359
  let regionCount = 0;
312
360
  let sliceCount = 0;
@@ -314,14 +362,25 @@ export function summarizeGeometryCoverageFromFrame(
314
362
  let anchorCount = 0;
315
363
  let hitTargetCount = 0;
316
364
  let semanticEntryCount = 0;
365
+ let pageLocalFieldLedgerCount = 0;
366
+ let layoutDivergenceObjectCount = 0;
367
+ let splitRowCarryCount = 0;
317
368
 
318
369
  for (const page of frame.pages) {
370
+ const pageMetadata = pageFrameMetadata(page);
319
371
  pageCount += 1;
372
+ pageFrameCompleteness[pageMetadata.frameCompleteness] += 1;
373
+ layoutDivergenceObjectCount += pageMetadata.layoutDivergenceObjectIds.length;
320
374
  recordPrecision(precision, "exact");
375
+ const pageLocalFieldCount = countPageLocalFieldLedgers(page);
376
+ pageLocalFieldLedgerCount += pageLocalFieldCount;
377
+ semanticEntryCount += pageLocalFieldCount;
378
+ precision["within-tolerance"] += pageLocalFieldCount;
321
379
  for (const [region] of collectRegionEntries(page)) {
322
380
  regionCount += 1;
323
381
  recordPrecision(precision, "exact");
324
382
  for (const block of region.blocks) {
383
+ splitRowCarryCount += countSplitRowCarry(block);
325
384
  sliceCount += 1;
326
385
  recordPrecision(precision, "exact");
327
386
  for (const line of block.lines) {
@@ -350,18 +409,127 @@ export function summarizeGeometryCoverageFromFrame(
350
409
  return {
351
410
  status: "realized",
352
411
  pageCount,
412
+ pageFrameCompleteness,
353
413
  regionCount,
354
414
  sliceCount,
355
415
  lineCount,
356
416
  anchorCount,
357
417
  hitTargetCount,
358
418
  semanticEntryCount,
419
+ pageLocalFieldLedgerCount,
359
420
  replacementEnvelopeCount: 0,
360
421
  objectHandleCount: 0,
422
+ layoutDivergenceObjectCount,
423
+ splitRowCarryCount,
361
424
  precision,
362
425
  };
363
426
  }
364
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
+
365
533
  function appendBlockSemanticEntries(input: {
366
534
  page: RenderPage;
367
535
  region: RenderStoryRegion;
@@ -373,6 +541,9 @@ function appendBlockSemanticEntries(input: {
373
541
  entries: SemanticDisplayEntry[];
374
542
  projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
375
543
  precision: GeometryPrecisionCounts;
544
+ frameCompleteness: GeometryPageFrameCompleteness;
545
+ layoutDivergenceIds: readonly string[];
546
+ layoutDivergenceObjectIds: readonly string[];
376
547
  }): void {
377
548
  const {
378
549
  page,
@@ -385,11 +556,18 @@ function appendBlockSemanticEntries(input: {
385
556
  entries,
386
557
  projectedBlocksByStory,
387
558
  precision,
559
+ frameCompleteness,
560
+ layoutDivergenceIds,
561
+ layoutDivergenceObjectIds,
388
562
  } = input;
389
563
  const tableIdentity = identities?.tableIdentity(
390
564
  storyKey,
391
565
  block.fragment.blockId,
392
566
  );
567
+ const sliceIdentity = identities?.sliceIdentity(
568
+ storyKey,
569
+ block.fragment.blockId,
570
+ );
393
571
  const base = {
394
572
  pageId: page.page.pageId,
395
573
  pageIndex: page.page.pageIndex,
@@ -400,7 +578,19 @@ function appendBlockSemanticEntries(input: {
400
578
  fragmentId: block.fragment.fragmentId,
401
579
  } as const;
402
580
 
581
+ appendFragmentLayoutObjectSemanticEntries({
582
+ base,
583
+ block,
584
+ sourceIdentity: sliceIdentity,
585
+ entries,
586
+ precision,
587
+ frameCompleteness,
588
+ layoutDivergenceIds,
589
+ layoutDivergenceObjectIds,
590
+ });
591
+
403
592
  if (block.kind === "table") {
593
+ const tableContinuation = tableContinuationMetadata(block);
404
594
  entries.push({
405
595
  ...base,
406
596
  entryId: `semantic:table-frame:${sliceId}`,
@@ -408,6 +598,12 @@ function appendBlockSemanticEntries(input: {
408
598
  rect: toGeometryRect(block.frame),
409
599
  status: "realized",
410
600
  precision: "exact",
601
+ frameCompleteness,
602
+ ...(layoutDivergenceIds.length > 0 ? { layoutDivergenceIds } : {}),
603
+ ...(layoutDivergenceObjectIds.length > 0
604
+ ? { layoutDivergenceObjectIds }
605
+ : {}),
606
+ ...(tableContinuation ? { tableContinuation } : {}),
411
607
  ...(tableIdentity
412
608
  ? { sourceIdentity: tableSourceIdentity(tableIdentity) }
413
609
  : {}),
@@ -416,6 +612,7 @@ function appendBlockSemanticEntries(input: {
416
612
 
417
613
  const plan = block.tablePlan;
418
614
  if (!plan) return;
615
+ const visibleRows = resolveVisibleTableRows(block);
419
616
  if (tableIdentity) {
420
617
  recordTableCellScopeBlocks({
421
618
  table: tableIdentity,
@@ -424,20 +621,23 @@ function appendBlockSemanticEntries(input: {
424
621
  storyKey,
425
622
  identities,
426
623
  projectedBlocksByStory,
624
+ visibleRows,
427
625
  });
428
626
  }
429
- const rowCount = resolveTableRowCount(block);
627
+ const visibleRowCount = visibleRows.length;
430
628
  const rowHeightPx =
431
- rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
432
- 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) {
433
633
  entries.push({
434
634
  ...base,
435
- entryId: `semantic:table-row:${sliceId}:${rowIndex}`,
635
+ entryId: `semantic:table-row:${sliceId}:${row.rowIndex}`,
436
636
  kind: "table-row",
437
- rowIndex,
637
+ rowIndex: row.rowIndex,
438
638
  rect: {
439
639
  leftPx: block.frame.leftPx,
440
- topPx: block.frame.topPx + rowIndex * rowHeightPx,
640
+ topPx: block.frame.topPx + row.visualIndex * rowHeightPx,
441
641
  widthPx: block.frame.widthPx,
442
642
  heightPx: rowHeightPx,
443
643
  space: "frame",
@@ -445,15 +645,27 @@ function appendBlockSemanticEntries(input: {
445
645
  },
446
646
  status: "realized",
447
647
  precision: "within-tolerance",
648
+ frameCompleteness,
649
+ ...tableRowContinuationMetadata(block, row.rowIndex),
448
650
  ...(tableIdentity
449
- ? { sourceIdentity: tableRowSourceIdentity(tableIdentity, rowIndex) }
651
+ ? {
652
+ sourceIdentity: tableRowSourceIdentity(
653
+ tableIdentity,
654
+ row.rowIndex,
655
+ ),
656
+ }
450
657
  : {}),
451
658
  });
452
659
  recordPrecision(precision, "within-tolerance");
453
660
  }
454
661
 
455
662
  for (const cell of plan.bandClasses.cells) {
456
- const rect = pageFrameCellRect(block, cell.rowIndex, cell.columnIndex);
663
+ const rect = pageFrameCellRect(
664
+ block,
665
+ cell.rowIndex,
666
+ cell.columnIndex,
667
+ visibleRows,
668
+ );
457
669
  if (!rect) continue;
458
670
  entries.push({
459
671
  ...base,
@@ -467,6 +679,8 @@ function appendBlockSemanticEntries(input: {
467
679
  },
468
680
  status: "realized",
469
681
  precision: "within-tolerance",
682
+ frameCompleteness,
683
+ ...tableRowContinuationMetadata(block, cell.rowIndex),
470
684
  ...(tableIdentity
471
685
  ? {
472
686
  sourceIdentity: tableCellSourceIdentity(
@@ -493,6 +707,7 @@ function appendBlockSemanticEntries(input: {
493
707
  },
494
708
  status: "realized",
495
709
  precision: "heuristic",
710
+ frameCompleteness,
496
711
  });
497
712
  recordPrecision(precision, "heuristic");
498
713
  return;
@@ -506,6 +721,7 @@ function appendBlockSemanticEntries(input: {
506
721
  rect: toGeometryRect(block.frame),
507
722
  status: "realized",
508
723
  precision: "exact",
724
+ frameCompleteness,
509
725
  });
510
726
  recordPrecision(precision, "exact");
511
727
  return;
@@ -519,18 +735,189 @@ function appendBlockSemanticEntries(input: {
519
735
  rect: toGeometryRect(block.frame),
520
736
  status: "realized",
521
737
  precision: "exact",
738
+ frameCompleteness,
522
739
  });
523
740
  recordPrecision(precision, "exact");
524
741
  }
525
742
  }
526
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
+
527
902
  function countBlockSemanticEntries(block: RenderBlock): GeometryPrecisionCounts {
528
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
+ }
529
910
  if (block.kind === "table") {
530
911
  counts.exact += 1;
531
912
  if (block.tablePlan) {
532
- counts["within-tolerance"] += resolveTableRowCount(block);
533
- 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;
534
921
  }
535
922
  } else if (block.kind === "image-float") {
536
923
  counts.heuristic += 1;
@@ -553,13 +940,134 @@ function resolveTableRowCount(block: RenderBlock): number {
553
940
  );
554
941
  }
555
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
+
556
1059
  function pageFrameCellRect(
557
1060
  block: RenderBlock,
558
1061
  rowIndex: number,
559
1062
  columnIndex: number,
1063
+ visibleRows: readonly VisibleTableRow[] = resolveVisibleTableRows(block),
560
1064
  ): RenderFrameRect | null {
561
1065
  const plan = block.tablePlan;
562
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;
563
1071
  const columnCount = plan.columnsTwips.length;
564
1072
  const totalWidthTwips = plan.columnsTwips.reduce(
565
1073
  (sum, value) => sum + Math.max(0, value),
@@ -586,12 +1094,12 @@ function pageFrameCellRect(
586
1094
  for (let i = columnIndex; i < columnIndex + columnSpan; i += 1) {
587
1095
  widthPx += (plan.columnsTwips[i] ?? 0) * pxPerTwip;
588
1096
  }
589
- const rowCount = resolveTableRowCount(block);
1097
+ const rowCount = visibleRows.length;
590
1098
  const rowHeightPx =
591
1099
  rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
592
1100
  return {
593
1101
  leftPx,
594
- topPx: block.frame.topPx + rowIndex * rowHeightPx,
1102
+ topPx: block.frame.topPx + visualRowIndex * rowHeightPx,
595
1103
  widthPx,
596
1104
  heightPx: rowHeightPx,
597
1105
  };
@@ -706,6 +1214,7 @@ interface MutableObjectHandleEntry {
706
1214
  rects: GeometryRect[];
707
1215
  status: GeometryRehydrationStatus;
708
1216
  precision: GeometryPrecision;
1217
+ layoutDivergenceIds?: string[];
709
1218
  sourceIdentity?: GeometrySourceIdentity;
710
1219
  }
711
1220
 
@@ -739,6 +1248,11 @@ function appendCanonicalObjectHandleEntries(input: {
739
1248
  if (existing) {
740
1249
  appendUnique(existing.pageIds, page.page.pageId);
741
1250
  existing.rects.push(...handleRects);
1251
+ appendDivergenceIdsForObject(
1252
+ existing,
1253
+ input.page,
1254
+ anchor.objectKey,
1255
+ );
742
1256
  if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
743
1257
  existing.precision = "heuristic";
744
1258
  existing.status = "requires-rehydration";
@@ -753,6 +1267,7 @@ function appendCanonicalObjectHandleEntries(input: {
753
1267
  rects: [...handleRects],
754
1268
  status,
755
1269
  precision: entryPrecision,
1270
+ ...layoutDivergenceIdsForObject(input.page, anchor.objectKey),
756
1271
  sourceIdentity: anchorSourceIdentity(
757
1272
  anchor,
758
1273
  exactObjectRect ? "direct" : "block-scoped",
@@ -762,6 +1277,80 @@ function appendCanonicalObjectHandleEntries(input: {
762
1277
  }
763
1278
  }
764
1279
 
1280
+ function appendPageLocalObjectHandleEntries(input: {
1281
+ page: RenderPage;
1282
+ entries: Map<string, MutableObjectHandleEntry>;
1283
+ precision: GeometryPrecisionCounts;
1284
+ divergenceIdsByObjectId: ReadonlyMap<string, readonly string[]>;
1285
+ }): void {
1286
+ const { page, entries, precision, divergenceIdsByObjectId } = input;
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;
1292
+ for (const story of stories) {
1293
+ const regionFrame =
1294
+ story.kind === "header" ? page.regions.header?.frame : page.regions.footer?.frame;
1295
+ if (!regionFrame) continue;
1296
+ for (const object of story.anchoredObjects) {
1297
+ const objectFrame = pageLocalObjectFrame(
1298
+ regionFrame,
1299
+ object.extentTwips,
1300
+ pxPerTwip,
1301
+ );
1302
+ const handleRects = buildObjectHandleRectsFromRect(objectFrame, "heuristic");
1303
+ const sourceIdentity: GeometrySourceIdentity = {
1304
+ storyKey: story.storyKey,
1305
+ objectKey: object.objectId,
1306
+ objectKind: object.sourceType,
1307
+ editPosture: object.preserveOnly ? "preserve-only" : "editable",
1308
+ joinKind: "block-scoped",
1309
+ };
1310
+ const existing = entries.get(object.objectId);
1311
+ if (existing) {
1312
+ appendUnique(existing.pageIds, page.page.pageId);
1313
+ existing.rects.push(...handleRects);
1314
+ appendDivergenceIds(existing, divergenceIdsByObjectId.get(object.objectId));
1315
+ if (existing.precision !== "heuristic") {
1316
+ existing.precision = "heuristic";
1317
+ existing.status = "requires-rehydration";
1318
+ existing.sourceIdentity = sourceIdentity;
1319
+ }
1320
+ continue;
1321
+ }
1322
+ entries.set(object.objectId, {
1323
+ objectId: object.objectId,
1324
+ pageIds: [page.page.pageId],
1325
+ rects: [...handleRects],
1326
+ status: "requires-rehydration",
1327
+ precision: "heuristic",
1328
+ ...mutableLayoutDivergenceIds(divergenceIdsByObjectId.get(object.objectId)),
1329
+ sourceIdentity,
1330
+ });
1331
+ recordPrecision(precision, "heuristic");
1332
+ }
1333
+ }
1334
+ }
1335
+
1336
+ function pageLocalObjectFrame(
1337
+ regionFrame: RenderFrameRect,
1338
+ extentTwips:
1339
+ | { readonly widthTwips: number; readonly heightTwips: number }
1340
+ | undefined,
1341
+ pxPerTwip: number,
1342
+ ): RenderFrameRect {
1343
+ if (!extentTwips) return regionFrame;
1344
+ const widthPx = Math.max(0, extentTwips.widthTwips * pxPerTwip);
1345
+ const heightPx = Math.max(0, extentTwips.heightTwips * pxPerTwip);
1346
+ return {
1347
+ leftPx: regionFrame.leftPx,
1348
+ topPx: regionFrame.topPx,
1349
+ widthPx: widthPx > 0 ? Math.min(widthPx, regionFrame.widthPx) : regionFrame.widthPx,
1350
+ heightPx: heightPx > 0 ? Math.min(heightPx, regionFrame.heightPx) : regionFrame.heightPx,
1351
+ };
1352
+ }
1353
+
765
1354
  function finalizeObjectHandleEntries(
766
1355
  entries: ReadonlyMap<string, MutableObjectHandleEntry>,
767
1356
  ): GeometryObjectHandleEntry[] {
@@ -771,6 +1360,9 @@ function finalizeObjectHandleEntries(
771
1360
  rects: entry.rects,
772
1361
  status: entry.status,
773
1362
  precision: entry.precision,
1363
+ ...(entry.layoutDivergenceIds && entry.layoutDivergenceIds.length > 0
1364
+ ? { layoutDivergenceIds: [...entry.layoutDivergenceIds] }
1365
+ : {}),
774
1366
  ...(entry.sourceIdentity ? { sourceIdentity: entry.sourceIdentity } : {}),
775
1367
  }));
776
1368
  }
@@ -826,6 +1418,7 @@ function recordTableCellScopeBlocks(input: {
826
1418
  storyKey: string;
827
1419
  identities: GeometryIdentityLookup | null;
828
1420
  projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
1421
+ visibleRows: readonly VisibleTableRow[];
829
1422
  }): void {
830
1423
  const {
831
1424
  table,
@@ -834,12 +1427,18 @@ function recordTableCellScopeBlocks(input: {
834
1427
  storyKey,
835
1428
  identities,
836
1429
  projectedBlocksByStory,
1430
+ visibleRows,
837
1431
  } = input;
838
1432
  if (!identities) return;
839
1433
 
840
1434
  for (const row of table.rows) {
841
1435
  for (const cell of row.cells) {
842
- const rect = pageFrameCellRect(block, row.rowIndex, cell.gridColumnStart);
1436
+ const rect = pageFrameCellRect(
1437
+ block,
1438
+ row.rowIndex,
1439
+ cell.gridColumnStart,
1440
+ visibleRows,
1441
+ );
843
1442
  if (!rect) continue;
844
1443
  for (let blockIndex = 0; blockIndex < cell.blockCount; blockIndex += 1) {
845
1444
  const blockPath =
@@ -1228,6 +1827,10 @@ function toGeometryRect(rect: RenderFrameRect): GeometryRect {
1228
1827
  };
1229
1828
  }
1230
1829
 
1830
+ function clamp(value: number, min: number, max: number): number {
1831
+ return Math.min(Math.max(value, min), max);
1832
+ }
1833
+
1231
1834
  function makeRegionId(
1232
1835
  page: RenderPage,
1233
1836
  region: RenderStoryRegion,
@@ -1260,6 +1863,48 @@ function createPrecisionCounts(): GeometryPrecisionCounts {
1260
1863
  };
1261
1864
  }
1262
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
+
1263
1908
  function recordPrecision(
1264
1909
  counts: GeometryPrecisionCounts,
1265
1910
  precision: keyof GeometryPrecisionCounts,