@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
@@ -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 {
@@ -1,15 +1,25 @@
1
+ import type { EditorStoryTarget } from "../api/public-types.ts";
1
2
  import type { CanonicalDocumentEnvelope } from "../core/state/editor-state.ts";
2
3
  import {
3
4
  getReviewCommandIntent,
4
5
  isSingleRevisionReviewCommand,
5
6
  type ReviewCommand,
6
7
  } from "../core/commands/review-commands.ts";
7
- import type { TransactionMapping } from "../core/selection/mapping.ts";
8
+ import {
9
+ MAIN_STORY_TARGET,
10
+ storyTargetsEqual,
11
+ type TransactionMapping,
12
+ } from "../core/selection/mapping.ts";
8
13
  import {
9
14
  applyRevisionAction,
15
+ type ApplyRevisionActionResult,
10
16
  type RevisionActionOutcome,
11
17
  } from "../review/store/revision-actions.ts";
12
- import { type RevisionStore } from "../review/store/revision-store.ts";
18
+ import {
19
+ createRevisionStore,
20
+ type RevisionStore,
21
+ } from "../review/store/revision-store.ts";
22
+ import { getStoryBlocks, replaceStoryBlocks } from "./story-targeting.ts";
13
23
 
14
24
  export interface RevisionRuntimeState {
15
25
  document: CanonicalDocumentEnvelope;
@@ -30,7 +40,12 @@ export interface RevisionRuntimeCommandEffects {
30
40
 
31
41
  export interface RevisionRuntimeCommandResult extends RevisionRuntimeState {
32
42
  outcomes: RevisionActionOutcome[];
33
- mappings: Array<{ revisionId: string; mapping: TransactionMapping; steps: number }>;
43
+ mappings: Array<{
44
+ revisionId: string;
45
+ mapping: TransactionMapping;
46
+ steps: number;
47
+ storyTarget: EditorStoryTarget;
48
+ }>;
34
49
  effects: RevisionRuntimeCommandEffects;
35
50
  }
36
51
 
@@ -44,30 +59,31 @@ export function applyRevisionRuntimeCommand(
44
59
  const revisionIds = expandLinkedMovePartners(rawRevisionIds, options.state.store);
45
60
 
46
61
  const outcomes: RevisionActionOutcome[] = [];
47
- const mappings: Array<{ revisionId: string; mapping: TransactionMapping; steps: number }> = [];
62
+ const mappings: RevisionRuntimeCommandResult["mappings"] = [];
48
63
  const appliedRevisionIds: string[] = [];
49
64
  const detachedRevisionIds = new Set<string>();
50
65
 
51
66
  let state = options.state;
52
67
 
53
68
  for (const revisionId of revisionIds) {
69
+ const revision = state.store.revisions[revisionId];
70
+ const storyTarget = getRevisionStoryTarget(revision);
71
+ const scopedState = createStoryScopedRevisionState(state, storyTarget);
54
72
  const result = applyRevisionAction({
55
- document: state.document,
56
- store: state.store,
73
+ document: scopedState.document,
74
+ store: scopedState.store,
57
75
  revisionId,
58
76
  intent: getReviewCommandIntent(options.command),
59
77
  timestamp: options.timestamp,
60
78
  });
61
79
 
62
- state = {
63
- document: result.document,
64
- store: result.store,
65
- };
80
+ state = mergeStoryScopedRevisionResult(state, storyTarget, scopedState, result);
66
81
  outcomes.push(result.outcome);
67
82
  mappings.push({
68
83
  revisionId,
69
84
  mapping: result.mapping,
70
85
  steps: result.mapping.steps.length,
86
+ storyTarget,
71
87
  });
72
88
 
73
89
  if (result.outcome.kind === "applied") {
@@ -94,6 +110,92 @@ export function applyRevisionRuntimeCommand(
94
110
  };
95
111
  }
96
112
 
113
+ interface StoryScopedRevisionState extends RevisionRuntimeState {
114
+ storyRevisionIds: readonly string[];
115
+ }
116
+
117
+ function createStoryScopedRevisionState(
118
+ state: RevisionRuntimeState,
119
+ storyTarget: EditorStoryTarget,
120
+ ): StoryScopedRevisionState {
121
+ if (storyTargetsEqual(storyTarget, MAIN_STORY_TARGET)) {
122
+ return {
123
+ ...state,
124
+ storyRevisionIds: Object.keys(state.store.revisions),
125
+ };
126
+ }
127
+
128
+ const storyRevisionIds = Object.entries(state.store.revisions)
129
+ .filter(([, revision]) =>
130
+ storyTargetsEqual(getRevisionStoryTarget(revision), storyTarget)
131
+ )
132
+ .map(([revisionId]) => revisionId);
133
+ const storyRevisions = Object.fromEntries(
134
+ storyRevisionIds.map((revisionId) => [
135
+ revisionId,
136
+ state.store.revisions[revisionId]!,
137
+ ]),
138
+ );
139
+
140
+ return {
141
+ document: {
142
+ ...state.document,
143
+ content: {
144
+ ...state.document.content,
145
+ children: [...getStoryBlocks(state.document, storyTarget)],
146
+ },
147
+ },
148
+ store: createRevisionStore(storyRevisions),
149
+ storyRevisionIds,
150
+ };
151
+ }
152
+
153
+ function mergeStoryScopedRevisionResult(
154
+ previousState: RevisionRuntimeState,
155
+ storyTarget: EditorStoryTarget,
156
+ scopedState: StoryScopedRevisionState,
157
+ result: ApplyRevisionActionResult,
158
+ ): RevisionRuntimeState {
159
+ if (storyTargetsEqual(storyTarget, MAIN_STORY_TARGET)) {
160
+ return {
161
+ document: result.document,
162
+ store: result.store,
163
+ };
164
+ }
165
+
166
+ const nextStore = createRevisionStore({
167
+ ...previousState.store.revisions,
168
+ ...Object.fromEntries(
169
+ scopedState.storyRevisionIds
170
+ .filter((revisionId) => result.store.revisions[revisionId])
171
+ .map((revisionId) => [
172
+ revisionId,
173
+ result.store.revisions[revisionId]!,
174
+ ]),
175
+ ),
176
+ });
177
+
178
+ return {
179
+ document: replaceStoryBlocks(
180
+ {
181
+ ...previousState.document,
182
+ updatedAt: result.document.updatedAt,
183
+ },
184
+ storyTarget,
185
+ result.document.content.children,
186
+ ),
187
+ store: nextStore,
188
+ };
189
+ }
190
+
191
+ function getRevisionStoryTarget(
192
+ revision: RevisionStore["revisions"][string] | undefined,
193
+ ): EditorStoryTarget {
194
+ return revision?.metadata.storyTarget
195
+ ? { ...revision.metadata.storyTarget }
196
+ : MAIN_STORY_TARGET;
197
+ }
198
+
97
199
  function expandLinkedMovePartners(
98
200
  revisionIds: readonly string[],
99
201
  store: RevisionStore,
@@ -34,6 +34,7 @@ export type {
34
34
  InteractionGuardSnapshot,
35
35
  TextFormattingDirective,
36
36
  WorkflowBlockedCommandReason,
37
+ WorkflowEventOrigin,
37
38
  WorkflowMetadataEntry,
38
39
  WorkflowMetadataSnapshot,
39
40
  WorkflowOverlay,
@@ -63,6 +63,7 @@ import {
63
63
  import { resolveScopeRange } from "./scope-range.ts";
64
64
  import type {
65
65
  ReplacementScope,
66
+ ScopeActionPosture,
66
67
  ScopeActionOperationKind,
67
68
  SemanticScope,
68
69
  ValidationApproval,
@@ -144,8 +145,6 @@ function inferActionId(
144
145
  case "insert-before":
145
146
  case "insert-after":
146
147
  return "generate_text";
147
- case "split":
148
- return "rewrite_paragraph";
149
148
  case "annotate":
150
149
  return "suggest_comment_response";
151
150
  }
@@ -296,6 +295,17 @@ function collectPreservationVerdict(
296
295
  ): void {
297
296
  const { document, scope, positionMap } = inputs;
298
297
  if (inputs.skipPreservation === true) return;
298
+ // Insert-before / insert-after lower to collapsed insertions at the
299
+ // effective scope edge. They do not replace or delete the scope's
300
+ // current contents, so preserve-only payloads and nested markers
301
+ // inside the scope are not at risk in the same way they are for
302
+ // `replace`.
303
+ if (
304
+ inputs.operation === "insert-before" ||
305
+ inputs.operation === "insert-after"
306
+ ) {
307
+ return;
308
+ }
299
309
  if (!document) return;
300
310
  const pm = positionMap ?? buildScopePositionMap(document);
301
311
  const range = inputs.enumeratedScope
@@ -397,6 +407,15 @@ function collectPolicyVerdict(
397
407
  return undefined;
398
408
  }
399
409
 
410
+ function validationPosture(
411
+ blockedReasons: readonly string[],
412
+ warnings: readonly ValidationIssue[],
413
+ ): ScopeActionPosture {
414
+ if (blockedReasons.length > 0) return "hard-refusal";
415
+ if (warnings.length > 0) return "warn-and-proceed";
416
+ return "supported";
417
+ }
418
+
400
419
  /**
401
420
  * Compose a single validation verdict for a proposed scope replacement.
402
421
  * Synchronous, deterministic, free of side effects. Safe to call in hot
@@ -420,6 +439,7 @@ export function composeScopeValidation(
420
439
  const safe = blockedReasons.length === 0;
421
440
  return {
422
441
  safe,
442
+ posture: validationPosture(blockedReasons, warnings),
423
443
  blockedReasons: Object.freeze([...blockedReasons]),
424
444
  warnings: Object.freeze([...warnings]),
425
445
  ...(approval ? { approval } : {}),