@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
@@ -34,6 +34,7 @@ import type {
34
34
  RuntimePageNode,
35
35
  RuntimePageRegion,
36
36
  RuntimePageRegions,
37
+ RuntimeStoryAnchoredObject,
37
38
  RuntimeTwipsRect,
38
39
  } from "./page-graph.ts";
39
40
  import type {
@@ -90,6 +91,7 @@ import { resolvePageFieldDisplayText } from "./resolve-page-fields.ts";
90
91
  // `runtime.workflow.*`; the layout facet supplies the page graph
91
92
  // as input, not the rail output.
92
93
  import { collectLineBoxesForRegion } from "../geometry/project-fragments.ts";
94
+ import { emitLayoutGuardWarning } from "../diagnostics/layout-guard-warning.ts";
93
95
  // `recordPerfSample` no longer imported — used only by the removed
94
96
  // `hitTest` wrapper to emit `chrome.hit_test` samples. Geometry facet
95
97
  // can add its own instrumentation at call site if needed.
@@ -111,26 +113,11 @@ export type { ScopeRailSegment };
111
113
  // Module-level warning emitter (D1 silent-success probes)
112
114
  // ---------------------------------------------------------------------------
113
115
 
114
- type LayoutWarningEmitter = (type: string, guard: string, inputs: Record<string, unknown>) => void;
115
- let _activeLayoutWarningEmitter: LayoutWarningEmitter | undefined;
116
-
117
- export function setActiveLayoutWarningEmitter(fn: LayoutWarningEmitter | undefined): void {
118
- _activeLayoutWarningEmitter = fn;
119
- }
120
-
121
- /**
122
- * Exported so debug-infra D1 probes at layout-semantic guards that have
123
- * physically moved into `src/runtime/geometry/**` (Slice 2a+ of refactor/05)
124
- * can continue to emit through the same `setActiveLayoutWarningEmitter`
125
- * pipeline as the remaining in-facet probes. The emit surface is unchanged;
126
- * only the call-site location moved.
127
- */
128
- export function emitLayoutGuardWarning(
129
- guard: string,
130
- inputs: Record<string, unknown>,
131
- ): void {
132
- _activeLayoutWarningEmitter?.(`layout.guard.return-empty`, guard, inputs);
133
- }
116
+ export {
117
+ emitLayoutGuardWarning,
118
+ setActiveLayoutWarningEmitter,
119
+ type LayoutWarningEmitter,
120
+ } from "../diagnostics/layout-guard-warning.ts";
134
121
 
135
122
  // ---------------------------------------------------------------------------
136
123
  // Public read model types (shape-stable, cloned at the facet boundary)
@@ -151,6 +138,7 @@ export interface PublicLayoutDivergence {
151
138
  message: string;
152
139
  regionKinds?: readonly RuntimePageRegion["kind"][];
153
140
  fragmentIds?: readonly string[];
141
+ objectIds?: readonly string[];
154
142
  }
155
143
 
156
144
  export interface PublicPageFrame {
@@ -159,6 +147,7 @@ export interface PublicPageFrame {
159
147
  pageIndex: number;
160
148
  sectionIndex: number;
161
149
  displayPageNumber: number;
150
+ completeness: RuntimePageFrame["completeness"];
162
151
  physicalBoundsTwips: PublicTwipsRect;
163
152
  divergenceIds: readonly string[];
164
153
  signature: string;
@@ -168,6 +157,42 @@ export interface PublicPageFrame {
168
157
  rectTwips?: PublicTwipsRect;
169
158
  fragmentIds: readonly string[];
170
159
  }[];
160
+ pageLocalStories: readonly PublicPageLocalStoryInstance[];
161
+ }
162
+
163
+ export interface PublicPageLocalStoryInstance {
164
+ instanceId: string;
165
+ storyKey: string;
166
+ pageId: string;
167
+ kind: "header" | "footer";
168
+ variant: "default" | "first" | "even" | "odd";
169
+ relationshipId: string;
170
+ sectionIndex?: number;
171
+ resolvedFields: readonly PublicResolvedStoryField[];
172
+ anchoredObjects: readonly PublicStoryAnchoredObject[];
173
+ measuredFrameHeightTwips: number;
174
+ signature: string;
175
+ }
176
+
177
+ export interface PublicResolvedStoryField {
178
+ fieldId: string;
179
+ family: string;
180
+ displayText: string;
181
+ }
182
+
183
+ export interface PublicStoryAnchoredObject {
184
+ objectId: string;
185
+ sourceType: RuntimeStoryAnchoredObject["sourceType"];
186
+ display: RuntimeStoryAnchoredObject["display"];
187
+ wrapMode?: RuntimeStoryAnchoredObject["wrapMode"];
188
+ extentTwips?: {
189
+ widthTwips: number;
190
+ heightTwips: number;
191
+ };
192
+ relationshipIds?: readonly string[];
193
+ mediaIds?: readonly string[];
194
+ preserveOnly: boolean;
195
+ divergenceIds: readonly string[];
171
196
  }
172
197
 
173
198
  export interface PublicPageNode {
@@ -265,9 +290,11 @@ export interface PublicBlockFragment {
265
290
  };
266
291
  columnIndex?: number;
267
292
  continuation?: PublicLayoutContinuationCursor;
293
+ layoutObject?: PublicFragmentLayoutObject;
268
294
  }
269
295
 
270
296
  export type PublicLayoutContinuationCursor = RuntimeLayoutContinuationCursor;
297
+ export type PublicFragmentLayoutObject = NonNullable<RuntimeBlockFragment["layoutObject"]>;
271
298
 
272
299
  /**
273
300
  * P8 — One block snapshot rendered into a region of a page. Returned by
@@ -1473,11 +1500,64 @@ function toPublicPageFrame(frame: RuntimePageFrame): PublicPageFrame {
1473
1500
  pageIndex: frame.pageIndex,
1474
1501
  sectionIndex: frame.sectionIndex,
1475
1502
  displayPageNumber: frame.displayPageNumber,
1503
+ completeness: frame.completeness,
1476
1504
  physicalBoundsTwips: toPublicTwipsRect(frame.physicalBoundsTwips),
1477
1505
  divergenceIds: [...frame.divergenceIds],
1478
1506
  signature: frame.signature,
1479
1507
  exclusionZoneCount: frame.regions.exclusionZones.length,
1480
1508
  regionFrames: frameRegionEntries(frame),
1509
+ pageLocalStories: frame.pageLocalStories.map(toPublicPageLocalStory),
1510
+ };
1511
+ }
1512
+
1513
+ function toPublicPageLocalStory(
1514
+ story: RuntimePageFrame["pageLocalStories"][number],
1515
+ ): PublicPageLocalStoryInstance {
1516
+ return {
1517
+ instanceId: story.instanceId,
1518
+ storyKey: story.storyKey,
1519
+ pageId: story.pageId,
1520
+ kind: story.kind,
1521
+ variant: story.variant,
1522
+ relationshipId: story.relationshipId,
1523
+ ...(story.sectionIndex !== undefined ? { sectionIndex: story.sectionIndex } : {}),
1524
+ resolvedFields: story.resolvedFields.map(toPublicResolvedStoryField),
1525
+ anchoredObjects: story.anchoredObjects.map(toPublicStoryAnchoredObject),
1526
+ measuredFrameHeightTwips: story.measuredFrameHeightTwips,
1527
+ signature: story.signature,
1528
+ };
1529
+ }
1530
+
1531
+ function toPublicResolvedStoryField(
1532
+ field: RuntimePageFrame["pageLocalStories"][number]["resolvedFields"][number],
1533
+ ): PublicResolvedStoryField {
1534
+ return {
1535
+ fieldId: field.fieldId,
1536
+ family: field.family,
1537
+ displayText: field.displayText,
1538
+ };
1539
+ }
1540
+
1541
+ function toPublicStoryAnchoredObject(
1542
+ object: RuntimeStoryAnchoredObject,
1543
+ ): PublicStoryAnchoredObject {
1544
+ return {
1545
+ objectId: object.objectId,
1546
+ sourceType: object.sourceType,
1547
+ display: object.display,
1548
+ ...(object.wrapMode !== undefined ? { wrapMode: object.wrapMode } : {}),
1549
+ ...(object.extentTwips
1550
+ ? {
1551
+ extentTwips: {
1552
+ widthTwips: object.extentTwips.widthTwips,
1553
+ heightTwips: object.extentTwips.heightTwips,
1554
+ },
1555
+ }
1556
+ : {}),
1557
+ ...(object.relationshipIds ? { relationshipIds: [...object.relationshipIds] } : {}),
1558
+ ...(object.mediaIds ? { mediaIds: [...object.mediaIds] } : {}),
1559
+ preserveOnly: object.preserveOnly,
1560
+ divergenceIds: [...object.divergenceIds],
1481
1561
  };
1482
1562
  }
1483
1563
 
@@ -1492,6 +1572,7 @@ function toPublicLayoutDivergence(
1492
1572
  message: divergence.message,
1493
1573
  ...(divergence.regionKinds !== undefined ? { regionKinds: [...divergence.regionKinds] } : {}),
1494
1574
  ...(divergence.fragmentIds !== undefined ? { fragmentIds: [...divergence.fragmentIds] } : {}),
1575
+ ...(divergence.objectIds !== undefined ? { objectIds: [...divergence.objectIds] } : {}),
1495
1576
  };
1496
1577
  }
1497
1578
 
@@ -1556,6 +1637,9 @@ function toPublicBlockFragment(
1556
1637
  ...(fragment.continuation !== undefined
1557
1638
  ? { continuation: cloneContinuationCursor(fragment.continuation) }
1558
1639
  : {}),
1640
+ ...(fragment.layoutObject !== undefined
1641
+ ? { layoutObject: cloneFragmentLayoutObject(fragment.layoutObject) }
1642
+ : {}),
1559
1643
  };
1560
1644
  }
1561
1645
 
@@ -1572,10 +1656,66 @@ function cloneContinuationCursor(
1572
1656
  ...cursor,
1573
1657
  rowRange: { ...cursor.rowRange },
1574
1658
  repeatedHeaderRowIndexes: [...cursor.repeatedHeaderRowIndexes],
1659
+ ...(cursor.splitRowCarry
1660
+ ? { splitRowCarry: cursor.splitRowCarry.map((carry) => ({ ...carry })) }
1661
+ : {}),
1575
1662
  verticalMergeCarry: cursor.verticalMergeCarry.map((carry) => ({ ...carry })),
1576
1663
  };
1577
1664
  }
1578
1665
 
1666
+ function cloneFragmentLayoutObject(
1667
+ layoutObject: NonNullable<RuntimeBlockFragment["layoutObject"]>,
1668
+ ): NonNullable<RuntimeBlockFragment["layoutObject"]> {
1669
+ return {
1670
+ ...layoutObject,
1671
+ measuredExtentTwips: { ...layoutObject.measuredExtentTwips },
1672
+ ...(layoutObject.fieldFamilies !== undefined
1673
+ ? { fieldFamilies: [...layoutObject.fieldFamilies] }
1674
+ : {}),
1675
+ ...(layoutObject.fieldRegions !== undefined
1676
+ ? {
1677
+ fieldRegions: layoutObject.fieldRegions.map((region) => ({
1678
+ ...region,
1679
+ ...(region.sourceRef !== undefined ? { sourceRef: { ...region.sourceRef } } : {}),
1680
+ ...(region.fieldEvidence !== undefined
1681
+ ? {
1682
+ fieldEvidence: {
1683
+ ...region.fieldEvidence,
1684
+ ...(region.fieldEvidence.sourceRef !== undefined
1685
+ ? { sourceRef: { ...region.fieldEvidence.sourceRef } }
1686
+ : {}),
1687
+ },
1688
+ }
1689
+ : {}),
1690
+ })),
1691
+ }
1692
+ : {}),
1693
+ ...(layoutObject.numbering !== undefined
1694
+ ? { numbering: cloneNumberingLayoutFacts(layoutObject.numbering) }
1695
+ : {}),
1696
+ ...(layoutObject.numberingRows !== undefined
1697
+ ? { numberingRows: layoutObject.numberingRows.map(cloneNumberingLayoutFacts) }
1698
+ : {}),
1699
+ };
1700
+ }
1701
+
1702
+ function cloneNumberingLayoutFacts(
1703
+ numbering: NonNullable<NonNullable<RuntimeBlockFragment["layoutObject"]>["numbering"]>,
1704
+ ): NonNullable<NonNullable<RuntimeBlockFragment["layoutObject"]>["numbering"]> {
1705
+ return {
1706
+ ...numbering,
1707
+ ...(numbering.markerLane !== undefined
1708
+ ? { markerLane: { ...numbering.markerLane } }
1709
+ : {}),
1710
+ ...(numbering.textColumn !== undefined
1711
+ ? { textColumn: { ...numbering.textColumn } }
1712
+ : {}),
1713
+ ...(numbering.tabStops !== undefined
1714
+ ? { tabStops: numbering.tabStops.map((tab) => ({ ...tab })) }
1715
+ : {}),
1716
+ };
1717
+ }
1718
+
1579
1719
  function toPublicLineBox(box: RuntimeLineBox): PublicLineBox {
1580
1720
  return {
1581
1721
  fragmentId: box.fragmentId,
@@ -1824,10 +1964,8 @@ function findPageForOffsetAndStory(
1824
1964
  // `src/runtime/geometry/project-anchors.ts` in refactor/05 Slice 2b. The
1825
1965
  // facet's `getLineBoxes` / `getScopeRailSegments` / `getAllScopeRailSegments`
1826
1966
  // / `getAllScopeCardModels` methods above now import from there. The D1
1827
- // `emitLayoutGuardWarning` probes (including the
1828
- // `collectScopeRailSegmentsForQuery` null-input probe) are ported alongside
1829
- // via the exported `emitLayoutGuardWarning` symbol — they continue to
1830
- // fire through `setActiveLayoutWarningEmitter`.
1967
+ // `emitLayoutGuardWarning` probes continue to fire through the shared
1968
+ // diagnostics helper behind `setActiveLayoutWarningEmitter`.
1831
1969
  //
1832
1970
  // Hit-test (`resolveHitTest`, `hitTestRegion`, `containsPoint`) and
1833
1971
  // anchor-rect resolution (`resolveAnchorRects`) were moved in Slice 2a.
@@ -0,0 +1,107 @@
1
+ export interface TableRowRangeContract {
2
+ readonly pageIndex?: number;
3
+ readonly pageNumber?: number;
4
+ readonly rowRange: {
5
+ readonly from: number;
6
+ readonly to: number;
7
+ readonly totalRows: number;
8
+ };
9
+ }
10
+
11
+ export interface TableSplitRowContinuation {
12
+ readonly rowIndex: number;
13
+ readonly fromPageIndex?: number;
14
+ readonly toPageIndex?: number;
15
+ readonly fromPageNumber?: number;
16
+ readonly toPageNumber?: number;
17
+ }
18
+
19
+ export interface TableSplitRowCarry {
20
+ readonly rowIndex: number;
21
+ readonly continuesFromPreviousPage: boolean;
22
+ readonly continuesToNextPage: boolean;
23
+ }
24
+
25
+ /**
26
+ * Detect Word-style row continuations from adjacent page-local row ranges.
27
+ *
28
+ * Runtime table slices were initially row-boundary-only, so adjacent ranges
29
+ * looked like [0, 8), [8, 10). Word can split a row across the page boundary,
30
+ * which appears as an intentional overlap such as [0, 8), [7, 10). This helper
31
+ * keeps that contract explicit for D-8 table-pagination evidence and future
32
+ * split-row rendering work.
33
+ */
34
+ export function collectSplitRowContinuations(
35
+ ranges: readonly TableRowRangeContract[],
36
+ ): TableSplitRowContinuation[] {
37
+ const continuations: TableSplitRowContinuation[] = [];
38
+ const seen = new Set<string>();
39
+
40
+ for (let index = 1; index < ranges.length; index += 1) {
41
+ const previous = ranges[index - 1]!;
42
+ const current = ranges[index]!;
43
+ const overlapFrom = Math.max(previous.rowRange.from, current.rowRange.from);
44
+ const overlapTo = Math.min(previous.rowRange.to, current.rowRange.to);
45
+ for (let rowIndex = overlapFrom; rowIndex < overlapTo; rowIndex += 1) {
46
+ const key = `${rowIndex}:${previous.pageIndex ?? ""}:${current.pageIndex ?? ""}`;
47
+ if (seen.has(key)) continue;
48
+ seen.add(key);
49
+ continuations.push({
50
+ rowIndex,
51
+ ...(previous.pageIndex !== undefined ? { fromPageIndex: previous.pageIndex } : {}),
52
+ ...(current.pageIndex !== undefined ? { toPageIndex: current.pageIndex } : {}),
53
+ ...(previous.pageNumber !== undefined ? { fromPageNumber: previous.pageNumber } : {}),
54
+ ...(current.pageNumber !== undefined ? { toPageNumber: current.pageNumber } : {}),
55
+ });
56
+ }
57
+ }
58
+
59
+ return continuations;
60
+ }
61
+
62
+ export function collectSplitRowCarryForPage(
63
+ ranges: readonly TableRowRangeContract[],
64
+ pageRangeIndex: number,
65
+ ): TableSplitRowCarry[] {
66
+ const current = ranges[pageRangeIndex];
67
+ if (!current) return [];
68
+
69
+ const rows = new Map<number, TableSplitRowCarry>();
70
+ const addRows = (
71
+ from: number,
72
+ to: number,
73
+ direction: "fromPrevious" | "toNext",
74
+ ): void => {
75
+ for (let rowIndex = from; rowIndex < to; rowIndex += 1) {
76
+ const prior = rows.get(rowIndex);
77
+ rows.set(rowIndex, {
78
+ rowIndex,
79
+ continuesFromPreviousPage:
80
+ direction === "fromPrevious" || prior?.continuesFromPreviousPage === true,
81
+ continuesToNextPage:
82
+ direction === "toNext" || prior?.continuesToNextPage === true,
83
+ });
84
+ }
85
+ };
86
+
87
+ const previous = ranges[pageRangeIndex - 1];
88
+ if (previous) {
89
+ addRows(
90
+ Math.max(previous.rowRange.from, current.rowRange.from),
91
+ Math.min(previous.rowRange.to, current.rowRange.to),
92
+ "fromPrevious",
93
+ );
94
+ }
95
+
96
+ const next = ranges[pageRangeIndex + 1];
97
+ if (next) {
98
+ addRows(
99
+ Math.max(current.rowRange.from, next.rowRange.from),
100
+ Math.min(current.rowRange.to, next.rowRange.to),
101
+ "toNext",
102
+ );
103
+ }
104
+
105
+ return [...rows.values()].sort((a, b) => a.rowIndex - b.rowIndex);
106
+ }
107
+
@@ -51,6 +51,9 @@ import { __resolveCellWidth } from "./paginated-layout-engine.ts";
51
51
 
52
52
  const MIN_ROW_HEIGHT_TWIPS = 240;
53
53
  const TABLE_ROW_PADDING_TWIPS = 120;
54
+ const AUTO_ROW_CALIBRATION_MIN_RESOLVED_TWIPS = 1440;
55
+ const AUTO_ROW_CALIBRATION_MIN_DELTA_TWIPS = 720;
56
+ const AUTO_ROW_CALIBRATION_MIN_RATIO = 1.75;
54
57
 
55
58
  export interface MeasureTableRowHeightsInput {
56
59
  block: Extract<SurfaceBlockSnapshot, { kind: "table" }>;
@@ -58,7 +61,12 @@ export interface MeasureTableRowHeightsInput {
58
61
  measurementProvider?: LayoutMeasurementProvider;
59
62
  defaultTabInterval?: number;
60
63
  themeFonts?: LayoutThemeFonts;
61
- contentMode?: "legacy-min" | "resolved";
64
+ contentMode?: "legacy-min" | "resolved" | "auto-height-calibrated";
65
+ }
66
+
67
+ export interface TableRowHeightProfile {
68
+ readonly heights: readonly number[];
69
+ readonly calibratedAutoRowIndexes: readonly number[];
62
70
  }
63
71
 
64
72
  /**
@@ -74,6 +82,12 @@ export interface MeasureTableRowHeightsInput {
74
82
  export function measureTableRowHeights(
75
83
  input: MeasureTableRowHeightsInput,
76
84
  ): number[] {
85
+ return [...measureTableRowHeightProfile(input).heights];
86
+ }
87
+
88
+ export function measureTableRowHeightProfile(
89
+ input: MeasureTableRowHeightsInput,
90
+ ): TableRowHeightProfile {
77
91
  const {
78
92
  block,
79
93
  columnWidth,
@@ -83,66 +97,109 @@ export function measureTableRowHeights(
83
97
  contentMode = "legacy-min",
84
98
  } = input;
85
99
  const heights: number[] = [];
100
+ const calibratedAutoRowIndexes: number[] = [];
86
101
 
87
102
  const totalGridTwips = block.gridColumns.reduce((sum, w) => sum + w, 0);
88
103
  const gridScale =
89
104
  totalGridTwips > 0 && columnWidth > 0 ? columnWidth / totalGridTwips : 1;
90
105
 
91
- for (const row of block.rows) {
106
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
107
+ const row = block.rows[rowIndex]!;
92
108
  const explicitHeight = row.height ?? 0;
93
109
  const heightRule = row.heightRule ?? "auto";
94
110
  const gridBefore = row.gridBefore ?? 0;
95
111
 
96
- let contentHeight = MIN_ROW_HEIGHT_TWIPS;
97
- let columnCursor = gridBefore;
112
+ const measureContentHeight = (mode: "legacy-min" | "resolved"): number => {
113
+ let contentHeight = MIN_ROW_HEIGHT_TWIPS;
114
+ let columnCursor = gridBefore;
98
115
 
99
- for (const cell of row.cells) {
100
- const span = Math.max(1, cell.colspan ?? 1);
101
- const cellWidth = __resolveCellWidth(
102
- block.gridColumns,
103
- columnCursor,
104
- span,
105
- columnWidth,
106
- gridScale,
107
- );
108
- columnCursor += span;
116
+ for (const cell of row.cells) {
117
+ const span = Math.max(1, cell.colspan ?? 1);
118
+ const cellWidth = __resolveCellWidth(
119
+ block.gridColumns,
120
+ columnCursor,
121
+ span,
122
+ columnWidth,
123
+ gridScale,
124
+ );
125
+ columnCursor += span;
109
126
 
110
- if (cell.verticalMerge === "continue") continue;
127
+ if (cell.verticalMerge === "continue") continue;
111
128
 
112
- let cellContentHeight = 0;
113
- for (const child of cell.content) {
114
- if (child.kind === "paragraph") {
115
- cellContentHeight += contentMode === "resolved"
116
- ? measureParagraphStandaloneHeight(
117
- child,
118
- cellWidth,
119
- measurementProvider,
120
- defaultTabInterval,
121
- themeFonts,
122
- )
123
- : MIN_ROW_HEIGHT_TWIPS;
124
- } else {
125
- cellContentHeight += MIN_ROW_HEIGHT_TWIPS;
129
+ let cellContentHeight = 0;
130
+ for (const child of cell.content) {
131
+ if (child.kind === "paragraph") {
132
+ cellContentHeight += mode === "resolved"
133
+ ? measureParagraphStandaloneHeight(
134
+ child,
135
+ cellWidth,
136
+ measurementProvider,
137
+ defaultTabInterval,
138
+ themeFonts,
139
+ )
140
+ : MIN_ROW_HEIGHT_TWIPS;
141
+ } else {
142
+ cellContentHeight += MIN_ROW_HEIGHT_TWIPS;
143
+ }
126
144
  }
145
+ contentHeight = Math.max(contentHeight, cellContentHeight + TABLE_ROW_PADDING_TWIPS);
127
146
  }
128
- contentHeight = Math.max(contentHeight, cellContentHeight + TABLE_ROW_PADDING_TWIPS);
129
- }
147
+
148
+ return contentHeight;
149
+ };
150
+
151
+ const legacyContentHeight = measureContentHeight("legacy-min");
152
+ const resolvedContentHeight = contentMode === "legacy-min"
153
+ ? legacyContentHeight
154
+ : measureContentHeight("resolved");
130
155
 
131
156
  let rowHeight: number;
132
157
  if (heightRule === "exact" && explicitHeight > 0) {
133
158
  rowHeight = explicitHeight;
134
159
  } else if (heightRule === "atLeast" && explicitHeight > 0) {
135
- rowHeight = Math.max(explicitHeight, contentHeight);
160
+ rowHeight = Math.max(explicitHeight, resolvedContentHeight);
136
161
  } else if (explicitHeight > 0) {
137
- rowHeight = Math.max(explicitHeight, contentHeight);
162
+ const legacyRowHeight = Math.max(explicitHeight, legacyContentHeight);
163
+ const resolvedRowHeight = Math.max(explicitHeight, resolvedContentHeight);
164
+ const shouldCalibrate =
165
+ contentMode === "auto-height-calibrated" &&
166
+ shouldUseResolvedAutoRowHeight(row, legacyRowHeight, resolvedRowHeight);
167
+ if (shouldCalibrate) calibratedAutoRowIndexes.push(rowIndex);
168
+ rowHeight = contentMode === "resolved" || shouldCalibrate
169
+ ? resolvedRowHeight
170
+ : legacyRowHeight;
138
171
  } else {
139
- rowHeight = contentHeight;
172
+ const shouldCalibrate =
173
+ contentMode === "auto-height-calibrated" &&
174
+ shouldUseResolvedAutoRowHeight(row, legacyContentHeight, resolvedContentHeight);
175
+ if (shouldCalibrate) calibratedAutoRowIndexes.push(rowIndex);
176
+ rowHeight = contentMode === "resolved" || shouldCalibrate
177
+ ? resolvedContentHeight
178
+ : legacyContentHeight;
140
179
  }
141
180
 
142
181
  heights.push(Math.max(MIN_ROW_HEIGHT_TWIPS, rowHeight));
143
182
  }
144
183
 
145
- return heights;
184
+ return {
185
+ heights: Object.freeze(heights),
186
+ calibratedAutoRowIndexes: Object.freeze(calibratedAutoRowIndexes),
187
+ };
188
+ }
189
+
190
+ function shouldUseResolvedAutoRowHeight(
191
+ row: Extract<SurfaceBlockSnapshot, { kind: "table" }>["rows"][number],
192
+ legacyHeight: number,
193
+ resolvedHeight: number,
194
+ ): boolean {
195
+ if ((row.heightRule ?? "auto") === "exact") return false;
196
+ if (row.cantSplit === true) return false;
197
+ const delta = resolvedHeight - legacyHeight;
198
+ return (
199
+ resolvedHeight >= AUTO_ROW_CALIBRATION_MIN_RESOLVED_TWIPS &&
200
+ delta >= AUTO_ROW_CALIBRATION_MIN_DELTA_TWIPS &&
201
+ resolvedHeight / Math.max(1, legacyHeight) >= AUTO_ROW_CALIBRATION_MIN_RATIO
202
+ );
146
203
  }
147
204
 
148
205
  /**
@@ -17,8 +17,8 @@ import type { RuntimePageGraph } from "../../model/layout/runtime-page-graph-typ
17
17
  * it, and the warm-path loader that rehydrates it.
18
18
  *
19
19
  * Load-time invariants checked by consumers before trusting the envelope:
20
- * - `schemaVersion === LAYCACHE_SCHEMA_VERSION` bump invalidates
21
- * - `engineVersion === LAYOUT_ENGINE_VERSION` bump invalidates
20
+ * - `schemaVersion === LAYCACHE_SCHEMA_VERSION` cache shape matches
21
+ * - `engineVersion === LAYOUT_ENGINE_VERSION` layout output matches
22
22
  * - `graph.revision === 0` — canonical marker
23
23
  *
24
24
  * The envelope MUST be structured-clone-safe because IndexedDB and Plan B's
@@ -14,11 +14,12 @@
14
14
  * metric source. "empirical-backend" in
15
15
  * Plan A; a real font-derived string after
16
16
  * Phase 8.
17
- * 3. engineVersion — LAYOUT_ENGINE_VERSION from src/runtime/
18
- * layout/layout-engine-version.ts. Bumped by
19
- * CI gate on any layout/render shape change.
17
+ * 3. engineVersion — LAYOUT_ENGINE_VERSION from
18
+ * src/runtime/layout/layout-engine-version.ts.
19
+ * Bumped when layout output or cache-key
20
+ * semantics change for the same input.
20
21
  * 4. schemaVersion — LAYCACHE_SCHEMA_VERSION for envelope
21
- * format.
22
+ * format and cached payload shape.
22
23
  * 5. canonicalDocumentHash — (Plan B, schema v2) sha256 of sorted-keys
23
24
  * JSON of the CanonicalDocument. Catches
24
25
  * non-structural mutations (styles,
@@ -222,4 +222,3 @@ function isValidCacheEnvelope(value: unknown): value is CacheEnvelope {
222
222
  }
223
223
  return true;
224
224
  }
225
-
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  import { MAIN_STORY_TARGET } from "../../core/selection/mapping.ts";
17
- import { recordPerfSample } from "../../ui-tailwind/editor-surface/perf-probe.ts";
17
+ import { recordPerfSample } from "../telemetry/perf-probe.ts";
18
18
  import { storyTargetKey } from "../story-targeting.ts";
19
19
  import type { EditorStoryTarget } from "../../api/public-types";
20
20
  import type {