@beyondwork/docx-react-component 1.0.109 → 1.0.111

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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +3 -0
  3. package/src/model/layout/page-graph-types.ts +33 -0
  4. package/src/model/layout/runtime-page-graph-types.ts +25 -0
  5. package/src/runtime/document-runtime.ts +46 -0
  6. package/src/runtime/geometry/adjacent-geometry-intake.ts +820 -15
  7. package/src/runtime/geometry/caret-geometry.ts +219 -7
  8. package/src/runtime/geometry/geometry-index.ts +52 -12
  9. package/src/runtime/geometry/object-handles.ts +42 -1
  10. package/src/runtime/layout/index.ts +3 -0
  11. package/src/runtime/layout/inert-layout-facet.ts +13 -0
  12. package/src/runtime/layout/layout-engine-instance.ts +233 -4
  13. package/src/runtime/layout/layout-engine-version.ts +47 -2
  14. package/src/runtime/layout/layout-facet-types.ts +3 -0
  15. package/src/runtime/layout/page-graph.ts +88 -7
  16. package/src/runtime/layout/paginated-layout-engine.ts +34 -0
  17. package/src/runtime/layout/project-block-fragments.ts +144 -1
  18. package/src/runtime/layout/public-facet.ts +228 -9
  19. package/src/runtime/layout/resolve-page-previews.ts +46 -8
  20. package/src/runtime/scopes/adjacent-geometry-evidence.ts +456 -0
  21. package/src/runtime/scopes/compile-scope-bundle.ts +8 -0
  22. package/src/runtime/scopes/evidence.ts +16 -0
  23. package/src/runtime/scopes/index.ts +13 -0
  24. package/src/runtime/scopes/semantic-scope-types.ts +67 -0
  25. package/src/ui-tailwind/chrome-overlay/tw-table-split-row-carry-overlay.tsx +62 -0
  26. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +104 -0
  27. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +50 -5
  28. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +27 -0
  29. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +62 -0
  30. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +1 -0
  31. package/src/README.md +0 -85
  32. package/src/api/README.md +0 -26
  33. package/src/api/v3/README.md +0 -91
  34. package/src/component-inventory.md +0 -99
  35. package/src/core/README.md +0 -10
  36. package/src/core/commands/README.md +0 -3
  37. package/src/core/schema/README.md +0 -3
  38. package/src/core/selection/README.md +0 -3
  39. package/src/core/state/README.md +0 -3
  40. package/src/io/README.md +0 -10
  41. package/src/io/export/README.md +0 -3
  42. package/src/io/normalize/README.md +0 -3
  43. package/src/io/ooxml/README.md +0 -3
  44. package/src/io/opc/README.md +0 -3
  45. package/src/model/README.md +0 -3
  46. package/src/preservation/README.md +0 -3
  47. package/src/review/README.md +0 -16
  48. package/src/review/store/README.md +0 -3
  49. package/src/runtime/README.md +0 -3
  50. package/src/ui/README.md +0 -30
  51. package/src/ui/comments/README.md +0 -3
  52. package/src/ui/compatibility/README.md +0 -3
  53. package/src/ui/editor-surface/README.md +0 -3
  54. package/src/ui/review/README.md +0 -3
  55. package/src/ui/status/README.md +0 -3
  56. package/src/ui/theme/README.md +0 -3
  57. package/src/ui/toolbar/README.md +0 -3
  58. package/src/ui-tailwind/debug/README.md +0 -22
  59. package/src/validation/README.md +0 -3
@@ -31,6 +31,16 @@ export interface PagePreviewMaps {
31
31
  footerPreviewByPageId: Map<string, string>;
32
32
  }
33
33
 
