@beyondwork/docx-react-component 1.0.104 → 1.0.106

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +3 -0
  3. package/src/api/v3/_create.ts +9 -2
  4. package/src/api/v3/ai/_audit-reference.ts +28 -0
  5. package/src/api/v3/ai/_pe2-evidence.ts +419 -0
  6. package/src/api/v3/ai/attach.ts +22 -2
  7. package/src/api/v3/ai/bundle.ts +18 -6
  8. package/src/api/v3/ai/inspect.ts +12 -2
  9. package/src/api/v3/ai/replacement.ts +124 -0
  10. package/src/api/v3/index.ts +7 -0
  11. package/src/api/v3/ui/_types.ts +139 -0
  12. package/src/api/v3/ui/index.ts +9 -0
  13. package/src/api/v3/ui/overlays.ts +104 -0
  14. package/src/api/v3/ui/viewport.ts +97 -0
  15. package/src/model/layout/index.ts +3 -0
  16. package/src/model/layout/page-graph-types.ts +118 -0
  17. package/src/model/layout/runtime-page-graph-types.ts +13 -0
  18. package/src/runtime/document-runtime.ts +39 -18
  19. package/src/runtime/event-refresh-hints.ts +33 -6
  20. package/src/runtime/geometry/geometry-facet.ts +9 -1
  21. package/src/runtime/geometry/geometry-index.ts +461 -10
  22. package/src/runtime/geometry/geometry-types.ts +6 -0
  23. package/src/runtime/geometry/object-handles.ts +7 -4
  24. package/src/runtime/layout/layout-engine-instance.ts +2 -0
  25. package/src/runtime/layout/layout-engine-version.ts +36 -1
  26. package/src/runtime/layout/page-graph.ts +697 -10
  27. package/src/runtime/layout/paginated-layout-engine.ts +10 -0
  28. package/src/runtime/layout/project-block-fragments.ts +187 -8
  29. package/src/runtime/layout/public-facet.ts +236 -0
  30. package/src/runtime/prerender/graph-canonicalize.ts +14 -0
  31. package/src/runtime/workflow/index.ts +1 -0
  32. package/src/runtime/workflow/overlay-lanes.ts +228 -0
  33. package/src/ui/presence-overlay-lane.ts +131 -0
  34. package/src/ui/ui-controller-factory.ts +21 -0
@@ -1791,6 +1791,16 @@ export function paginateSectionBlocksWithSplits(
1791
1791
  to: refRange.blockTo,
1792
1792
  heightTwips: heightTwips + FOOTNOTE_REFERENCE_RESERVATION_TWIPS,
1793
1793
  kind: "whole",
1794
+ layoutObject: {
1795
+ objectId: `footnote-body:${fragmentId}`,
1796
+ kind: "footnote-body",
1797
+ sourceBlockId: `note-body-${noteKind}-${noteId}`,
1798
+ paginationRole: "whole",
1799
+ measuredExtentTwips: {
1800
+ heightTwips: heightTwips + FOOTNOTE_REFERENCE_RESERVATION_TWIPS,
1801
+ widthTwips: effectiveColumnWidth,
1802
+ },
1803
+ },
1794
1804
  };
1795
1805
 
1796
1806
  allocations.push(allocation);
