@beyondwork/docx-react-component 1.0.104 → 1.0.105

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.
@@ -22,6 +22,7 @@ import {
22
22
  MAIN_STORY_KEY,
23
23
  type CanonicalAnchorLayoutInput,
24
24
  type CanonicalLayoutInputs,
25
+ type CanonicalScopeLayoutInput,
25
26
  type CanonicalTableLayoutInput,
26
27
  type CanonicalTableCellLayoutInput,
27
28
  type CanonicalTableRowLayoutInput,
@@ -43,12 +44,15 @@ import type {
43
44
  GeometryIndexRegion,
44
45
  GeometryIndexSlice,
45
46
  GeometryObjectHandleEntry,
47
+ GeometryPrecision,
46
48
  GeometryPrecisionCounts,
47
49
  GeometryRect,
50
+ GeometryRehydrationStatus,
48
51
  GeometryReplacementEnvelopeEntry,
49
52
  GeometrySourceIdentity,
50
53
  SemanticDisplayEntry,
51
54
  } from "./geometry-types.ts";
55
+ import { buildObjectHandleRectsFromRect } from "./object-handles.ts";
52
56
 
53
57
  export interface GeometryIndexProjectionOptions {
54
58
  readonly canonicalDocument?: CanonicalDocument | null;
@@ -86,7 +90,8 @@ export function projectGeometryIndexFromFrame(
86
90
  const hitTargets: GeometryHitTarget[] = [];
87
91
  const semanticEntries: SemanticDisplayEntry[] = [];
88
92
  const replacementEnvelopes: GeometryReplacementEnvelopeEntry[] = [];
89
- const objectHandles: GeometryObjectHandleEntry[] = [];
93
+ const objectHandleEntries = new Map<string, MutableObjectHandleEntry>();
94
+ const projectedBlocksByStory = new Map<string, ProjectedScopeBlock[]>();
90
95
  const precision = createPrecisionCounts();
91
96
 
92
97
  for (const page of frame.pages) {
@@ -95,7 +100,9 @@ export function projectGeometryIndexFromFrame(
95
100
 
96
101
  for (const [region, ordinal] of regionEntries) {
97
102
  const regionId = makeRegionId(page, region, ordinal);
98
- const storyKey = canonicalStoryKeyFromTarget(region.storyTarget);
103
+ const storyKey =
104
+ identities?.storyKeyForTarget(region.storyTarget) ??
105
+ canonicalStoryKeyFromTarget(region.storyTarget);
99
106
  regionIds.push(regionId);
100
107
  const sliceIds: string[] = [];
101
108
 
@@ -200,6 +207,25 @@ export function projectGeometryIndexFromFrame(
200
207
  ...(sliceIdentity ? { sourceIdentity: sliceIdentity } : {}),
201
208
  });
202
209
  recordPrecision(precision, "exact");
210
+ recordProjectedScopeBlock(projectedBlocksByStory, {
211
+ storyKey,
212
+ blockId: block.fragment.blockId,
213
+ blockPath: identities?.blockPathForBlockId(
214
+ storyKey,
215
+ block.fragment.blockId,
216
+ ),
217
+ pageId: page.page.pageId,
218
+ rect: toGeometryRect(block.frame),
219
+ });
220
+ appendCanonicalObjectHandleEntries({
221
+ frame,
222
+ page,
223
+ block,
224
+ identities,
225
+ storyKey,
226
+ entries: objectHandleEntries,
227
+ precision,
228
+ });
203
229
  appendBlockSemanticEntries({
204
230
  page,
205
231
  region,
@@ -209,6 +235,7 @@ export function projectGeometryIndexFromFrame(
209
235
  identities,
210
236
  storyKey,
211
237
  entries: semanticEntries,
238
+ projectedBlocksByStory,
212
239
  precision,
213
240
  });
214
241
  }
@@ -234,6 +261,14 @@ export function projectGeometryIndexFromFrame(
234
261
  recordPrecision(precision, "exact");
235
262
  }
236
263
 
264
+ appendScopeReplacementEnvelopeEntries({
265
+ identities,
266
+ projectedBlocksByStory,
267
+ entries: replacementEnvelopes,
268
+ precision,
269
+ });
270
+
271
+ const objectHandles = finalizeObjectHandleEntries(objectHandleEntries);
237
272
  const coverage: GeometryIndexCoverage = {
238
273
  status: "realized",
239
274
  pageCount: pages.length,
@@ -336,6 +371,7 @@ function appendBlockSemanticEntries(input: {
336
371
  identities: GeometryIdentityLookup | null;
337
372
  storyKey: string;
338
373
  entries: SemanticDisplayEntry[];
374
+ projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
339
375
  precision: GeometryPrecisionCounts;
340
376
  }): void {
341
377
  const {
@@ -347,6 +383,7 @@ function appendBlockSemanticEntries(input: {
347
383
  identities,
348
384
  storyKey,
349
385
  entries,
386
+ projectedBlocksByStory,
350
387
  precision,
351
388
  } = input;
352
389
  const tableIdentity = identities?.tableIdentity(
@@ -379,6 +416,16 @@ function appendBlockSemanticEntries(input: {
379
416
 
380
417
  const plan = block.tablePlan;
381
418
  if (!plan) return;
419
+ if (tableIdentity) {
420
+ recordTableCellScopeBlocks({
421
+ table: tableIdentity,
422
+ block,
423
+ pageId: page.page.pageId,
424
+ storyKey,
425
+ identities,
426
+ projectedBlocksByStory,
427
+ });
428
+ }
382
429
  const rowCount = resolveTableRowCount(block);
383
430
  const rowHeightPx =
384
431
  rowCount > 0 ? block.frame.heightPx / rowCount : block.frame.heightPx;
@@ -551,9 +598,14 @@ function pageFrameCellRect(
551
598
  }
552
599
 
553
600
  interface GeometryIdentityLookup {
601
+ storyKeyForTarget(target: EditorStoryTarget): string;
554
602
  sliceIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
555
603
  tableIdentity(storyKey: string, blockId: string): CanonicalTableLayoutInput | undefined;
556
604
  anchorIdentity(storyKey: string, blockId: string): GeometrySourceIdentity | undefined;
605
+ objectAnchors(storyKey: string, blockId: string): readonly CanonicalAnchorLayoutInput[];
606
+ blockIdForPath(storyKey: string, blockPath: string): string | undefined;
607
+ blockPathForBlockId(storyKey: string, blockId: string): string | undefined;
608
+ scopeInputs(): readonly CanonicalScopeLayoutInput[];
557
609
  }
558
610
 
559
611
  function createIdentityLookup(
@@ -566,6 +618,7 @@ function createIdentityLookup(
566
618
  : null);
567
619
  if (!layoutInputs) return null;
568
620
 
621
+ const storyKeys = new Set(layoutInputs.stories.map((story) => story.storyKey));
569
622
  const blockIdByPath = options?.canonicalDocument
570
623
  ? collectProjectedBlockIdsByPath(options.canonicalDocument)
571
624
  : new Map<string, string>();
@@ -603,6 +656,9 @@ function createIdentityLookup(
603
656
  }
604
657
 
605
658
  return {
659
+ storyKeyForTarget(target) {
660
+ return resolveCanonicalStoryKeyForTarget(target, storyKeys);
661
+ },
606
662
  sliceIdentity(storyKey, blockId) {
607
663
  const table = tableByStoryBlockId.get(storyBlockKey(storyKey, blockId));
608
664
  if (table) return tableSourceIdentity(table);
@@ -627,16 +683,337 @@ function createIdentityLookup(
627
683
  };
628
684
  }
629
685
  const anchor = anchors[0]!;
630
- return {
631
- storyKey,
632
- blockPath: anchor.blockPath,
633
- objectKey: anchor.objectKey,
634
- inlinePath: anchor.inlinePath,
635
- objectKind: anchor.objectKind,
636
- editPosture: anchor.editPosture,
637
- joinKind: "block-scoped",
638
- };
686
+ return anchorSourceIdentity(anchor, "block-scoped");
687
+ },
688
+ objectAnchors(storyKey, blockId) {
689
+ return anchorsByStoryBlockId.get(storyBlockKey(storyKey, blockId)) ?? [];
690
+ },
691
+ blockIdForPath(storyKey, blockPath) {
692
+ return blockIdByPath.get(storyPathKey(storyKey, blockPath));
693
+ },
694
+ blockPathForBlockId(storyKey, blockId) {
695
+ return blockPathByStoryBlockId.get(storyBlockKey(storyKey, blockId));
639
696
  },
697
+ scopeInputs() {
698
+ return layoutInputs.scopes;
699
+ },
700
+ };
701
+ }
702
+
703
+ interface MutableObjectHandleEntry {
704
+ objectId: string;
705
+ pageIds: string[];
706
+ rects: GeometryRect[];
707
+ status: GeometryRehydrationStatus;
708
+ precision: GeometryPrecision;
709
+ sourceIdentity?: GeometrySourceIdentity;
710
+ }
711
+
712
+ function appendCanonicalObjectHandleEntries(input: {
713
+ frame: RenderFrame;
714
+ page: RenderPage;
715
+ block: RenderBlock;
716
+ identities: GeometryIdentityLookup | null;
717
+ storyKey: string;
718
+ entries: Map<string, MutableObjectHandleEntry>;
719
+ precision: GeometryPrecisionCounts;
720
+ }): void {
721
+ const { frame, page, block, identities, storyKey, entries, precision } = input;
722
+ if (!identities) return;
723
+
724
+ for (const anchor of identities.objectAnchors(
725
+ storyKey,
726
+ block.fragment.blockId,
727
+ )) {
728
+ if (anchor.hidden === true) continue;
729
+ const exactObjectRect = frame.anchorIndex.byObjectId(anchor.objectKey);
730
+ const rect = exactObjectRect ?? block.frame;
731
+ const entryPrecision: GeometryPrecision = exactObjectRect
732
+ ? "within-tolerance"
733
+ : "heuristic";
734
+ const status: GeometryRehydrationStatus = exactObjectRect
735
+ ? "realized"
736
+ : "requires-rehydration";
737
+ const handleRects = buildObjectHandleRectsFromRect(rect, entryPrecision);
738
+ const existing = entries.get(anchor.objectKey);
739
+ if (existing) {
740
+ appendUnique(existing.pageIds, page.page.pageId);
741
+ existing.rects.push(...handleRects);
742
+ if (existing.precision !== "heuristic" && entryPrecision === "heuristic") {
743
+ existing.precision = "heuristic";
744
+ existing.status = "requires-rehydration";
745
+ existing.sourceIdentity = anchorSourceIdentity(anchor, "block-scoped");
746
+ }
747
+ continue;
748
+ }
749
+
750
+ entries.set(anchor.objectKey, {
751
+ objectId: anchor.objectKey,
752
+ pageIds: [page.page.pageId],
753
+ rects: [...handleRects],
754
+ status,
755
+ precision: entryPrecision,
756
+ sourceIdentity: anchorSourceIdentity(
757
+ anchor,
758
+ exactObjectRect ? "direct" : "block-scoped",
759
+ ),
760
+ });
761
+ recordPrecision(precision, entryPrecision);
762
+ }
763
+ }
764
+
765
+ function finalizeObjectHandleEntries(
766
+ entries: ReadonlyMap<string, MutableObjectHandleEntry>,
767
+ ): GeometryObjectHandleEntry[] {
768
+ return Array.from(entries.values()).map((entry) => ({
769
+ objectId: entry.objectId,
770
+ pageIds: entry.pageIds,
771
+ rects: entry.rects,
772
+ status: entry.status,
773
+ precision: entry.precision,
774
+ ...(entry.sourceIdentity ? { sourceIdentity: entry.sourceIdentity } : {}),
775
+ }));
776
+ }
777
+
778
+ function appendUnique(target: string[], value: string): void {
779
+ if (!target.includes(value)) target.push(value);
780
+ }
781
+
782
+ function resolveCanonicalStoryKeyForTarget(
783
+ target: EditorStoryTarget,
784
+ storyKeys: ReadonlySet<string>,
785
+ ): string {
786
+ const exact = canonicalStoryKeyFromTarget(target);
787
+ if (storyKeys.has(exact)) return exact;
788
+ if (
789
+ (target.kind === "header" || target.kind === "footer") &&
790
+ target.sectionIndex !== undefined
791
+ ) {
792
+ const unscoped = createHeaderFooterStoryKey({
793
+ kind: target.kind,
794
+ relationshipId: target.relationshipId,
795
+ variant: target.variant,
796
+ });
797
+ if (storyKeys.has(unscoped)) return unscoped;
798
+ }
799
+ return exact;
800
+ }
801
+
802
+ interface ProjectedScopeBlock {
803
+ readonly storyKey: string;
804
+ readonly blockId: string;
805
+ readonly blockPath?: string;
806
+ readonly pageId: string;
807
+ readonly rect: GeometryRect;
808
+ }
809
+
810
+ function recordProjectedScopeBlock(
811
+ blocksByStory: Map<string, ProjectedScopeBlock[]>,
812
+ block: ProjectedScopeBlock,
813
+ ): void {
814
+ const list = blocksByStory.get(block.storyKey);
815
+ if (list) {
816
+ list.push(block);
817
+ } else {
818
+ blocksByStory.set(block.storyKey, [block]);
819
+ }
820
+ }
821
+
822
+ function recordTableCellScopeBlocks(input: {
823
+ table: CanonicalTableLayoutInput;
824
+ block: RenderBlock;
825
+ pageId: string;
826
+ storyKey: string;
827
+ identities: GeometryIdentityLookup | null;
828
+ projectedBlocksByStory: Map<string, ProjectedScopeBlock[]>;
829
+ }): void {
830
+ const {
831
+ table,
832
+ block,
833
+ pageId,
834
+ storyKey,
835
+ identities,
836
+ projectedBlocksByStory,
837
+ } = input;
838
+ if (!identities) return;
839
+
840
+ for (const row of table.rows) {
841
+ for (const cell of row.cells) {
842
+ const rect = pageFrameCellRect(block, row.rowIndex, cell.gridColumnStart);
843
+ if (!rect) continue;
844
+ for (let blockIndex = 0; blockIndex < cell.blockCount; blockIndex += 1) {
845
+ const blockPath =
846
+ `${table.blockPath}/row[${row.rowIndex}]/cell[${cell.cellIndex}]` +
847
+ `/block[${blockIndex}]`;
848
+ const blockId = identities.blockIdForPath(storyKey, blockPath);
849
+ if (!blockId) continue;
850
+ recordProjectedScopeBlock(projectedBlocksByStory, {
851
+ storyKey,
852
+ blockId,
853
+ blockPath,
854
+ pageId,
855
+ rect: {
856
+ ...toGeometryRect(rect),
857
+ precision: "within-tolerance",
858
+ },
859
+ });
860
+ }
861
+ }
862
+ }
863
+ }
864
+
865
+ function appendScopeReplacementEnvelopeEntries(input: {
866
+ identities: GeometryIdentityLookup | null;
867
+ projectedBlocksByStory: ReadonlyMap<string, readonly ProjectedScopeBlock[]>;
868
+ entries: GeometryReplacementEnvelopeEntry[];
869
+ precision: GeometryPrecisionCounts;
870
+ }): void {
871
+ const { identities, projectedBlocksByStory, entries, precision } = input;
872
+ if (!identities) return;
873
+
874
+ for (const scope of identities.scopeInputs()) {
875
+ const entry = projectScopeReplacementEnvelopeEntry(
876
+ scope,
877
+ identities,
878
+ projectedBlocksByStory.get(scope.storyKey) ?? [],
879
+ );
880
+ entries.push(entry);
881
+ recordPrecision(precision, entry.precision);
882
+ }
883
+ }
884
+
885
+ function projectScopeReplacementEnvelopeEntry(
886
+ scope: CanonicalScopeLayoutInput,
887
+ identities: GeometryIdentityLookup,
888
+ storyBlocks: readonly ProjectedScopeBlock[],
889
+ ): GeometryReplacementEnvelopeEntry {
890
+ const startBlockId = scope.start
891
+ ? identities.blockIdForPath(scope.storyKey, scope.start.blockPath)
892
+ : undefined;
893
+ const endBlockId = scope.end
894
+ ? identities.blockIdForPath(scope.storyKey, scope.end.blockPath)
895
+ : undefined;
896
+
897
+ let blocks: readonly ProjectedScopeBlock[] = [];
898
+ let status: GeometryReplacementEnvelopeEntry["status"] = "unavailable";
899
+ let precision: GeometryReplacementEnvelopeEntry["precision"] = "heuristic";
900
+
901
+ if (scope.status === "paired" && startBlockId && endBlockId) {
902
+ blocks = blocksInCanonicalPathRange(scope, storyBlocks);
903
+ if (blocks.length === 0) {
904
+ const startIndex = firstBlockIndex(storyBlocks, startBlockId);
905
+ const endIndex = lastBlockIndex(storyBlocks, endBlockId);
906
+ if (startIndex >= 0 && endIndex >= 0) {
907
+ const from = Math.min(startIndex, endIndex);
908
+ const to = Math.max(startIndex, endIndex);
909
+ blocks = storyBlocks.slice(from, to + 1);
910
+ }
911
+ }
912
+ if (blocks.length > 0) {
913
+ status = "realized";
914
+ precision = "within-tolerance";
915
+ }
916
+ }
917
+
918
+ if (blocks.length === 0) {
919
+ const fallbackBlockId = startBlockId ?? endBlockId;
920
+ if (fallbackBlockId) {
921
+ blocks = storyBlocks.filter((block) => block.blockId === fallbackBlockId);
922
+ if (blocks.length > 0) status = "requires-rehydration";
923
+ }
924
+ }
925
+
926
+ return {
927
+ scopeId: scope.scopeId,
928
+ pageIds: uniquePageIds(blocks),
929
+ rects: blocks.map((block) => ({
930
+ ...block.rect,
931
+ precision,
932
+ })),
933
+ status,
934
+ precision,
935
+ sourceIdentity: scopeSourceIdentity(scope),
936
+ };
937
+ }
938
+
939
+ function blocksInCanonicalPathRange(
940
+ scope: CanonicalScopeLayoutInput,
941
+ storyBlocks: readonly ProjectedScopeBlock[],
942
+ ): readonly ProjectedScopeBlock[] {
943
+ if (!scope.start || !scope.end) return [];
944
+ const orderByPath = new Map<string, number>();
945
+ for (const block of storyBlocks) {
946
+ if (!block.blockPath || orderByPath.has(block.blockPath)) continue;
947
+ orderByPath.set(block.blockPath, orderByPath.size);
948
+ }
949
+ const startOrder = orderByPath.get(scope.start.blockPath);
950
+ const endOrder = orderByPath.get(scope.end.blockPath);
951
+ if (startOrder === undefined || endOrder === undefined) return [];
952
+
953
+ const from = Math.min(startOrder, endOrder);
954
+ const to = Math.max(startOrder, endOrder);
955
+ return storyBlocks.filter((block) => {
956
+ if (!block.blockPath) return false;
957
+ const order = orderByPath.get(block.blockPath);
958
+ return order !== undefined && order >= from && order <= to;
959
+ });
960
+ }
961
+
962
+ function firstBlockIndex(
963
+ blocks: readonly ProjectedScopeBlock[],
964
+ blockId: string,
965
+ ): number {
966
+ return blocks.findIndex((block) => block.blockId === blockId);
967
+ }
968
+
969
+ function lastBlockIndex(
970
+ blocks: readonly ProjectedScopeBlock[],
971
+ blockId: string,
972
+ ): number {
973
+ for (let index = blocks.length - 1; index >= 0; index -= 1) {
974
+ if (blocks[index]?.blockId === blockId) return index;
975
+ }
976
+ return -1;
977
+ }
978
+
979
+ function uniquePageIds(
980
+ blocks: readonly ProjectedScopeBlock[],
981
+ ): readonly string[] {
982
+ const ids: string[] = [];
983
+ const seen = new Set<string>();
984
+ for (const block of blocks) {
985
+ if (seen.has(block.pageId)) continue;
986
+ seen.add(block.pageId);
987
+ ids.push(block.pageId);
988
+ }
989
+ return ids;
990
+ }
991
+
992
+ function scopeSourceIdentity(
993
+ scope: CanonicalScopeLayoutInput,
994
+ ): GeometrySourceIdentity {
995
+ const marker = scope.start ?? scope.end;
996
+ return {
997
+ storyKey: scope.storyKey,
998
+ ...(marker ? { blockPath: marker.blockPath } : {}),
999
+ scopeKey: scope.scopeKey,
1000
+ scopeId: scope.scopeId,
1001
+ joinKind: "block-scoped",
1002
+ };
1003
+ }
1004
+
1005
+ function anchorSourceIdentity(
1006
+ anchor: CanonicalAnchorLayoutInput,
1007
+ joinKind: GeometrySourceIdentity["joinKind"],
1008
+ ): GeometrySourceIdentity {
1009
+ return {
1010
+ storyKey: anchor.storyKey,
1011
+ blockPath: anchor.blockPath,
1012
+ objectKey: anchor.objectKey,
1013
+ inlinePath: anchor.inlinePath,
1014
+ objectKind: anchor.objectKind,
1015
+ editPosture: anchor.editPosture,
1016
+ joinKind,
640
1017
  };
641
1018
  }
642
1019
 
@@ -147,6 +147,8 @@ export interface GeometrySourceIdentity {
147
147
  tableKey?: string;
148
148
  rowKey?: string;
149
149
  cellKey?: string;
150
+ scopeKey?: string;
151
+ scopeId?: string;
150
152
  objectKey?: string;
151
153
  inlinePath?: string;
152
154
  objectKind?: string;
@@ -288,16 +290,20 @@ export interface SemanticDisplayEntry {
288
290
 
289
291
  export interface GeometryReplacementEnvelopeEntry {
290
292
  scopeId: string;
293
+ pageIds?: readonly string[];
291
294
  rects: readonly GeometryRect[];
292
295
  status: GeometryRehydrationStatus;
293
296
  precision: GeometryPrecision;
297
+ sourceIdentity?: GeometrySourceIdentity;
294
298
  }
295
299
 
296
300
  export interface GeometryObjectHandleEntry {
297
301
  objectId: string;
302
+ pageIds?: readonly string[];
298
303
  rects: readonly GeometryRect[];
299
304
  status: GeometryRehydrationStatus;
300
305
  precision: GeometryPrecision;
306
+ sourceIdentity?: GeometrySourceIdentity;
301
307
  }
302
308
 
303
309
  // ---------------------------------------------------------------------------
@@ -27,7 +27,7 @@
27
27
  */
28
28
 
29
29
  import type { RenderFrame, RenderFrameRect } from "../render/index.ts";
30
- import type { GeometryRect } from "./geometry-types.ts";
30
+ import type { GeometryPrecision, GeometryRect } from "./geometry-types.ts";
31
31
 
32
32
  const ROTATE_HANDLE_OFFSET_PX = 20;
33
33
 
@@ -38,10 +38,13 @@ export function resolveObjectHandles(
38
38
  if (!frame) return [];
39
39
  const bbox = frame.anchorIndex.byObjectId(objectId);
40
40
  if (!bbox) return [];
41
- return buildHandles(bbox);
41
+ return buildObjectHandleRectsFromRect(bbox);
42
42
  }
43
43
 
44
- function buildHandles(bbox: RenderFrameRect): readonly GeometryRect[] {
44
+ export function buildObjectHandleRectsFromRect(
45
+ bbox: RenderFrameRect,
46
+ precision: GeometryPrecision = "heuristic",
47
+ ): readonly GeometryRect[] {
45
48
  const { leftPx, topPx, widthPx, heightPx } = bbox;
46
49
  const right = leftPx + widthPx;
47
50
  const bottom = topPx + heightPx;
@@ -61,7 +64,7 @@ function buildHandles(bbox: RenderFrameRect): readonly GeometryRect[] {
61
64
  widthPx: 0,
62
65
  heightPx: 0,
63
66
  space: "frame",
64
- precision: "heuristic",
67
+ precision,
65
68
  });
66
69
  return [
67
70
  point(leftPx, topPx), // 0 top-left
@@ -490,6 +490,7 @@ export function createLayoutEngine(
490
490
  fragmentsByPageIndex,
491
491
  lineBoxesByPageIndex,
492
492
  noteAllocationsByPageIndex: pageStack.noteAllocationsByPageIndex,
493
+ subParts: document.subParts,
493
494
  });
494
495
 
495
496
  // Field dirtiness diff from previous graph
@@ -669,6 +670,7 @@ export function createLayoutEngine(
669
670
  fragmentsByPageIndex: freshFragmentsByPageIndex,
670
671
  lineBoxesByPageIndex: freshLineBoxesByPageIndex,
671
672
  noteAllocationsByPageIndex: freshResult.noteAllocationsByPageIndex,
673
+ subParts: document.subParts,
672
674
  });
673
675
  const freshNodes = freshGraph.pages;
674
676
 
@@ -1046,8 +1046,36 @@
1046
1046
  * Existing `regions` remain the compatibility surface, but cache envelopes
1047
1047
  * from v63 invalidate because the page graph payload shape changed.
1048
1048
  * Shipped via pe2 commit `24a316af2`.
1049
+ *
1050
+ * 65 — PE2 Slice 2 page-local story instances (Layer 04). `RuntimePageFrame`
1051
+ * now carries stable header/footer `pageLocalStories` records with story
1052
+ * keys, variants, relationship ids, measured frame heights, and empty
1053
+ * field/object ledgers for later Slice 2/4 population. Cache envelopes
1054
+ * from v64 invalidate because the page-frame payload shape changed again.
1055
+ * Shipped via pe2 commit `06fe6f692`.
1056
+ *
1057
+ * 66 — PE2 Slice 2 page-local field ledgers (Layer 04). Page graph
1058
+ * construction now walks canonical header/footer subparts for each
1059
+ * page-local story instance and records resolved PAGE / NUMPAGES /
1060
+ * SECTIONPAGES display text in the story's `resolvedFields` ledger.
1061
+ * Cache envelopes from v65 invalidate because page-frame story payloads
1062
+ * now carry field-resolution data. Shipped via pe2 commit `a3a42bec9`.
1063
+ *
1064
+ * 67 — PE2 Slice 2 page-local object ledgers (Layer 04). Header/footer
1065
+ * page-local story instances now record canonical image/drawing/preserve-only
1066
+ * objects as twips/plain anchored-object ledger rows and emit typed
1067
+ * unsupported-wrap / preserve-only-placeholder divergences. Cache envelopes
1068
+ * from v66 invalidate because page-frame story payloads and divergence ids
1069
+ * can change. Shipped via pe2 commit `7d8bb94ac`.
1070
+ *
1071
+ * 68 — PE2 Slice 3 typed continuation cursors (Layer 04). Paragraph and
1072
+ * table slice fragments now carry a twips/plain continuation cursor with
1073
+ * sequence position, line/row range, continuation direction, repeated
1074
+ * table header rows, and vertical-merge carry metadata. Cache envelopes
1075
+ * from v67 invalidate because fragment payloads can now include
1076
+ * continuation state. Shipped via pe2 commit `33fbf45ac`.
1049
1077
  */
1050
- export const LAYOUT_ENGINE_VERSION = 64 as const;
1078
+ export const LAYOUT_ENGINE_VERSION = 68 as const;
1051
1079
 
1052
1080
  /**
1053
1081
  * Serialization schema version for the LayCache payload (the cache envelope