@beyondwork/docx-react-component 1.0.106 → 1.0.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +2 -1
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-time.ts +5 -0
  11. package/src/api/v3/ai/_pe2-evidence.ts +38 -0
  12. package/src/api/v3/ai/attach.ts +7 -2
  13. package/src/api/v3/ai/replacement.ts +101 -18
  14. package/src/api/v3/ai/resolve.ts +2 -2
  15. package/src/api/v3/ai/review.ts +177 -3
  16. package/src/api/v3/index.ts +1 -0
  17. package/src/api/v3/runtime/collab.ts +462 -0
  18. package/src/api/v3/runtime/document.ts +503 -20
  19. package/src/api/v3/runtime/geometry.ts +97 -0
  20. package/src/api/v3/runtime/layout.ts +744 -0
  21. package/src/api/v3/runtime/perf-probe.ts +14 -0
  22. package/src/api/v3/runtime/viewport.ts +9 -8
  23. package/src/api/v3/ui/_types.ts +149 -55
  24. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  25. package/src/api/v3/ui/debug.ts +115 -2
  26. package/src/api/v3/ui/index.ts +13 -0
  27. package/src/api/v3/ui/overlays.ts +0 -8
  28. package/src/api/v3/ui/surface.ts +56 -0
  29. package/src/api/v3/ui/viewport.ts +22 -9
  30. package/src/core/commands/image-commands.ts +1 -0
  31. package/src/core/commands/index.ts +6 -0
  32. package/src/core/schema/text-schema.ts +43 -5
  33. package/src/core/selection/mapping.ts +8 -1
  34. package/src/core/selection/review-anchors.ts +5 -1
  35. package/src/core/state/text-transaction.ts +8 -2
  36. package/src/io/export/serialize-revisions.ts +149 -1
  37. package/src/io/normalize/normalize-text.ts +6 -0
  38. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  39. package/src/io/ooxml/parse-fields.ts +24 -2
  40. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  41. package/src/io/ooxml/parse-main-document.ts +153 -9
  42. package/src/io/ooxml/parse-numbering.ts +20 -0
  43. package/src/io/ooxml/parse-revisions.ts +19 -8
  44. package/src/io/opc/package-reader.ts +98 -8
  45. package/src/model/anchor.ts +4 -3
  46. package/src/model/canonical-document.ts +220 -2
  47. package/src/model/canonical-hash.ts +221 -0
  48. package/src/model/canonical-layout-inputs.ts +245 -6
  49. package/src/model/layout/index.ts +1 -0
  50. package/src/model/layout/page-graph-types.ts +118 -1
  51. package/src/model/review/revision-types.ts +14 -3
  52. package/src/preservation/store.ts +20 -4
  53. package/src/review/README.md +1 -1
  54. package/src/review/store/revision-actions.ts +14 -2
  55. package/src/runtime/collab/event-types.ts +67 -1
  56. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  57. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  58. package/src/runtime/document-heading-outline.ts +147 -0
  59. package/src/runtime/document-navigation.ts +8 -243
  60. package/src/runtime/document-runtime.ts +240 -97
  61. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  62. package/src/runtime/formatting/layout-inputs.ts +38 -5
  63. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  64. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  65. package/src/runtime/geometry/caret-geometry.ts +5 -6
  66. package/src/runtime/geometry/geometry-facet.ts +60 -10
  67. package/src/runtime/geometry/geometry-index.ts +591 -20
  68. package/src/runtime/geometry/geometry-types.ts +59 -0
  69. package/src/runtime/geometry/hit-test.ts +11 -1
  70. package/src/runtime/geometry/overlay-rects.ts +5 -3
  71. package/src/runtime/geometry/project-anchors.ts +1 -1
  72. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  73. package/src/runtime/layout/index.ts +6 -0
  74. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  75. package/src/runtime/layout/layout-engine-version.ts +181 -16
  76. package/src/runtime/layout/layout-facet-types.ts +6 -0
  77. package/src/runtime/layout/page-graph.ts +21 -4
  78. package/src/runtime/layout/paginated-layout-engine.ts +139 -15
  79. package/src/runtime/layout/project-block-fragments.ts +265 -7
  80. package/src/runtime/layout/public-facet.ts +78 -24
  81. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  82. package/src/runtime/layout/table-row-split.ts +92 -35
  83. package/src/runtime/prerender/cache-envelope.ts +2 -2
  84. package/src/runtime/prerender/cache-key.ts +5 -4
  85. package/src/runtime/prerender/customxml-cache.ts +0 -1
  86. package/src/runtime/render/render-kernel.ts +1 -1
  87. package/src/runtime/revision-runtime.ts +112 -10
  88. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  89. package/src/runtime/scopes/action-validation.ts +22 -2
  90. package/src/runtime/scopes/capabilities.ts +316 -0
  91. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  92. package/src/runtime/scopes/compiler-service.ts +108 -4
  93. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  94. package/src/runtime/scopes/create-issue.ts +5 -5
  95. package/src/runtime/scopes/evidence.ts +91 -0
  96. package/src/runtime/scopes/formatting/apply.ts +2 -0
  97. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  98. package/src/runtime/scopes/index.ts +54 -0
  99. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  100. package/src/runtime/scopes/layout-evidence.ts +374 -0
  101. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  102. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  103. package/src/runtime/scopes/replacement/apply.ts +97 -34
  104. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  105. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  106. package/src/runtime/scopes/visualization.ts +28 -0
  107. package/src/runtime/surface-projection.ts +44 -5
  108. package/src/runtime/telemetry/perf-probe.ts +216 -0
  109. package/src/runtime/virtualized-rendering.ts +36 -1
  110. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  111. package/src/runtime/workflow/coordinator.ts +39 -11
  112. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  113. package/src/runtime/workflow/index.ts +3 -0
  114. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  115. package/src/runtime/workflow/overlay-lanes.ts +168 -10
  116. package/src/runtime/workflow/overlay-store.ts +2 -2
  117. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  118. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  119. package/src/session/_sync-legacy.ts +17 -27
  120. package/src/session/import/loader.ts +6 -4
  121. package/src/session/import/source-package-evidence.ts +186 -2
  122. package/src/session/index.ts +5 -6
  123. package/src/session/session.ts +30 -56
  124. package/src/session/types.ts +8 -13
  125. package/src/shell/session-bootstrap.ts +155 -81
  126. package/src/ui/WordReviewEditor.tsx +520 -12
  127. package/src/ui/editor-shell-view.tsx +14 -4
  128. package/src/ui/editor-surface-controller.tsx +5 -3
  129. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  130. package/src/ui/presence-overlay-lane.ts +0 -1
  131. package/src/ui/ui-controller-factory.ts +7 -0
  132. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  133. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  134. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  135. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  136. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  137. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  138. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  139. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  140. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  141. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  142. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  143. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  144. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  145. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  146. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  147. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  148. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  149. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  150. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  151. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  152. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  153. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  154. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  155. package/src/ui-tailwind/debug/README.md +4 -1
  156. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  157. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  158. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  159. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  160. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  161. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  162. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  163. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  164. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  165. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  166. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  167. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  168. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  169. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  170. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  171. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  172. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  173. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  174. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  175. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  176. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  177. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  178. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  179. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  180. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  181. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  182. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  183. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  184. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  185. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  186. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  187. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  188. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  189. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  190. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -26,6 +26,7 @@