@@ -126,6 +126,14 @@ export function projectSurfaceBlocksToPageFragments(
126
126
  if (pageIndex === null) continue;
127
127
 
128
128
  const columnIndex = columnIndexFor(pageIndex, block.blockId);
129
+ const heightTwips = measuredHeightFor(
130
+ pageIndex,
131
+ block.blockId,
132
+ estimateBlockHeightFromSpan(block),
133
+ );
134
+ const widthTwips = fragmentMeasurementsByPageIndex
135
+ ?.get(pageIndex)
136
+ ?.get(block.blockId)?.widthTwips;
129
137
  const fragment: FragmentWithoutPageId = {
130
138
  fragmentId: `fragment-${block.blockId}`,
131
139
  blockId: block.blockId,
@@ -133,13 +141,16 @@ export function projectSurfaceBlocksToPageFragments(
133
141
  regionKind: "body",
134
142
  from: block.from,
135
143
  to: block.to,
136
- heightTwips: measuredHeightFor(
137
- pageIndex,
138
- block.blockId,
139
- estimateBlockHeightFromSpan(block),
140
- ),
144
+ heightTwips,
141
145
  ...deriveStyleMetadata(block),
142
146
  kind: "whole",
147
+ layoutObject: buildFragmentLayoutObject({
148
+ block,
149
+ fragmentId: `fragment-${block.blockId}`,
150
+ heightTwips,
151
+ widthTwips,
152
+ paginationRole: "whole",
153
+ }),
143
154
  ...(columnIndex !== undefined ? { columnIndex } : {}),
144
155
  };
145
156
 
@@ -224,6 +235,7 @@ function emitSlicedParagraph(
224
235
  ): void {
225
236
  for (let i = 0; i < slices.length; i += 1) {
226
237
  const slice = slices[i]!;
238
+ const heightTwips = slice.heightTwips ?? estimateSliceHeightFromLines(slice.lineRange);
227
239
  const fragment: FragmentWithoutPageId = {
228
240
  fragmentId: `fragment-${block.blockId}-slice-${i}`,
229
241
  blockId: block.blockId,
@@ -231,15 +243,38 @@ function emitSlicedParagraph(
231
243
  regionKind: "body",
232
244
  from: block.from,
233
245
  to: block.to,
234
- heightTwips: slice.heightTwips ?? estimateSliceHeightFromLines(slice.lineRange),
246
+ heightTwips,
235
247
  ...deriveStyleMetadata(block),
236
248
  kind: "paragraph-slice",
237
249
  paragraphLineRange: slice.lineRange,
250
+ continuation: buildParagraphContinuationCursor(slice, i, slices.length),
251
+ layoutObject: buildFragmentLayoutObject({
252
+ block,
253
+ fragmentId: `fragment-${block.blockId}-slice-${i}`,
254
+ heightTwips,
255
+ widthTwips: slice.widthTwips,
256
+ paginationRole: slice.lineRange.from > 0 ? "continuation" : "slice",
257
+ }),
238
258
  };
239
259
  emit(slice.pageIndex, fragment);
240
260
  }
241
261
  }
242
262
 
263
+ function buildParagraphContinuationCursor(
264
+ slice: ParagraphLineSlice,
265
+ sequenceIndex: number,
266
+ sliceCount: number,
267
+ ): NonNullable<RuntimeBlockFragment["continuation"]> {
268
+ return {
269
+ kind: "paragraph",
270
+ sequenceIndex,
271
+ sliceCount,
272
+ lineRange: slice.lineRange,
273
+ continuesFromPreviousPage: slice.lineRange.from > 0,
274
+ continuesToNextPage: slice.lineRange.to < slice.lineRange.totalLines,
275
+ };
276
+ }
277
+
243
278
  function estimateSliceHeightFromLines(lineRange: {
244
279
  from: number;
245
280
  to: number;
@@ -259,12 +294,13 @@ function estimateSliceHeightFromLines(lineRange: {
259
294
  * every continuation page.
260
295
  */
261
296
  function emitSlicedTable(
262
- block: SurfaceBlockSnapshot,
297
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
263
298
  slices: readonly TableRowSlice[],
264
299
  emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
265
300
  ): void {
266
301
  for (let i = 0; i < slices.length; i += 1) {
267
302
  const slice = slices[i]!;
303
+ const heightTwips = slice.heightTwips ?? estimateSliceHeightFromRows(slice.rowRange);
268
304
  const fragment: FragmentWithoutPageId = {
269
305
  fragmentId: `fragment-${block.blockId}-rowslice-${i}`,
270
306
  blockId: block.blockId,
@@ -272,16 +308,106 @@ function emitSlicedTable(
272
308
  regionKind: "body",
273
309
  from: block.from,
274
310
  to: block.to,
275
- heightTwips: slice.heightTwips ?? estimateSliceHeightFromRows(slice.rowRange),
311
+ heightTwips,
276
312
  ...deriveStyleMetadata(block),
277
313
  kind: "table-slice",
278
314
  tableRowRange: slice.rowRange,
315
+ continuation: buildTableContinuationCursor(block, slice, i, slices.length),
316
+ layoutObject: buildFragmentLayoutObject({
317
+ block,
318
+ fragmentId: `fragment-${block.blockId}-rowslice-${i}`,
319
+ heightTwips,
320
+ paginationRole: slice.rowRange.from > 0 ? "continuation" : "slice",
321
+ }),
279
322
  ...(slice.columnIndex !== undefined ? { columnIndex: slice.columnIndex } : {}),
280
323
  };
281
324
  emit(slice.pageIndex, fragment);
282
325
  }
283
326
  }
284
327
 
328
+ function buildTableContinuationCursor(
329
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
330
+ slice: TableRowSlice,
331
+ sequenceIndex: number,
332
+ sliceCount: number,
333
+ ): NonNullable<RuntimeBlockFragment["continuation"]> {
334
+ return {
335
+ kind: "table",
336
+ sequenceIndex,
337
+ sliceCount,
338
+ rowRange: slice.rowRange,
339
+ continuesFromPreviousPage: slice.rowRange.from > 0,
340
+ continuesToNextPage: slice.rowRange.to < slice.rowRange.totalRows,
341
+ repeatedHeaderRowIndexes:
342
+ slice.rowRange.from > 0
343
+ ? collectRepeatedHeaderRowIndexes(block, slice.rowRange.from)
344
+ : [],
345
+ verticalMergeCarry: collectVerticalMergeCarry(block, slice.rowRange.from),
346
+ };
347
+ }
348
+
349
+ function collectRepeatedHeaderRowIndexes(
350
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
351
+ startRow: number,
352
+ ): number[] {
353
+ const headerRows: number[] = [];
354
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
355
+ if (rowIndex >= startRow) break;
356
+ if (block.rows[rowIndex]?.isHeader === true) headerRows.push(rowIndex);
357
+ }
358
+ return headerRows;
359
+ }
360
+
361
+ function collectVerticalMergeCarry(
362
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
363
+ startRow: number,
364
+ ): Array<{ columnIndex: number; restartRowIndex: number }> {
365
+ if (startRow <= 0) return [];
366
+
367
+ const activeByColumn = new Map<number, number>();
368
+ for (let rowIndex = 0; rowIndex < startRow; rowIndex += 1) {
369
+ visitRowCells(block.rows[rowIndex], (columnIndex, span, verticalMerge) => {
370
+ for (let column = columnIndex; column < columnIndex + span; column += 1) {
371
+ if (verticalMerge === "restart") {
372
+ activeByColumn.set(column, rowIndex);
373
+ } else if (verticalMerge !== "continue") {
374
+ activeByColumn.delete(column);
375
+ }
376
+ }
377
+ });
378
+ }
379
+
380
+ const carried = new Map<number, number>();
381
+ visitRowCells(block.rows[startRow], (columnIndex, span, verticalMerge) => {
382
+ if (verticalMerge !== "continue") return;
383
+ for (let column = columnIndex; column < columnIndex + span; column += 1) {
384
+ const restartRowIndex = activeByColumn.get(column);
385
+ if (restartRowIndex !== undefined) carried.set(column, restartRowIndex);
386
+ }
387
+ });
388
+
389
+ return [...carried]
390
+ .sort(([a], [b]) => a - b)
391
+ .map(([columnIndex, restartRowIndex]) => ({ columnIndex, restartRowIndex }));
392
+ }
393
+
394
+ function visitRowCells(
395
+ row: Extract<SurfaceBlockSnapshot, { kind: "table" }>["rows"][number] | undefined,
396
+ visit: (
397
+ columnIndex: number,
398
+ span: number,
399
+ verticalMerge: "restart" | "continue" | null,
400
+ ) => void,
401
+ ): void {
402
+ if (!row) return;
403
+ let columnIndex = Math.max(0, row.gridBefore ?? 0);
404
+ for (const cell of row.cells) {
405
+ const span = Math.max(1, cell.gridSpan || cell.colspan || 1);
406
+ visit(columnIndex, span, cell.verticalMerge);
407
+ columnIndex += span;
408
+ }
409
+ }
410
+
285
411
  function estimateSliceHeightFromRows(rowRange: {
286
412
  from: number;
287
413
  to: number;
@@ -291,6 +417,59 @@ function estimateSliceHeightFromRows(rowRange: {
291
417
  return rows * 360; // ~1 line + padding per row, approximate
292
418
  }
293
419
 
420
+ function buildFragmentLayoutObject(input: {
421
+ block: SurfaceBlockSnapshot;
422
+ fragmentId: string;
423
+ heightTwips: number;
424
+ widthTwips?: number;
425
+ paginationRole: NonNullable<RuntimeBlockFragment["layoutObject"]>["paginationRole"];
426
+ }): NonNullable<RuntimeBlockFragment["layoutObject"]> {
427
+ const fieldFamilies = collectFieldFamilies(input.block);
428
+ const kind = resolveFragmentLayoutObjectKind(input.block, fieldFamilies);
429
+ return {
430
+ objectId: `${kind}:${input.fragmentId}`,
431
+ kind,
432
+ sourceBlockId: input.block.blockId,
433
+ paginationRole: input.paginationRole,
434
+ measuredExtentTwips: {
435
+ heightTwips: Math.max(0, input.heightTwips),
436
+ ...(input.widthTwips !== undefined
437
+ ? { widthTwips: Math.max(0, input.widthTwips) }
438
+ : {}),
439
+ },
440
+ ...(fieldFamilies.length > 0 ? { fieldFamilies } : {}),
441
+ };
442
+ }
443
+
444
+ function resolveFragmentLayoutObjectKind(
445
+ block: SurfaceBlockSnapshot,
446
+ fieldFamilies: readonly string[],
447
+ ): NonNullable<RuntimeBlockFragment["layoutObject"]>["kind"] {
448
+ switch (block.kind) {
449
+ case "paragraph":
450
+ if (fieldFamilies.length > 0) return "field-region";
451
+ return block.numbering ? "numbered-paragraph" : "paragraph";
452
+ case "table":
453
+ return "table";
454
+ case "sdt_block":
455
+ return "sdt-block";
456
+ case "opaque_block":
457
+ return "opaque-block";
458
+ }
459
+ }
460
+
461
+ function collectFieldFamilies(block: SurfaceBlockSnapshot): string[] {
462
+ if (block.kind !== "paragraph") return [];
463
+ const families: string[] = [];
464
+ const seen = new Set<string>();
465
+ for (const segment of block.segments) {
466
+ if (segment.kind !== "field_ref" || seen.has(segment.fieldFamily)) continue;
467
+ seen.add(segment.fieldFamily);
468
+ families.push(segment.fieldFamily);
469
+ }
470
+ return families;
471
+ }
472
+
294
473
  function findPageIndexForOffset(
295
474
  pages: readonly DocumentPageSnapshot[],
296
475
  offset: number,
@@ -25,12 +25,17 @@ import type {
25
25
  } from "./page-story-resolver.ts";
26
26
  import type {
27
27
  RuntimeBlockFragment,
28
+ RuntimeLayoutContinuationCursor,
29
+ RuntimeLayoutDivergence,
28
30
  RuntimeLineBox,
29
31
  RuntimeNoteAllocation,
32
+ RuntimePageFrame,
30
33
  RuntimePageGraph,
31
34
  RuntimePageNode,
32
35
  RuntimePageRegion,
33
36
  RuntimePageRegions,
37
+ RuntimeStoryAnchoredObject,
38
+ RuntimeTwipsRect,
34
39
  } from "./page-graph.ts";
35
40
  import type {
36
41
  ResolvedFormattingState,
@@ -132,6 +137,67 @@ export function emitLayoutGuardWarning(
132
137
  // Public read model types (shape-stable, cloned at the facet boundary)
133
138
  // ---------------------------------------------------------------------------
134
139
 
140
+ export interface PublicTwipsRect {
141
+ xTwips: number;
142
+ yTwips: number;
143
+ widthTwips: number;
144
+ heightTwips: number;
145
+ }
146
+
147
+ export interface PublicLayoutDivergence {
148
+ divergenceId: string;
149
+ kind: RuntimeLayoutDivergence["kind"];
150
+ source: RuntimeLayoutDivergence["source"];
151
+ severity: RuntimeLayoutDivergence["severity"];
152
+ message: string;
153
+ regionKinds?: readonly RuntimePageRegion["kind"][];
154
+ fragmentIds?: readonly string[];
155
+ }
156
+
157
+ export interface PublicPageFrame {
158
+ frameId: string;
159
+ pageId: string;
160
+ pageIndex: number;
161
+ sectionIndex: number;
162
+ displayPageNumber: number;
163
+ physicalBoundsTwips: PublicTwipsRect;
164
+ divergenceIds: readonly string[];
165
+ signature: string;
166
+ exclusionZoneCount: number;
167
+ regionFrames: readonly {
168
+ kind: RuntimePageRegion["kind"];
169
+ rectTwips?: PublicTwipsRect;
170
+ fragmentIds: readonly string[];
171
+ }[];
172
+ pageLocalStories: readonly PublicPageLocalStoryInstance[];
173
+ }
174
+
175
+ export interface PublicPageLocalStoryInstance {
176
+ instanceId: string;
177
+ storyKey: string;
178
+ pageId: string;
179
+ kind: "header" | "footer";
180
+ variant: "default" | "first" | "even" | "odd";
181
+ relationshipId: string;
182
+ sectionIndex?: number;
183
+ anchoredObjects: readonly PublicStoryAnchoredObject[];
184
+ measuredFrameHeightTwips: number;
185
+ signature: string;
186
+ }
187
+
188
+ export interface PublicStoryAnchoredObject {
189
+ objectId: string;
190
+ sourceType: RuntimeStoryAnchoredObject["sourceType"];
191
+ display: RuntimeStoryAnchoredObject["display"];
192
+ extentTwips?: {
193
+ widthTwips: number;
194
+ heightTwips: number;
195
+ };
196
+ relationshipIds?: readonly string[];
197
+ preserveOnly: boolean;
198
+ divergenceIds: readonly string[];
199
+ }
200
+
135
201
  export interface PublicPageNode {
136
202
  pageId: string;
137
203
  pageIndex: number;
@@ -157,6 +223,10 @@ export interface PublicPageNode {
157
223
  lineBoxCount: number;
158
224
  /** Footnotes reserved at the bottom of the page, if any. */
159
225
  noteAllocations: readonly PublicNoteAllocation[];
226
+ /** PE2 page-frame graph projection, when emitted by the L04 graph. */
227
+ frame?: PublicPageFrame;
228
+ /** Typed layout divergences detected while building this page. */
229
+ divergences?: readonly PublicLayoutDivergence[];
160
230
  }
161
231
 
162
232
  export interface PublicResolvedPageStories {
@@ -187,6 +257,8 @@ export interface PublicPageRegion {
187
257
  widthTwips: number;
188
258
  heightTwips: number;
189
259
  fragmentCount: number;
260
+ /** PE2 semantic twips rect, when populated by the L04 page-frame graph. */
261
+ rectTwips?: PublicTwipsRect;
190
262
  }
191
263
 
192
264
  /**
@@ -208,8 +280,25 @@ export interface PublicBlockFragment {
208
280
  to: number;
209
281
  heightTwips: number;
210
282
  orderInRegion: number;
283
+ kind?: "whole" | "paragraph-slice" | "table-slice";
284
+ paragraphLineRange?: {
285
+ from: number;
286
+ to: number;
287
+ totalLines: number;
288
+ };
289
+ tableRowRange?: {
290
+ from: number;
291
+ to: number;
292
+ totalRows: number;
293
+ };
294
+ columnIndex?: number;
295
+ continuation?: PublicLayoutContinuationCursor;
296
+ layoutObject?: PublicFragmentLayoutObject;
211
297
  }
212
298
 
299
+ export type PublicLayoutContinuationCursor = RuntimeLayoutContinuationCursor;
300
+ export type PublicFragmentLayoutObject = NonNullable<RuntimeBlockFragment["layoutObject"]>;
301
+
213
302
  /**
214
303
  * P8 — One block snapshot rendered into a region of a page. Returned by
215
304
  * `WordReviewEditorLayoutFacet.getStoryBlocksForRegion` and
@@ -245,6 +334,7 @@ export interface PublicNoteAllocation {
245
334
  noteKind: "footnote" | "endnote";
246
335
  noteId: string;
247
336
  reservedHeightTwips: number;
337
+ fragmentId?: string;
248
338
  }
249
339
 
250
340
  export interface PublicPageAnchor {
@@ -1367,12 +1457,113 @@ function toPublicPageNode(
1367
1457
  regions: toPublicPageRegions(node.regions),
1368
1458
  lineBoxCount: node.lineBoxes.length,
1369
1459
  noteAllocations: node.noteAllocations.map(toPublicNoteAllocation),
1460
+ ...(node.frame ? { frame: toPublicPageFrame(node.frame) } : {}),
1461
+ ...(node.divergences && node.divergences.length > 0
1462
+ ? { divergences: node.divergences.map(toPublicLayoutDivergence) }
1463
+ : {}),
1370
1464
  };
1371
1465
  publicPageNodeCache.set(node, built);
1372
1466
  void graph; // reserved for future cross-page derivations
1373
1467
  return built;
1374
1468
  }
1375
1469
 
1470
+ function toPublicTwipsRect(rect: RuntimeTwipsRect): PublicTwipsRect {
1471
+ return {
1472
+ xTwips: rect.xTwips,
1473
+ yTwips: rect.yTwips,
1474
+ widthTwips: rect.widthTwips,
1475
+ heightTwips: rect.heightTwips,
1476
+ };
1477
+ }
1478
+
1479
+ function frameRegionEntries(
1480
+ frame: RuntimePageFrame,
1481
+ ): PublicPageFrame["regionFrames"] {
1482
+ const regions: PublicPageFrame["regionFrames"][number][] = [];
1483
+ const push = (region: RuntimePageRegion | undefined) => {
1484
+ if (!region) return;
1485
+ regions.push({
1486
+ kind: region.kind,
1487
+ ...(region.rectTwips ? { rectTwips: toPublicTwipsRect(region.rectTwips) } : {}),
1488
+ fragmentIds: [...region.fragmentIds],
1489
+ });
1490
+ };
1491
+ push(frame.regions.header);
1492
+ push(frame.regions.body);
1493
+ for (const column of frame.regions.columns ?? []) push(column);
1494
+ push(frame.regions.footer);
1495
+ for (const footnote of frame.regions.footnotes ?? []) push(footnote);
1496
+ return regions;
1497
+ }
1498
+
1499
+ function toPublicPageFrame(frame: RuntimePageFrame): PublicPageFrame {
1500
+ return {
1501
+ frameId: frame.frameId,
1502
+ pageId: frame.pageId,
1503
+ pageIndex: frame.pageIndex,
1504
+ sectionIndex: frame.sectionIndex,
1505
+ displayPageNumber: frame.displayPageNumber,
1506
+ physicalBoundsTwips: toPublicTwipsRect(frame.physicalBoundsTwips),
1507
+ divergenceIds: [...frame.divergenceIds],
1508
+ signature: frame.signature,
1509
+ exclusionZoneCount: frame.regions.exclusionZones.length,
1510
+ regionFrames: frameRegionEntries(frame),
1511
+ pageLocalStories: frame.pageLocalStories.map(toPublicPageLocalStory),
1512
+ };
1513
+ }
1514
+
1515
+ function toPublicPageLocalStory(
1516
+ story: RuntimePageFrame["pageLocalStories"][number],
1517
+ ): PublicPageLocalStoryInstance {
1518
+ return {
1519
+ instanceId: story.instanceId,
1520
+ storyKey: story.storyKey,
1521
+ pageId: story.pageId,
1522
+ kind: story.kind,
1523
+ variant: story.variant,
1524
+ relationshipId: story.relationshipId,
1525
+ ...(story.sectionIndex !== undefined ? { sectionIndex: story.sectionIndex } : {}),
1526
+ anchoredObjects: story.anchoredObjects.map(toPublicStoryAnchoredObject),
1527
+ measuredFrameHeightTwips: story.measuredFrameHeightTwips,
1528
+ signature: story.signature,
1529
+ };
1530
+ }
1531
+
1532
+ function toPublicStoryAnchoredObject(
1533
+ object: RuntimeStoryAnchoredObject,
1534
+ ): PublicStoryAnchoredObject {
1535
+ return {
1536
+ objectId: object.objectId,
1537
+ sourceType: object.sourceType,
1538
+ display: object.display,
1539
+ ...(object.extentTwips
1540
+ ? {
1541
+ extentTwips: {
1542
+ widthTwips: object.extentTwips.widthTwips,
1543
+ heightTwips: object.extentTwips.heightTwips,
1544
+ },
1545
+ }
1546
+ : {}),
1547
+ ...(object.relationshipIds ? { relationshipIds: [...object.relationshipIds] } : {}),
1548
+ preserveOnly: object.preserveOnly,
1549
+ divergenceIds: [...object.divergenceIds],
1550
+ };
1551
+ }
1552
+
1553
+ function toPublicLayoutDivergence(
1554
+ divergence: RuntimeLayoutDivergence,
1555
+ ): PublicLayoutDivergence {
1556
+ return {
1557
+ divergenceId: divergence.divergenceId,
1558
+ kind: divergence.kind,
1559
+ source: divergence.source,
1560
+ severity: divergence.severity,
1561
+ message: divergence.message,
1562
+ ...(divergence.regionKinds !== undefined ? { regionKinds: [...divergence.regionKinds] } : {}),
1563
+ ...(divergence.fragmentIds !== undefined ? { fragmentIds: [...divergence.fragmentIds] } : {}),
1564
+ };
1565
+ }
1566
+
1376
1567
  function toPublicResolvedPageStories(
1377
1568
  stories: ResolvedPageStories,
1378
1569
  ): PublicResolvedPageStories {
@@ -1404,6 +1595,7 @@ function toPublicPageRegion(region: RuntimePageRegion): PublicPageRegion {
1404
1595
  widthTwips: region.widthTwips,
1405
1596
  heightTwips: region.heightTwips,
1406
1597
  fragmentCount: region.fragmentIds.length,
1598
+ ...(region.rectTwips ? { rectTwips: toPublicTwipsRect(region.rectTwips) } : {}),
1407
1599
  };
1408
1600
  }
1409
1601
 
@@ -1422,6 +1614,49 @@ function toPublicBlockFragment(
1422
1614
  to: fragment.to,
1423
1615
  heightTwips: fragment.heightTwips,
1424
1616
  orderInRegion: fragment.orderInRegion,
1617
+ ...(fragment.kind !== undefined ? { kind: fragment.kind } : {}),
1618
+ ...(fragment.paragraphLineRange !== undefined
1619
+ ? { paragraphLineRange: { ...fragment.paragraphLineRange } }
1620
+ : {}),
1621
+ ...(fragment.tableRowRange !== undefined
1622
+ ? { tableRowRange: { ...fragment.tableRowRange } }
1623
+ : {}),
1624
+ ...(fragment.columnIndex !== undefined ? { columnIndex: fragment.columnIndex } : {}),
1625
+ ...(fragment.continuation !== undefined
1626
+ ? { continuation: cloneContinuationCursor(fragment.continuation) }
1627
+ : {}),
1628
+ ...(fragment.layoutObject !== undefined
1629
+ ? { layoutObject: cloneFragmentLayoutObject(fragment.layoutObject) }
1630
+ : {}),
1631
+ };
1632
+ }
1633
+
1634
+ function cloneContinuationCursor(
1635
+ cursor: RuntimeLayoutContinuationCursor,
1636
+ ): RuntimeLayoutContinuationCursor {
1637
+ if (cursor.kind === "paragraph") {
1638
+ return {
1639
+ ...cursor,
1640
+ lineRange: { ...cursor.lineRange },
1641
+ };
1642
+ }
1643
+ return {
1644
+ ...cursor,
1645
+ rowRange: { ...cursor.rowRange },
1646
+ repeatedHeaderRowIndexes: [...cursor.repeatedHeaderRowIndexes],
1647
+ verticalMergeCarry: cursor.verticalMergeCarry.map((carry) => ({ ...carry })),
1648
+ };
1649
+ }
1650
+
1651
+ function cloneFragmentLayoutObject(
1652
+ layoutObject: NonNullable<RuntimeBlockFragment["layoutObject"]>,
1653
+ ): NonNullable<RuntimeBlockFragment["layoutObject"]> {
1654
+ return {
1655
+ ...layoutObject,
1656
+ measuredExtentTwips: { ...layoutObject.measuredExtentTwips },
1657
+ ...(layoutObject.fieldFamilies !== undefined
1658
+ ? { fieldFamilies: [...layoutObject.fieldFamilies] }
1659
+ : {}),
1425
1660
  };
1426
1661
  }
1427
1662
 
@@ -1440,6 +1675,7 @@ function toPublicNoteAllocation(note: RuntimeNoteAllocation): PublicNoteAllocati
1440
1675
  noteKind: note.noteKind,
1441
1676
  noteId: note.noteId,
1442
1677
  reservedHeightTwips: note.reservedHeightTwips,
1678
+ ...(note.fragmentId !== undefined ? { fragmentId: note.fragmentId } : {}),
1443
1679
  };
1444
1680
  }
1445
1681
 
@@ -3,6 +3,7 @@ import type {
3
3
  RuntimePageAnchor,
4
4
  RuntimePageFrame,
5
5
  RuntimePageGraph,
6
+ RuntimePageLocalStoryInstance,
6
7
  RuntimePageNode,
7
8
  RuntimePageRegion,
8
9
  RuntimePageRegions,
@@ -97,6 +98,9 @@ function rewriteFrame(
97
98
  return {
98
99
  ...frame,
99
100
  pageId: rewriteId(frame.pageId),
101
+ pageLocalStories: frame.pageLocalStories.map((story) =>
102
+ rewritePageLocalStoryInstance(story, rewriteId),
103
+ ),
100
104
  regions: {
101
105
  ...frame.regions,
102
106
  body: rewriteRegion(frame.regions.body, rewriteId),
@@ -116,6 +120,16 @@ function rewriteFrame(
116
120
  };
117
121
  }
118
122
 
123
+ function rewritePageLocalStoryInstance(
124
+ story: RuntimePageLocalStoryInstance,
125
+ rewriteId: (id: string) => string,
126
+ ): RuntimePageLocalStoryInstance {
127
+ return {
128
+ ...story,
129
+ pageId: rewriteId(story.pageId),
130
+ };
131
+ }
132
+
119
133
  function rewriteRegions(
120
134
  regions: RuntimePageRegions,
121
135
  rewriteId: (id: string) => string,
@@ -19,6 +19,7 @@ export * from "./scope-writer.ts";
19
19
  export * from "./metadata-writer.ts";
20
20
  export * from "./projector.ts";
21
21
  export * from "./overlay-store.ts";
22
+ export * from "./overlay-lanes.ts";
22
23
  export * from "./coordinator.ts";
23
24
  export * from "./visibility-policy.ts";
24
25
  export * from "./markup-mode-policy.ts";