34
+ type PageLocalFieldLedger = readonly {
35
+ readonly family: string;
36
+ readonly displayText: string;
37
+ }[];
38
+
39
+ interface PagePreviewFieldState {
40
+ readonly ledger: PageLocalFieldLedger | undefined;
41
+ ordinal: number;
42
+ }
43
+
34
44
  export function buildPagePreviewMaps(
35
45
  graph: RuntimePageGraph,
36
46
  subParts: {
@@ -49,7 +59,10 @@ export function buildPagePreviewMaps(
49
59
  if (source) {
50
60
  headerPreviewByPageId.set(
51
61
  page.pageId,
52
- flattenBlocksToPreview(source.blocks, page, graph),
62
+ flattenBlocksToPreview(source.blocks, page, graph, {
63
+ ledger: findPageLocalStoryFieldLedger(page, headerStory),
64
+ ordinal: 0,
65
+ }),
53
66
  );
54
67
  }
55
68
  }
@@ -59,7 +72,10 @@ export function buildPagePreviewMaps(
59
72
  if (source) {
60
73
  footerPreviewByPageId.set(
61
74
  page.pageId,
62
- flattenBlocksToPreview(source.blocks, page, graph),
75
+ flattenBlocksToPreview(source.blocks, page, graph, {
76
+ ledger: findPageLocalStoryFieldLedger(page, footerStory),
77
+ ordinal: 0,
78
+ }),
63
79
  );
64
80
  }
65
81
  }
@@ -76,14 +92,30 @@ function findSubPart<T extends HeaderDocument | FooterDocument>(
76
92
  return parts.find((p) => p.relationshipId === relationshipId);
77
93
  }
78
94
 
95
+ function findPageLocalStoryFieldLedger(
96
+ page: RuntimePageNode,
97
+ story: NonNullable<RuntimePageNode["stories"]["header" | "footer"]>,
98
+ ): PageLocalFieldLedger | undefined {
99
+ const pageLocalStory = page.frame?.pageLocalStories.find(
100
+ (candidate) =>
101
+ candidate.kind === story.kind &&
102
+ candidate.relationshipId === story.relationshipId &&
103
+ candidate.variant === story.variant &&
104
+ (story.sectionIndex === undefined ||
105
+ candidate.sectionIndex === story.sectionIndex),
106
+ );
107
+ return pageLocalStory?.resolvedFields;
108
+ }
109
+
79
110
  function flattenBlocksToPreview(
80
111
  blocks: ReadonlyArray<BlockNode>,
81
112
  page: RuntimePageNode,
82
113
  graph: RuntimePageGraph,
114
+ fieldState: PagePreviewFieldState,
83
115
  ): string {
84
116
  const parts: string[] = [];
85
117
  for (const block of blocks) {
86
- collectBlockText(block, page, graph, parts);
118
+ collectBlockText(block, page, graph, fieldState, parts);
87
119
  if (joinPreview(parts).length >= MAX_PREVIEW_CHARS) break;
88
120
  }
89
121
  return truncate(joinPreview(parts));
@@ -93,26 +125,27 @@ function collectBlockText(
93
125
  block: BlockNode,
94
126
  page: RuntimePageNode,
95
127
  graph: RuntimePageGraph,
128
+ fieldState: PagePreviewFieldState,
96
129
  out: string[],
97
130
  ): void {
98
131
  switch (block.type) {
99
132
  case "paragraph":
100
133
  for (const child of block.children) {
101
- collectInlineText(child, page, graph, out);
134
+ collectInlineText(child, page, graph, fieldState, out);
102
135
  }
103
136
  break;
104
137
  case "table":
105
138
  for (const row of block.rows) {
106
139
  for (const cell of row.cells) {
107
140
  for (const childBlock of cell.children) {
108
- collectBlockText(childBlock, page, graph, out);
141
+ collectBlockText(childBlock, page, graph, fieldState, out);
109
142
  }
110
143
  }
111
144
  }
112
145
  break;
113
146
  case "sdt":
114
147
  for (const child of block.children) {
115
- collectBlockText(child, page, graph, out);
148
+ collectBlockText(child, page, graph, fieldState, out);
116
149
  }
117
150
  break;
118
151
  // opaque_block / section_break / custom_xml / alt_chunk — no preview text.
@@ -125,6 +158,7 @@ function collectInlineText(
125
158
  inline: InlineNode,
126
159
  page: RuntimePageNode,
127
160
  graph: RuntimePageGraph,
161
+ fieldState: PagePreviewFieldState,
128
162
  out: string[],
129
163
  ): void {
130
164
  switch (inline.type) {
@@ -141,8 +175,12 @@ function collectInlineText(
141
175
  const family = inline.fieldFamily ?? classifyFieldInstructionLocal(inline.instruction);
142
176
  const cached = flattenInline(inline.children);
143
177
  if (family === "PAGE" || family === "NUMPAGES" || family === "SECTIONPAGES") {
178
+ const ledgerField = fieldState.ledger?.[fieldState.ordinal];
179
+ fieldState.ordinal += 1;
144
180
  out.push(
145
- resolvePageFieldDisplayText(family, cached, { page, graph }),
181
+ ledgerField?.family === family
182
+ ? ledgerField.displayText
183
+ : resolvePageFieldDisplayText(family, cached, { page, graph }),
146
184
  );
147
185
  } else {
148
186
  out.push(cached);
@@ -151,7 +189,7 @@ function collectInlineText(
151
189
  }
152
190
  case "hyperlink":
153
191
  for (const child of inline.children) {
154
- collectInlineText(child, page, graph, out);
192
+ collectInlineText(child, page, graph, fieldState, out);
155
193
  }
156
194
  break;
157
195
  // bookmark_start/end, opaque_inline, note_ref etc — skip.
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Bounded adjacent geometry evidence from Layer 05.
3
+ *
4
+ * L08 consumes only compositor-ready `frame-px` rows from the L05 adjacent
5
+ * geometry intake. This evidence is read-side scope-bundle context; it is not
6
+ * replacement-envelope geometry and must not widen mutation capability.
7
+ */
8
+
9
+ import type { FieldEnumeratedScope, EnumeratedScope } from "./enumerate-scopes.ts";
10
+ import type {
11
+ ScopeAdjacentGeometryEvidence,
12
+ ScopeAdjacentGeometryFramePixelPoint,
13
+ ScopeAdjacentGeometryFramePixelRect,
14
+ ScopeAdjacentGeometryRowEvidence,
15
+ SemanticScope,
16
+ } from "./semantic-scope-types.ts";
17
+
18
+ interface FramePixelScaleLike {
19
+ readonly source?: unknown;
20
+ readonly renderFrameRevision?: unknown;
21
+ }
22
+
23
+ interface FramePixelProjectionLike {
24
+ readonly status?: unknown;
25
+ readonly coordinateSpace?: unknown;
26
+ readonly precision?: unknown;
27
+ readonly pageIndex?: unknown;
28
+ readonly pageId?: unknown;
29
+ readonly frameId?: unknown;
30
+ readonly scale?: FramePixelScaleLike;
31
+ readonly markerLane?: unknown;
32
+ readonly textColumn?: unknown;
33
+ readonly fieldStartAnchorPx?: unknown;
34
+ readonly fieldEndAnchorPx?: unknown;
35
+ readonly fieldResultRangePx?: unknown;
36
+ }
37
+
38
+ interface AdjacentPageLocalGeometryLike {
39
+ readonly compositorReady?: unknown;
40
+ readonly framePixelProjection?: FramePixelProjectionLike;
41
+ }
42
+
43
+ interface AdjacentNumberingRowLike {
44
+ readonly docId?: unknown;
45
+ readonly canonicalBlockId?: unknown;
46
+ readonly runtimeBlockId?: unknown;
47
+ readonly runtimeFragmentId?: unknown;
48
+ readonly runtimeNumberingId?: unknown;
49
+ readonly canonicalNumberingInstanceId?: unknown;
50
+ readonly canonicalLevel?: unknown;
51
+ readonly markerKind?: unknown;
52
+ readonly markerSuffix?: unknown;
53
+ readonly pageLocalGeometry?: AdjacentPageLocalGeometryLike;
54
+ readonly compositorReady?: unknown;
55
+ }
56
+
57
+ interface AdjacentFieldRegionRowLike {
58
+ readonly docId?: unknown;
59
+ readonly canonicalBlockId?: unknown;
60
+ readonly canonicalFieldId?: unknown;
61
+ readonly runtimeFieldRegionId?: unknown;
62
+ readonly runtimeFragmentId?: unknown;
63
+ readonly instructionFamily?: unknown;
64
+ readonly pageLocalGeometry?: AdjacentPageLocalGeometryLike;
65
+ readonly compositorReady?: unknown;
66
+ }
67
+
68
+ export interface AdjacentGeometryIntakeLike {
69
+ readonly schemaVersion?: unknown;
70
+ readonly pageLocalNormalization?: {
71
+ readonly framePixelCoordinateSpace?: unknown;
72
+ readonly compositorReady?: unknown;
73
+ };
74
+ readonly numberingRows?: readonly AdjacentNumberingRowLike[];
75
+ readonly fieldRegionRows?: readonly AdjacentFieldRegionRowLike[];
76
+ }
77
+
78
+ export interface ScopeAdjacentGeometryEvidenceProvider {
79
+ getScopeAdjacentGeometryEvidence(
80
+ scope: SemanticScope,
81
+ entry: EnumeratedScope | null,
82
+ ): ScopeAdjacentGeometryEvidence | null;
83
+ }
84
+
85
+ export interface ScopeAdjacentGeometryIntakeSummary {
86
+ readonly schemaVersion: string | null;
87
+ readonly intakeReady: boolean;
88
+ readonly numberingCompositorReadyRows: number;
89
+ readonly fieldRegionCompositorReadyRows: number;
90
+ readonly eligibleRowCount: number;
91
+ }
92
+
93
+ function stringValue(value: unknown): string | undefined {
94
+ return typeof value === "string" && value.length > 0 ? value : undefined;
95
+ }
96
+
97
+ function numberValue(value: unknown): number | undefined {
98
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
99
+ }
100
+
101
+ function parseParagraphBlockIndex(value: unknown): number | undefined {
102
+ const raw = stringValue(value);
103
+ if (!raw) return undefined;
104
+ const match = /^paragraph-(\d+)$/.exec(raw);
105
+ return match ? Number(match[1]) : undefined;
106
+ }
107
+
108
+ function docMatches(scope: SemanticScope, docId: string | undefined): boolean {
109
+ return !docId || !scope.handle.documentId || scope.handle.documentId === docId;
110
+ }
111
+
112
+ function isCompositorReady(geometry: AdjacentPageLocalGeometryLike | undefined): boolean {
113
+ const projection = geometry?.framePixelProjection;
114
+ return (
115
+ geometry?.compositorReady === true &&
116
+ projection?.status === "projected-frame-pixels" &&
117
+ projection.coordinateSpace === "frame-px" &&
118
+ projection.precision === "word-page-local-calibration"
119
+ );
120
+ }
121
+
122
+ function normalizeRect(value: unknown): ScopeAdjacentGeometryFramePixelRect | undefined {
123
+ const rect = value as
124
+ | {
125
+ readonly leftPx?: unknown;
126
+ readonly topPx?: unknown;
127
+ readonly widthPx?: unknown;
128
+ readonly heightPx?: unknown;
129
+ readonly coordinateSpace?: unknown;
130
+ }
131
+ | undefined;
132
+ const leftPx = numberValue(rect?.leftPx);
133
+ const topPx = numberValue(rect?.topPx);
134
+ const widthPx = numberValue(rect?.widthPx);
135
+ const heightPx = numberValue(rect?.heightPx);
136
+ if (
137
+ leftPx === undefined ||
138
+ topPx === undefined ||
139
+ widthPx === undefined ||
140
+ heightPx === undefined ||
141
+ rect?.coordinateSpace !== "frame-px"
142
+ ) {
143
+ return undefined;
144
+ }
145
+ return { leftPx, topPx, widthPx, heightPx, coordinateSpace: "frame-px" };
146
+ }
147
+
148
+ function normalizePoint(value: unknown): ScopeAdjacentGeometryFramePixelPoint | undefined {
149
+ const point = value as
150
+ | {
151
+ readonly xPx?: unknown;
152
+ readonly yPx?: unknown;
153
+ readonly coordinateSpace?: unknown;
154
+ }
155
+ | undefined;
156
+ const xPx = numberValue(point?.xPx);
157
+ const yPx = numberValue(point?.yPx);
158
+ if (xPx === undefined || yPx === undefined || point?.coordinateSpace !== "frame-px") {
159
+ return undefined;
160
+ }
161
+ return { xPx, yPx, coordinateSpace: "frame-px" };
162
+ }
163
+
164
+ function baseRow(
165
+ axis: ScopeAdjacentGeometryRowEvidence["axis"],
166
+ input: {
167
+ readonly docId?: string;
168
+ readonly canonicalBlockId?: string;
169
+ readonly runtimeFragmentId?: string;
170
+ readonly projection: FramePixelProjectionLike;
171
+ },
172
+ ): Omit<ScopeAdjacentGeometryRowEvidence, "numbering" | "fieldRegion"> {
173
+ return {
174
+ axis,
175
+ source: "l05-adjacent-geometry-intake",
176
+ ...(input.docId ? { docId: input.docId } : {}),
177
+ ...(input.canonicalBlockId ? { canonicalBlockId: input.canonicalBlockId } : {}),
178
+ ...(input.runtimeFragmentId ? { runtimeFragmentId: input.runtimeFragmentId } : {}),
179
+ ...(typeof input.projection.pageId === "string"
180
+ ? { pageId: input.projection.pageId }
181
+ : {}),
182
+ ...(typeof input.projection.frameId === "string"
183
+ ? { frameId: input.projection.frameId }
184
+ : {}),
185
+ ...(numberValue(input.projection.pageIndex) !== undefined
186
+ ? { pageIndex: numberValue(input.projection.pageIndex) }
187
+ : {}),
188
+ precision: "word-page-local-calibration",
189
+ coordinateSpace: "frame-px",
190
+ compositorReady: true,
191
+ ...(input.projection.scale?.source === "runtime-render-frame-page-rect"
192
+ ? { scaleSource: "runtime-render-frame-page-rect" as const }
193
+ : {}),
194
+ ...(numberValue(input.projection.scale?.renderFrameRevision) !== undefined
195
+ ? { renderFrameRevision: numberValue(input.projection.scale?.renderFrameRevision) }
196
+ : {}),
197
+ };
198
+ }
199
+
200
+ function projectNumberingRow(row: AdjacentNumberingRowLike): ScopeAdjacentGeometryRowEvidence | null {
201
+ if (row.compositorReady !== true || !isCompositorReady(row.pageLocalGeometry)) return null;
202
+ const projection = row.pageLocalGeometry!.framePixelProjection!;
203
+ const docId = stringValue(row.docId);
204
+ const canonicalBlockId = stringValue(row.canonicalBlockId) ?? stringValue(row.runtimeBlockId);
205
+ return {
206
+ ...baseRow("numbering-marker", {
207
+ docId,
208
+ canonicalBlockId,
209
+ runtimeFragmentId: stringValue(row.runtimeFragmentId),
210
+ projection,
211
+ }),
212
+ numbering: {
213
+ ...(stringValue(row.runtimeNumberingId)
214
+ ? { runtimeNumberingId: stringValue(row.runtimeNumberingId) }
215
+ : {}),
216
+ ...(stringValue(row.canonicalNumberingInstanceId)
217
+ ? { canonicalNumberingInstanceId: stringValue(row.canonicalNumberingInstanceId) }
218
+ : {}),
219
+ ...(numberValue(row.canonicalLevel) !== undefined
220
+ ? { canonicalLevel: numberValue(row.canonicalLevel) }
221
+ : {}),
222
+ ...(stringValue(row.markerKind) ? { markerKind: stringValue(row.markerKind) } : {}),
223
+ ...(stringValue(row.markerSuffix) ? { markerSuffix: stringValue(row.markerSuffix) } : {}),
224
+ ...(normalizeRect(projection.markerLane)
225
+ ? { markerLane: normalizeRect(projection.markerLane) }
226
+ : {}),
227
+ ...(normalizeRect(projection.textColumn)
228
+ ? { textColumn: normalizeRect(projection.textColumn) }
229
+ : {}),
230
+ },
231
+ };
232
+ }
233
+
234
+ function projectFieldRegionRow(
235
+ row: AdjacentFieldRegionRowLike,
236
+ ): ScopeAdjacentGeometryRowEvidence | null {
237
+ if (row.compositorReady !== true || !isCompositorReady(row.pageLocalGeometry)) return null;
238
+ const projection = row.pageLocalGeometry!.framePixelProjection!;
239
+ const docId = stringValue(row.docId);
240
+ const canonicalBlockId = stringValue(row.canonicalBlockId);
241
+ return {
242
+ ...baseRow("field-region", {
243
+ docId,
244
+ canonicalBlockId,
245
+ runtimeFragmentId: stringValue(row.runtimeFragmentId),
246
+ projection,
247
+ }),
248
+ fieldRegion: {
249
+ ...(stringValue(row.canonicalFieldId)
250
+ ? { canonicalFieldId: stringValue(row.canonicalFieldId) }
251
+ : {}),
252
+ ...(stringValue(row.runtimeFieldRegionId)
253
+ ? { runtimeFieldRegionId: stringValue(row.runtimeFieldRegionId) }
254
+ : {}),
255
+ ...(stringValue(row.instructionFamily)
256
+ ? { instructionFamily: stringValue(row.instructionFamily) }
257
+ : {}),
258
+ ...(normalizePoint(projection.fieldStartAnchorPx)
259
+ ? { fieldStartAnchorPx: normalizePoint(projection.fieldStartAnchorPx) }
260
+ : {}),
261
+ ...(normalizePoint(projection.fieldEndAnchorPx)
262
+ ? { fieldEndAnchorPx: normalizePoint(projection.fieldEndAnchorPx) }
263
+ : {}),
264
+ ...(normalizeRect(projection.fieldResultRangePx)
265
+ ? { fieldResultRangePx: normalizeRect(projection.fieldResultRangePx) }
266
+ : {}),
267
+ },
268
+ };
269
+ }
270
+
271
+ function numberingRowMatchesScope(
272
+ row: ScopeAdjacentGeometryRowEvidence,
273
+ scope: SemanticScope,
274
+ entry: EnumeratedScope | null,
275
+ ): boolean {
276
+ if (scope.kind !== "list-item" || entry?.kind !== "list-item") return false;
277
+ if (!docMatches(scope, row.docId)) return false;
278
+ const rowBlockIndex = parseParagraphBlockIndex(row.canonicalBlockId);
279
+ if (rowBlockIndex !== undefined && rowBlockIndex !== entry.blockIndex) return false;
280
+ const numbering = row.numbering;
281
+ if (
282
+ numbering?.canonicalNumberingInstanceId &&
283
+ scope.formatting.numbering?.numberingInstanceId &&
284
+ numbering.canonicalNumberingInstanceId !== scope.formatting.numbering.numberingInstanceId
285
+ ) {
286
+ return false;
287
+ }
288
+ if (
289
+ numbering?.canonicalLevel !== undefined &&
290
+ scope.formatting.numbering?.level !== undefined &&
291
+ numbering.canonicalLevel !== scope.formatting.numbering.level
292
+ ) {
293
+ return false;
294
+ }
295
+ return rowBlockIndex !== undefined;
296
+ }
297
+
298
+ function fieldRowMatchesScope(
299
+ row: ScopeAdjacentGeometryRowEvidence,
300
+ scope: SemanticScope,
301
+ entry: EnumeratedScope | null,
302
+ ): boolean {
303
+ if (scope.kind !== "field" || entry?.kind !== "field") return false;
304
+ if (!docMatches(scope, row.docId)) return false;
305
+ const fieldEntry = entry as FieldEnumeratedScope;
306
+ const rowCanonicalFieldId = row.fieldRegion?.canonicalFieldId;
307
+ if (rowCanonicalFieldId && fieldEntry.field.canonicalFieldId) {
308
+ return rowCanonicalFieldId === fieldEntry.field.canonicalFieldId;
309
+ }
310
+ const rowBlockIndex = parseParagraphBlockIndex(row.canonicalBlockId);
311
+ return rowBlockIndex !== undefined && rowBlockIndex === fieldEntry.blockIndex;
312
+ }
313
+
314
+ export function createAdjacentGeometryScopeEvidenceProvider(
315
+ intake: AdjacentGeometryIntakeLike,
316
+ ): ScopeAdjacentGeometryEvidenceProvider {
317
+ const intakeReady = isIntakeReady(intake);
318
+ const numberingRows = intakeReady
319
+ ? (intake.numberingRows ?? []).map(projectNumberingRow).filter(isEvidenceRow)
320
+ : [];
321
+ const fieldRegionRows = intakeReady
322
+ ? (intake.fieldRegionRows ?? []).map(projectFieldRegionRow).filter(isEvidenceRow)
323
+ : [];
324
+ const allRows = Object.freeze([...numberingRows, ...fieldRegionRows]);
325
+
326
+ return {
327
+ getScopeAdjacentGeometryEvidence(scope, entry) {
328
+ if (!intakeReady) {
329
+ return {
330
+ status: "unavailable",
331
+ source: "l05-adjacent-geometry-intake",
332
+ rowCount: 0,
333
+ reason: "l05-adjacent-intake-not-compositor-ready",
334
+ };
335
+ }
336
+ const rows = allRows.filter(
337
+ (row) =>
338
+ numberingRowMatchesScope(row, scope, entry) ||
339
+ fieldRowMatchesScope(row, scope, entry),
340
+ );
341
+ if (rows.length === 0) return null;
342
+ return {
343
+ status: "available",
344
+ source: "l05-adjacent-geometry-intake",
345
+ schemaVersion: "layer-05-adjacent-geometry-intake/v2",
346
+ rowCount: rows.length,
347
+ rows: Object.freeze(rows.map(cloneAdjacentGeometryRow)),
348
+ };
349
+ },
350
+ };
351
+ }
352
+
353
+ export function summarizeAdjacentGeometryEvidenceIntake(
354
+ intake: AdjacentGeometryIntakeLike,
355
+ ): ScopeAdjacentGeometryIntakeSummary {
356
+ const intakeReady = isIntakeReady(intake);
357
+ const numberingCompositorReadyRows = intakeReady
358
+ ? (intake.numberingRows ?? []).map(projectNumberingRow).filter(isEvidenceRow).length
359
+ : 0;
360
+ const fieldRegionCompositorReadyRows = intakeReady
361
+ ? (intake.fieldRegionRows ?? []).map(projectFieldRegionRow).filter(isEvidenceRow).length
362
+ : 0;
363
+ return {
364
+ schemaVersion: typeof intake.schemaVersion === "string" ? intake.schemaVersion : null,
365
+ intakeReady,
366
+ numberingCompositorReadyRows,
367
+ fieldRegionCompositorReadyRows,
368
+ eligibleRowCount: numberingCompositorReadyRows + fieldRegionCompositorReadyRows,
369
+ };
370
+ }
371
+
372
+ function isIntakeReady(intake: AdjacentGeometryIntakeLike): boolean {
373
+ return (
374
+ intake.schemaVersion === "layer-05-adjacent-geometry-intake/v2" &&
375
+ intake.pageLocalNormalization?.framePixelCoordinateSpace === "frame-px" &&
376
+ intake.pageLocalNormalization.compositorReady === true
377
+ );
378
+ }
379
+
380
+ function isEvidenceRow(
381
+ row: ScopeAdjacentGeometryRowEvidence | null,
382
+ ): row is ScopeAdjacentGeometryRowEvidence {
383
+ return row !== null;
384
+ }
385
+
386
+ function cloneRect(
387
+ rect: ScopeAdjacentGeometryFramePixelRect | undefined,
388
+ ): ScopeAdjacentGeometryFramePixelRect | undefined {
389
+ return rect ? { ...rect } : undefined;
390
+ }
391
+
392
+ function clonePoint(
393
+ point: ScopeAdjacentGeometryFramePixelPoint | undefined,
394
+ ): ScopeAdjacentGeometryFramePixelPoint | undefined {
395
+ return point ? { ...point } : undefined;
396
+ }
397
+
398
+ function cloneAdjacentGeometryRow(
399
+ row: ScopeAdjacentGeometryRowEvidence,
400
+ ): ScopeAdjacentGeometryRowEvidence {
401
+ return {
402
+ ...row,
403
+ ...(row.numbering
404
+ ? {
405
+ numbering: {
406
+ ...row.numbering,
407
+ ...(row.numbering.markerLane
408
+ ? { markerLane: cloneRect(row.numbering.markerLane) }
409
+ : {}),
410
+ ...(row.numbering.textColumn
411
+ ? { textColumn: cloneRect(row.numbering.textColumn) }
412
+ : {}),
413
+ },
414
+ }
415
+ : {}),
416
+ ...(row.fieldRegion
417
+ ? {
418
+ fieldRegion: {
419
+ ...row.fieldRegion,
420
+ ...(row.fieldRegion.fieldStartAnchorPx
421
+ ? { fieldStartAnchorPx: clonePoint(row.fieldRegion.fieldStartAnchorPx) }
422
+ : {}),
423
+ ...(row.fieldRegion.fieldEndAnchorPx
424
+ ? { fieldEndAnchorPx: clonePoint(row.fieldRegion.fieldEndAnchorPx) }
425
+ : {}),
426
+ ...(row.fieldRegion.fieldResultRangePx
427
+ ? { fieldResultRangePx: cloneRect(row.fieldRegion.fieldResultRangePx) }
428
+ : {}),
429
+ },
430
+ }
431
+ : {}),
432
+ };
433
+ }
434
+
435
+ export function deriveScopeAdjacentGeometryEvidence(
436
+ scope: SemanticScope,
437
+ entry: EnumeratedScope | null,
438
+ provider?: ScopeAdjacentGeometryEvidenceProvider,
439
+ ): ScopeAdjacentGeometryEvidence | undefined {
440
+ if (!provider) return undefined;
441
+ const evidence = provider.getScopeAdjacentGeometryEvidence(scope, entry);
442
+ if (!evidence) {
443
+ return {
444
+ status: "unavailable",
445
+ source: "l05-adjacent-geometry-intake",
446
+ rowCount: 0,
447
+ reason: "scope-adjacent-frame-pixel-row-unavailable",
448
+ };
449
+ }
450
+ return {
451
+ ...evidence,
452
+ ...(evidence.rows
453
+ ? { rows: Object.freeze(evidence.rows.map(cloneAdjacentGeometryRow)) }
454
+ : {}),
455
+ };
456
+ }
@@ -18,6 +18,7 @@ import type {
18
18
  WorkflowOverlay,
19
19
  } from "./_scope-dependencies.ts";
20
20
  import type { ScopeGeometryEvidenceProvider } from "./geometry-evidence.ts";
21
+ import type { ScopeAdjacentGeometryEvidenceProvider } from "./adjacent-geometry-evidence.ts";
21
22
 
22
23
  import {
23
24
  buildParagraphIndexMap,
@@ -59,6 +60,12 @@ export interface ScopeBundleInputs {
59
60
  * records layout as unavailable instead of deriving page slices in L08.
60
61
  */
61
62
  readonly layout?: ScopeLayoutEvidenceProvider;
63
+ /**
64
+ * Optional Layer-05 adjacent geometry intake seam for compositor-ready
65
+ * `frame-px` rows. Kept separate from replacement-envelope geometry so it
66
+ * cannot widen edit capability.
67
+ */
68
+ readonly adjacentGeometry?: ScopeAdjacentGeometryEvidenceProvider;
62
69
  }
63
70
 
64
71
  /**
@@ -140,6 +147,7 @@ export function compileScopeBundle(
140
147
  : {}),
141
148
  ...(inputs.geometry ? { geometry: inputs.geometry } : {}),
142
149
  ...(inputs.layout ? { layout: inputs.layout } : {}),
150
+ ...(inputs.adjacentGeometry ? { adjacentGeometry: inputs.adjacentGeometry } : {}),
143
151
  });
144
152
  return {
145
153
  scope,
@@ -24,6 +24,10 @@ import type {
24
24
  } from "./_scope-dependencies.ts";
25
25
 
26
26
  import { AI_EXPLANATION_METADATA_ID } from "./attach-explanation.ts";
27
+ import {
28
+ deriveScopeAdjacentGeometryEvidence,
29
+ type ScopeAdjacentGeometryEvidenceProvider,
30
+ } from "./adjacent-geometry-evidence.ts";
27
31
  import { deriveScopeCapabilities } from "./capabilities.ts";
28
32
  import { deriveScopeContentControlEvidence } from "./content-control-evidence.ts";
29
33
  import { AI_ISSUE_METADATA_ID } from "./create-issue.ts";
@@ -107,6 +111,12 @@ export interface EvidenceInputs {
107
111
  * rows are surfaced explicitly in `ScopeBundleEvidence.layout`.
108
112
  */
109
113
  readonly layout?: ScopeLayoutEvidenceProvider;
114
+ /**
115
+ * Optional Layer-05 adjacent geometry intake seam. This is bounded
116
+ * compositor/read evidence and does not participate in replacement
117
+ * capability derivation.
118
+ */
119
+ readonly adjacentGeometry?: ScopeAdjacentGeometryEvidenceProvider;
110
120
  }
111
121
 
112
122
  function normalizeSeverity(raw: unknown): AIIssueSummary["severity"] {
@@ -301,6 +311,11 @@ export function composeEvidence(inputs: EvidenceInputs): ScopeBundleEvidence {
301
311
 
302
312
  const layout = deriveScopeLayoutEvidence(scope.handle.scopeId, inputs.layout);
303
313
  const geometry = deriveScopeGeometryEvidence(scope.handle.scopeId, inputs.geometry);
314
+ const adjacentGeometry = deriveScopeAdjacentGeometryEvidence(
315
+ scope,
316
+ entry,
317
+ inputs.adjacentGeometry,
318
+ );
304
319
  const contentControls = deriveScopeContentControlEvidence(document, selfRange);
305
320
 
306
321
  return {
@@ -310,6 +325,7 @@ export function composeEvidence(inputs: EvidenceInputs): ScopeBundleEvidence {
310
325
  compatibilityFlags,
311
326
  layout,
312
327
  geometry,
328
+ ...(adjacentGeometry ? { adjacentGeometry } : {}),
313
329
  visualization: deriveScopeVisualization(scope),
314
330
  contentControls,
315
331
  capabilities: deriveScopeCapabilities(scope, {
@@ -52,6 +52,11 @@ export type {
52
52
  ScopeFormattingClearTarget,
53
53
  ScopeFormattingScope,
54
54
  ScopeActionPosture,
55
+ ScopeAdjacentGeometryAxis,
56
+ ScopeAdjacentGeometryEvidence,
57
+ ScopeAdjacentGeometryFramePixelPoint,
58
+ ScopeAdjacentGeometryFramePixelRect,
59
+ ScopeAdjacentGeometryRowEvidence,
55
60
  ScopeBundle,
56
61
  ScopeBundleEvidence,
57
62
  ScopeBundleNeighborhood,
@@ -73,6 +78,14 @@ export type {
73
78
  ValidationIssue,
74
79
  ValidationResult,
75
80
  } from "./semantic-scope-types.ts";
81
+ export {
82
+ createAdjacentGeometryScopeEvidenceProvider,
83
+ deriveScopeAdjacentGeometryEvidence,
84
+ summarizeAdjacentGeometryEvidenceIntake,
85
+ type AdjacentGeometryIntakeLike,
86
+ type ScopeAdjacentGeometryIntakeSummary,
87
+ type ScopeAdjacentGeometryEvidenceProvider,
88
+ } from "./adjacent-geometry-evidence.ts";
76
89
  export { deriveScopeCapabilities } from "./capabilities.ts";
77
90
  export type { ScopeCapabilityContext } from "./capabilities.ts";
78
91
  export {