@beyondwork/docx-react-component 1.0.110 → 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 (52) 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/runtime/geometry/adjacent-geometry-intake.ts +373 -5
  5. package/src/runtime/geometry/caret-geometry.ts +219 -7
  6. package/src/runtime/geometry/geometry-index.ts +35 -10
  7. package/src/runtime/geometry/object-handles.ts +42 -1
  8. package/src/runtime/layout/index.ts +3 -0
  9. package/src/runtime/layout/inert-layout-facet.ts +13 -0
  10. package/src/runtime/layout/layout-engine-instance.ts +2 -0
  11. package/src/runtime/layout/layout-engine-version.ts +32 -2
  12. package/src/runtime/layout/layout-facet-types.ts +3 -0
  13. package/src/runtime/layout/page-graph.ts +81 -7
  14. package/src/runtime/layout/project-block-fragments.ts +144 -1
  15. package/src/runtime/layout/public-facet.ts +160 -0
  16. package/src/runtime/scopes/adjacent-geometry-evidence.ts +456 -0
  17. package/src/runtime/scopes/compile-scope-bundle.ts +8 -0
  18. package/src/runtime/scopes/evidence.ts +16 -0
  19. package/src/runtime/scopes/index.ts +13 -0
  20. package/src/runtime/scopes/semantic-scope-types.ts +67 -0
  21. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +104 -0
  22. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +50 -5
  23. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +26 -0
  24. package/src/README.md +0 -85
  25. package/src/api/README.md +0 -26
  26. package/src/api/v3/README.md +0 -91
  27. package/src/component-inventory.md +0 -99
  28. package/src/core/README.md +0 -10
  29. package/src/core/commands/README.md +0 -3
  30. package/src/core/schema/README.md +0 -3
  31. package/src/core/selection/README.md +0 -3
  32. package/src/core/state/README.md +0 -3
  33. package/src/io/README.md +0 -10
  34. package/src/io/export/README.md +0 -3
  35. package/src/io/normalize/README.md +0 -3
  36. package/src/io/ooxml/README.md +0 -3
  37. package/src/io/opc/README.md +0 -3
  38. package/src/model/README.md +0 -3
  39. package/src/preservation/README.md +0 -3
  40. package/src/review/README.md +0 -16
  41. package/src/review/store/README.md +0 -3
  42. package/src/runtime/README.md +0 -3
  43. package/src/ui/README.md +0 -30
  44. package/src/ui/comments/README.md +0 -3
  45. package/src/ui/compatibility/README.md +0 -3
  46. package/src/ui/editor-surface/README.md +0 -3
  47. package/src/ui/review/README.md +0 -3
  48. package/src/ui/status/README.md +0 -3
  49. package/src/ui/theme/README.md +0 -3
  50. package/src/ui/toolbar/README.md +0 -3
  51. package/src/ui-tailwind/debug/README.md +0 -22
  52. package/src/validation/README.md +0 -3
@@ -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 {
@@ -289,6 +289,12 @@ export interface ScopeBundleEvidence {
289
289
  * `requires-rehydration` or `unavailable`; Layer 08 never fabricates rects.
290
290
  */
291
291
  readonly geometry?: ScopeGeometryEvidence;
292
+ /**
293
+ * Bounded Layer-05 adjacent geometry evidence. This is compositor/read
294
+ * evidence only; it does not make a scope replaceable. Rows are present only
295
+ * when L05 marked them `compositorReady` in `frame-px` space.
296
+ */
297
+ readonly adjacentGeometry?: ScopeAdjacentGeometryEvidence;
292
298
  /**
293
299
  * Presentation hint only. Consumers may use this to choose a cheap inline
294
300
  * treatment for field-like scopes versus a broader overlay treatment for
@@ -449,6 +455,67 @@ export interface ScopeGeometryEvidence {
449
455
  };
450
456
  }
451
457
 
458
+ export type ScopeAdjacentGeometryAxis = "numbering-marker" | "field-region";
459
+
460
+ export interface ScopeAdjacentGeometryFramePixelRect {
461
+ readonly leftPx: number;
462
+ readonly topPx: number;
463
+ readonly widthPx: number;
464
+ readonly heightPx: number;
465
+ readonly coordinateSpace: "frame-px";
466
+ }
467
+
468
+ export interface ScopeAdjacentGeometryFramePixelPoint {
469
+ readonly xPx: number;
470
+ readonly yPx: number;
471
+ readonly coordinateSpace: "frame-px";
472
+ }
473
+
474
+ export interface ScopeAdjacentGeometryRowEvidence {
475
+ readonly axis: ScopeAdjacentGeometryAxis;
476
+ readonly source: "l05-adjacent-geometry-intake";
477
+ readonly docId?: string;
478
+ readonly canonicalBlockId?: string;
479
+ readonly runtimeFragmentId?: string;
480
+ readonly pageId?: string;
481
+ readonly pageIndex?: number;
482
+ readonly frameId?: string;
483
+ readonly precision: "word-page-local-calibration";
484
+ readonly coordinateSpace: "frame-px";
485
+ readonly compositorReady: true;
486
+ readonly scaleSource?: "runtime-render-frame-page-rect";
487
+ readonly renderFrameRevision?: number;
488
+ readonly numbering?: {
489
+ readonly runtimeNumberingId?: string;
490
+ readonly canonicalNumberingInstanceId?: string;
491
+ readonly canonicalLevel?: number;
492
+ readonly markerKind?: string;
493
+ readonly markerSuffix?: string;
494
+ readonly markerLane?: ScopeAdjacentGeometryFramePixelRect;
495
+ readonly textColumn?: ScopeAdjacentGeometryFramePixelRect;
496
+ };
497
+ readonly fieldRegion?: {
498
+ readonly canonicalFieldId?: string;
499
+ readonly runtimeFieldRegionId?: string;
500
+ readonly instructionFamily?: string;
501
+ readonly fieldStartAnchorPx?: ScopeAdjacentGeometryFramePixelPoint;
502
+ readonly fieldEndAnchorPx?: ScopeAdjacentGeometryFramePixelPoint;
503
+ readonly fieldResultRangePx?: ScopeAdjacentGeometryFramePixelRect;
504
+ };
505
+ }
506
+
507
+ export interface ScopeAdjacentGeometryEvidence {
508
+ readonly status: "available" | "unavailable";
509
+ readonly source: "l05-adjacent-geometry-intake";
510
+ readonly schemaVersion?: "layer-05-adjacent-geometry-intake/v2";
511
+ readonly rowCount: number;
512
+ readonly rows?: readonly ScopeAdjacentGeometryRowEvidence[];
513
+ readonly reason?:
514
+ | "adjacent-geometry-provider-unavailable"
515
+ | "scope-adjacent-frame-pixel-row-unavailable"
516
+ | "l05-adjacent-intake-not-compositor-ready";
517
+ }
518
+
452
519
  export type ScopeVisualizationClass = "field" | "broad";
453
520
 
454
521
  export interface ScopeVisualizationHint {