26
26
  import type { EditorStoryTarget } from "../../api/public-types";
27
27
  import type {
28
28
  PublicMeasurementFidelity,
29
+ PublicPageFrame,
29
30
  PublicPageRegion,
30
31
  } from "../layout/public-facet.ts";
31
32
 
@@ -119,17 +120,28 @@ export interface GeometryPrecisionCounts {
119
120
  export interface GeometryIndexCoverage {
120
121
  status: GeometryRehydrationStatus;
121
122
  pageCount: number;
123
+ pageFrameCompleteness: GeometryPageFrameCompletenessCounts;
122
124
  regionCount: number;
123
125
  sliceCount: number;
124
126
  lineCount: number;
125
127
  anchorCount: number;
126
128
  hitTargetCount: number;
127
129
  semanticEntryCount: number;
130
+ pageLocalFieldLedgerCount: number;
128
131
  replacementEnvelopeCount: number;
129
132
  objectHandleCount: number;
133
+ layoutDivergenceObjectCount: number;
134
+ splitRowCarryCount: number;
130
135
  precision: GeometryPrecisionCounts;
131
136
  }
132
137
 
138
+ export type GeometryPageFrameCompleteness = PublicPageFrame["completeness"];
139
+
140
+ export type GeometryPageFrameCompletenessCounts = Record<
141
+ GeometryPageFrameCompleteness,
142
+ number
143
+ >;
144
+
133
145
  export type GeometrySourceIdentityJoinKind = "direct" | "block-scoped";
134
146
 
135
147
  /**
@@ -183,6 +195,10 @@ export interface GeometryIndexPage {
183
195
  pageIndex: number;
184
196
  rect: GeometryRect;
185
197
  regionIds: readonly string[];
198
+ frameCompleteness: GeometryPageFrameCompleteness;
199
+ displayPageNumber?: number;
200
+ layoutDivergenceIds?: readonly string[];
201
+ layoutDivergenceObjectIds?: readonly string[];
186
202
  }
187
203
 
188
204
  export interface GeometryIndexRegion {
@@ -255,6 +271,8 @@ export interface GeometryHitTarget {
255
271
 
256
272
  export type SemanticDisplayEntryKind =
257
273
  | "text-line"
274
+ | "field-region"
275
+ | "page-local-field-ledger"
258
276
  | "numbering-marker"
259
277
  | "table-frame"
260
278
  | "table-row"
@@ -269,6 +287,34 @@ export type SemanticDisplayEntryKind =
269
287
  | "border"
270
288
  | "debug-classification";
271
289
 
290
+ export interface GeometryTableRowRange {
291
+ from: number;
292
+ to: number;
293
+ totalRows: number;
294
+ }
295
+
296
+ export interface GeometryTableSplitRowCarry {
297
+ rowIndex: number;
298
+ continuesFromPreviousPage: boolean;
299
+ continuesToNextPage: boolean;
300
+ }
301
+
302
+ export interface GeometryTableVerticalMergeCarry {
303
+ columnIndex: number;
304
+ restartRowIndex: number;
305
+ }
306
+
307
+ export interface GeometryTableContinuation {
308
+ sequenceIndex?: number;
309
+ sliceCount?: number;
310
+ rowRange?: GeometryTableRowRange;
311
+ continuesFromPreviousPage?: boolean;
312
+ continuesToNextPage?: boolean;
313
+ repeatedHeaderRowIndexes?: readonly number[];
314
+ splitRowCarry?: readonly GeometryTableSplitRowCarry[];
315
+ verticalMergeCarry?: readonly GeometryTableVerticalMergeCarry[];
316
+ }
317
+
272
318
  export interface SemanticDisplayEntry {
273
319
  entryId: string;
274
320
  kind: SemanticDisplayEntryKind;
@@ -282,6 +328,18 @@ export interface SemanticDisplayEntry {
282
328
  fragmentId?: string;
283
329
  rowIndex?: number;
284
330
  columnIndex?: number;
331
+ tableContinuation?: GeometryTableContinuation;
332
+ layoutObjectId?: string;
333
+ fieldFamilies?: readonly string[];
334
+ pageLocalStoryId?: string;
335
+ pageLocalStoryKind?: "header" | "footer";
336
+ pageLocalStoryVariant?: string;
337
+ resolvedFieldId?: string;
338
+ resolvedFieldFamily?: string;
339
+ resolvedFieldDisplayText?: string;
340
+ frameCompleteness?: GeometryPageFrameCompleteness;
341
+ layoutDivergenceIds?: readonly string[];
342
+ layoutDivergenceObjectIds?: readonly string[];
285
343
  rect: GeometryRect;
286
344
  status: GeometryRehydrationStatus;
287
345
  precision: GeometryPrecision;
@@ -303,6 +361,7 @@ export interface GeometryObjectHandleEntry {
303
361
  rects: readonly GeometryRect[];
304
362
  status: GeometryRehydrationStatus;
305
363
  precision: GeometryPrecision;
364
+ layoutDivergenceIds?: readonly string[];
306
365
  sourceIdentity?: GeometrySourceIdentity;
307
366
  }
308
367
 
@@ -32,7 +32,17 @@ export function resolveHitTest(
32
32
  if (!frame) return null;
33
33
  for (const page of frame.pages) {
34
34
  if (!containsPoint(page.frame, point)) continue;
35
- // Check body first it owns the bulk of the page. Header /
35
+ // Column regions are explicit L04 projection facts nested inside
36
+ // the body band. Check them before the broad body region so the
37
+ // body's between-block snap cannot steal clicks from a populated
38
+ // column.
39
+ if (page.regions.columns) {
40
+ for (const columnRegion of page.regions.columns) {
41
+ const hit = hitTestStoryRegion(page, point, columnRegion);
42
+ if (hit) return hit;
43
+ }
44
+ }
45
+ // Check body next — it owns the bulk of the page. Header /
36
46
  // footer / footnotes are secondary stories and only matter when
37
47
  // the point falls outside the body.
38
48
  const bodyHit = hitTestStoryRegion(page, point, page.regions.body);
@@ -48,8 +48,10 @@ function normalizeVisibleRange(
48
48
 
49
49
  /**
50
50
  * Build the `PageOverlayRect` list from a `GeometryFacet`. Returns
51
- * `null` when the facet does not resolve page 0 (cold-open) so the
52
- * caller can fall back to a DOM-measurement path.
51
+ * `null` when the facet cannot resolve every requested visible page
52
+ * (cold-open or partial frame) so the caller can fall back to an
53
+ * explicitly degraded DOM-measurement path instead of painting a
54
+ * partial page stack.
53
55
  */
54
56
  export function resolvePageOverlayRectsFromGeometry(
55
57
  geometryFacet: GeometryFacet,
@@ -69,7 +71,7 @@ export function resolvePageOverlayRectsFromGeometry(
69
71
  if (i < normalizedVisible.start || i >= normalizedVisible.end) continue;
70
72
  }
71
73
  const page = geometryFacet.getPage(i);
72
- if (!page) continue;
74
+ if (!page) return null;
73
75
  rects.push({
74
76
  pageId: page.pageId,
75
77
  pageIndex: page.pageIndex,
@@ -21,7 +21,7 @@ import type {
21
21
  RenderFrame,
22
22
  RenderFrameRect,
23
23
  } from "../render/index.ts";
24
- import { emitLayoutGuardWarning } from "../layout/public-facet.ts";
24
+ import { emitLayoutGuardWarning } from "../diagnostics/layout-guard-warning.ts";
25
25
 
26
26
  export function resolveAnchorRects(
27
27
  frame: RenderFrame | null,
@@ -0,0 +1,323 @@
1
+ export type WordLayoutV2LineIntakeLane = "ccep" | "ccep-redline";
2
+
3
+ export interface WordLayoutV2LineIntakeDocumentInput {
4
+ readonly lane: WordLayoutV2LineIntakeLane;
5
+ readonly runId: string;
6
+ readonly docId: string;
7
+ readonly recordPath?: string;
8
+ readonly artifact: unknown;
9
+ }
10
+
11
+ export interface WordLayoutV2LineGeometryRect {
12
+ readonly x: number;
13
+ readonly y: number;
14
+ readonly width: number;
15
+ readonly height: number;
16
+ readonly unit: "screenPx" | string;
17
+ readonly approxTwips?: {
18
+ readonly x?: number;
19
+ readonly y?: number;
20
+ readonly width?: number;
21
+ readonly height?: number;
22
+ readonly unit?: "twipsApproxFromScreen" | string;
23
+ };
24
+ }
25
+
26
+ export interface WordLayoutV2LineGeometryRow {
27
+ readonly lineId?: string;
28
+ readonly textHash?: string;
29
+ readonly charLength?: number;
30
+ readonly rectCount: number;
31
+ readonly rects: readonly WordLayoutV2LineGeometryRect[];
32
+ }
33
+
34
+ export interface WordLayoutV2LineGeometryDocumentSummary {
35
+ readonly lane: WordLayoutV2LineIntakeLane;
36
+ readonly runId: string;
37
+ readonly docId: string;
38
+ readonly recordPath?: string;
39
+ readonly source?: {
40
+ readonly fileName?: string;
41
+ readonly sha256?: string;
42
+ readonly bytes?: number;
43
+ };
44
+ readonly tuple: {
45
+ readonly zoom?: number;
46
+ readonly viewportWidth?: number;
47
+ readonly markupMode?: string;
48
+ readonly dpi?: number;
49
+ };
50
+ readonly word?: {
51
+ readonly version?: string;
52
+ readonly build?: string;
53
+ readonly activePrinter?: string;
54
+ };
55
+ readonly comSummary: {
56
+ readonly pageCount?: number | null;
57
+ readonly paragraphCount?: number;
58
+ readonly revisionCount?: number;
59
+ readonly commentCount?: number;
60
+ };
61
+ readonly lineBoxSummary: {
62
+ readonly status?: string;
63
+ readonly lineCount: number;
64
+ readonly lineRows: number;
65
+ readonly linesWithTextHash: number;
66
+ readonly linesWithRects: number;
67
+ readonly rectCount: number;
68
+ readonly approxTwipsRectCount: number;
69
+ readonly documentRangeRectCount?: number;
70
+ readonly firstLineId?: string;
71
+ readonly lastLineId?: string;
72
+ readonly coordinateSpace: "screen-px";
73
+ readonly precision: "screen-space-approximate";
74
+ readonly normalizationStatus: "awaiting-l04-page-frame-join";
75
+ };
76
+ readonly lineRows: readonly WordLayoutV2LineGeometryRow[];
77
+ }
78
+
79
+ export interface WordLayoutV2LineGeometryIntakeSummary {
80
+ readonly schemaVersion: "layer-05-word-layout-v2-line-geometry-intake/v0";
81
+ readonly generatedAtUtc: string;
82
+ readonly sourceRuns: readonly {
83
+ readonly runId: string;
84
+ readonly lane: WordLayoutV2LineIntakeLane;
85
+ readonly documents: number;
86
+ readonly lineRows: number;
87
+ readonly linesWithRects: number;
88
+ readonly rectCount: number;
89
+ readonly markupModes: readonly string[];
90
+ }[];
91
+ readonly totals: {
92
+ readonly documents: number;
93
+ readonly lineRows: number;
94
+ readonly linesWithRects: number;
95
+ readonly rectCount: number;
96
+ readonly approxTwipsRectCount: number;
97
+ readonly cleanDocuments: number;
98
+ readonly redlineDocuments: number;
99
+ readonly cleanLineRows: number;
100
+ readonly redlineLineRows: number;
101
+ };
102
+ readonly routing: {
103
+ readonly routedTo: "L05";
104
+ readonly axis: "line-geometry";
105
+ readonly sourceId: "word-layout-v2.uia.lineBoxes.lines";
106
+ readonly coordinateSpace: "screen-px";
107
+ readonly precision: "screen-space-approximate";
108
+ readonly requiredUpstreamFact: "L04 page/frame mapping";
109
+ };
110
+ readonly documents: readonly WordLayoutV2LineGeometryDocumentSummary[];
111
+ }
112
+
113
+ export function summarizeWordLayoutV2LineGeometryIntake(input: {
114
+ readonly generatedAtUtc: string;
115
+ readonly documents: readonly WordLayoutV2LineIntakeDocumentInput[];
116
+ }): WordLayoutV2LineGeometryIntakeSummary {
117
+ const documents = input.documents
118
+ .map(summarizeDocument)
119
+ .sort((a, b) =>
120
+ a.lane.localeCompare(b.lane) ||
121
+ a.docId.localeCompare(b.docId) ||
122
+ a.recordPath?.localeCompare(b.recordPath ?? "") ||
123
+ 0,
124
+ );
125
+ const sourceRuns = summarizeRuns(documents);
126
+ return {
127
+ schemaVersion: "layer-05-word-layout-v2-line-geometry-intake/v0",
128
+ generatedAtUtc: input.generatedAtUtc,
129
+ sourceRuns,
130
+ totals: {
131
+ documents: documents.length,
132
+ lineRows: sum(documents, (doc) => doc.lineBoxSummary.lineRows),
133
+ linesWithRects: sum(documents, (doc) => doc.lineBoxSummary.linesWithRects),
134
+ rectCount: sum(documents, (doc) => doc.lineBoxSummary.rectCount),
135
+ approxTwipsRectCount: sum(documents, (doc) => doc.lineBoxSummary.approxTwipsRectCount),
136
+ cleanDocuments: documents.filter((doc) => doc.lane === "ccep").length,
137
+ redlineDocuments: documents.filter((doc) => doc.lane === "ccep-redline").length,
138
+ cleanLineRows: sum(
139
+ documents.filter((doc) => doc.lane === "ccep"),
140
+ (doc) => doc.lineBoxSummary.lineRows,
141
+ ),
142
+ redlineLineRows: sum(
143
+ documents.filter((doc) => doc.lane === "ccep-redline"),
144
+ (doc) => doc.lineBoxSummary.lineRows,
145
+ ),
146
+ },
147
+ routing: {
148
+ routedTo: "L05",
149
+ axis: "line-geometry",
150
+ sourceId: "word-layout-v2.uia.lineBoxes.lines",
151
+ coordinateSpace: "screen-px",
152
+ precision: "screen-space-approximate",
153
+ requiredUpstreamFact: "L04 page/frame mapping",
154
+ },
155
+ documents,
156
+ };
157
+ }
158
+
159
+ function summarizeDocument(
160
+ input: WordLayoutV2LineIntakeDocumentInput,
161
+ ): WordLayoutV2LineGeometryDocumentSummary {
162
+ const artifact = record(input.artifact);
163
+ const source = record(artifact.source);
164
+ const tuple = record(artifact.tuple);
165
+ const word = record(artifact.word);
166
+ const comProbe = record(artifact.comProbe);
167
+ const uia = record(artifact.uia);
168
+ const lineBoxes = record(uia.lineBoxes);
169
+ const documentRange = record(lineBoxes.documentRange);
170
+ const lines = array(lineBoxes.lines);
171
+ const lineRows = lines.map(normalizeLineRow);
172
+ const lineCount = numberValue(lineBoxes.lineCount) ?? lineRows.length;
173
+ const rectCount = sum(lineRows, (line) => line.rects.length);
174
+ return {
175
+ lane: input.lane,
176
+ runId: input.runId,
177
+ docId: input.docId,
178
+ recordPath: input.recordPath,
179
+ source: {
180
+ fileName: stringValue(source.fileName),
181
+ sha256: stringValue(source.sha256),
182
+ bytes: numberValue(source.bytes),
183
+ },
184
+ tuple: {
185
+ zoom: numberValue(tuple.zoom),
186
+ viewportWidth: numberValue(tuple.viewportWidth),
187
+ markupMode: stringValue(tuple.markupMode),
188
+ dpi: numberValue(tuple.dpi),
189
+ },
190
+ word: {
191
+ version: stringValue(word.version),
192
+ build: stringValue(word.build),
193
+ activePrinter: stringValue(word.activePrinter),
194
+ },
195
+ comSummary: {
196
+ pageCount: nullableNumberValue(comProbe.pageCount),
197
+ paragraphCount: numberValue(comProbe.paragraphs),
198
+ revisionCount: numberValue(comProbe.revisions),
199
+ commentCount: numberValue(comProbe.comments),
200
+ },
201
+ lineBoxSummary: {
202
+ status: stringValue(lineBoxes.status),
203
+ lineCount,
204
+ lineRows: lineRows.length,
205
+ linesWithTextHash: lineRows.filter((line) => line.textHash !== undefined).length,
206
+ linesWithRects: lineRows.filter((line) => line.rects.length > 0).length,
207
+ rectCount,
208
+ approxTwipsRectCount: sum(
209
+ lineRows,
210
+ (line) => line.rects.filter((rect) => rect.approxTwips !== undefined).length,
211
+ ),
212
+ documentRangeRectCount: Array.isArray(documentRange.rects)
213
+ ? documentRange.rects.length
214
+ : undefined,
215
+ firstLineId: lineRows[0]?.lineId,
216
+ lastLineId: lineRows[lineRows.length - 1]?.lineId,
217
+ coordinateSpace: "screen-px",
218
+ precision: "screen-space-approximate",
219
+ normalizationStatus: "awaiting-l04-page-frame-join",
220
+ },
221
+ lineRows,
222
+ };
223
+ }
224
+
225
+ function summarizeRuns(
226
+ documents: readonly WordLayoutV2LineGeometryDocumentSummary[],
227
+ ): WordLayoutV2LineGeometryIntakeSummary["sourceRuns"] {
228
+ const keys = [...new Set(documents.map((doc) => `${doc.lane}\0${doc.runId}`))].sort();
229
+ return keys.map((key) => {
230
+ const [lane, runId] = key.split("\0") as [WordLayoutV2LineIntakeLane, string];
231
+ const docs = documents.filter((doc) => doc.lane === lane && doc.runId === runId);
232
+ const markupModes = [
233
+ ...new Set(docs.map((doc) => doc.tuple.markupMode).filter((mode): mode is string => !!mode)),
234
+ ].sort();
235
+ return {
236
+ runId,
237
+ lane,
238
+ documents: docs.length,
239
+ lineRows: sum(docs, (doc) => doc.lineBoxSummary.lineRows),
240
+ linesWithRects: sum(docs, (doc) => doc.lineBoxSummary.linesWithRects),
241
+ rectCount: sum(docs, (doc) => doc.lineBoxSummary.rectCount),
242
+ markupModes,
243
+ };
244
+ });
245
+ }
246
+
247
+ function normalizeLineRow(raw: unknown): WordLayoutV2LineGeometryRow {
248
+ const line = record(raw);
249
+ const rects = normalizeRects(line.rects);
250
+ return {
251
+ lineId: stringValue(line.lineId),
252
+ textHash: stringValue(line.textHash),
253
+ charLength: numberValue(line.charLength),
254
+ rectCount: numberValue(line.rectCount) ?? rects.length,
255
+ rects,
256
+ };
257
+ }
258
+
259
+ function normalizeRects(raw: unknown): readonly WordLayoutV2LineGeometryRect[] {
260
+ if (Array.isArray(raw)) return raw.map(normalizeRect).filter(isRect);
261
+ const maybeRect = normalizeRect(raw);
262
+ return isRect(maybeRect) ? [maybeRect] : [];
263
+ }
264
+
265
+ function normalizeRect(raw: unknown): WordLayoutV2LineGeometryRect | undefined {
266
+ const rect = record(raw);
267
+ const x = numberValue(rect.x);
268
+ const y = numberValue(rect.y);
269
+ const width = numberValue(rect.width);
270
+ const height = numberValue(rect.height);
271
+ if (x === undefined || y === undefined || width === undefined || height === undefined) {
272
+ return undefined;
273
+ }
274
+ const approxTwips = record(rect.approxTwips);
275
+ const normalizedApproxTwips = {
276
+ x: numberValue(approxTwips.x),
277
+ y: numberValue(approxTwips.y),
278
+ width: numberValue(approxTwips.width),
279
+ height: numberValue(approxTwips.height),
280
+ unit: stringValue(approxTwips.unit),
281
+ };
282
+ const hasApproxTwips = Object.values(normalizedApproxTwips).some(
283
+ (value) => value !== undefined,
284
+ );
285
+ return {
286
+ x,
287
+ y,
288
+ width,
289
+ height,
290
+ unit: stringValue(rect.unit) ?? "screenPx",
291
+ approxTwips: hasApproxTwips ? normalizedApproxTwips : undefined,
292
+ };
293
+ }
294
+
295
+ function isRect(value: WordLayoutV2LineGeometryRect | undefined): value is WordLayoutV2LineGeometryRect {
296
+ return value !== undefined;
297
+ }
298
+
299
+ function record(value: unknown): Record<string, unknown> {
300
+ return value !== null && typeof value === "object" && !Array.isArray(value)
301
+ ? value as Record<string, unknown>
302
+ : {};
303
+ }
304
+
305
+ function array(value: unknown): readonly unknown[] {
306
+ return Array.isArray(value) ? value : [];
307
+ }
308
+
309
+ function stringValue(value: unknown): string | undefined {
310
+ return typeof value === "string" && value.length > 0 ? value : undefined;
311
+ }
312
+
313
+ function numberValue(value: unknown): number | undefined {
314
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
315
+ }
316
+
317
+ function nullableNumberValue(value: unknown): number | null | undefined {
318
+ return value === null ? null : numberValue(value);
319
+ }
320
+
321
+ function sum<T>(items: readonly T[], read: (item: T) => number): number {
322
+ return items.reduce((total, item) => total + read(item), 0);
323
+ }
@@ -183,14 +183,20 @@ export {
183
183
  type PublicLineBox,
184
184
  type PublicNoteAllocation,
185
185
  type PublicPageAnchor,
186
+ type PublicPageFrame,
187
+ type PublicPageLocalStoryInstance,
186
188
  type PublicPageSpan,
187
189
  type PublicSectionNode,
188
190
  type PublicResolvedPageStories,
191
+ type PublicResolvedStoryField,
189
192
  type PublicResolvedParagraphFormatting,
190
193
  type PublicResolvedRunFormatting,
194
+ type PublicStoryAnchoredObject,
195
+ type PublicTwipsRect,
191
196
  type PublicBlockMeasurement,
192
197
  type PublicMeasurementFidelity,
193
198
  type PublicFieldDirtinessReport,
199
+ type PublicLayoutDivergence,
194
200
  type LayoutFacetEvent,
195
201
  type LayoutFacetInvalidationReason,
196
202
  type RenderZoomSummary,
@@ -27,6 +27,7 @@ import type {
27
27
  import type {
28
28
  CanonicalDocumentEnvelope,
29
29
  } from "../../core/state/editor-state.ts";
30
+ import { collectCanonicalFieldRegionIdentities } from "../../model/canonical-layout-inputs.ts";
30
31
  import { MAIN_STORY_TARGET } from "../../core/selection/mapping.ts";
31
32
  import { createSelectionSnapshot } from "../../core/state/editor-state.ts";
32
33
  import {
@@ -36,7 +37,7 @@ import {
36
37
  } from "../document-layout.ts";
37
38
  import { findNoteReferencePosition } from "../view-state.ts";
38
39
  import { createEditorSurfaceSnapshot } from "../surface-projection.ts";
39
- import { buildHeadingOutline } from "../document-navigation.ts";
40
+ import { buildHeadingOutline } from "../document-heading-outline.ts";
40
41
  import {
41
42
  analyzeInvalidation,
42
43
  computeFieldDirtiness,
@@ -456,6 +457,7 @@ export function createLayoutEngine(
456
457
  MAIN_STORY_TARGET,
457
458
  );
458
459
  const sections = buildResolvedSections(document);
460
+ const fieldRegions = collectCanonicalFieldRegionIdentities(document);
459
461
  const pageStack = buildPageStackWithSplits(
460
462
  document,
461
463
  sections,
@@ -470,6 +472,7 @@ export function createLayoutEngine(
470
472
  pageStack.splits,
471
473
  pageStack.columnByBlockIdByPageIndex,
472
474
  pageStack.fragmentMeasurementsByPageIndex,
475
+ fieldRegions,
473
476
  );
474
477
  const lineBoxesByPageIndex = projectLineBoxesForPageFragments(
475
478
  pages,
@@ -642,6 +645,7 @@ export function createLayoutEngine(
642
645
  : undefined;
643
646
 
644
647
  const freshStories = resolvePageStories(freshSnapshotsToRebuild);
648
+ const fieldRegions = collectCanonicalFieldRegionIdentities(document);
645
649
  // Project fragments for the fresh tail pages, threading paragraph
646
650
  // line-range splits produced by intra-paragraph pagination.
647
651
  const freshBodyFragmentsByPageIndex = projectSurfaceBlocksToPageFragments(
@@ -650,6 +654,7 @@ export function createLayoutEngine(
650
654
  freshResult.splits,
651
655
  freshResult.columnByBlockIdByPageIndex,
652
656
  freshResult.fragmentMeasurementsByPageIndex,
657
+ fieldRegions,
653
658
  );
654
659
  const freshLineBoxesByPageIndex = projectLineBoxesForPageFragments(
655
660
  freshSnapshotsToRebuild,