@beyondwork/docx-react-component 1.0.106 → 1.0.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +2 -1
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-time.ts +5 -0
  11. package/src/api/v3/ai/_pe2-evidence.ts +38 -0
  12. package/src/api/v3/ai/attach.ts +7 -2
  13. package/src/api/v3/ai/replacement.ts +101 -18
  14. package/src/api/v3/ai/resolve.ts +2 -2
  15. package/src/api/v3/ai/review.ts +177 -3
  16. package/src/api/v3/index.ts +1 -0
  17. package/src/api/v3/runtime/collab.ts +462 -0
  18. package/src/api/v3/runtime/document.ts +503 -20
  19. package/src/api/v3/runtime/geometry.ts +97 -0
  20. package/src/api/v3/runtime/layout.ts +744 -0
  21. package/src/api/v3/runtime/perf-probe.ts +14 -0
  22. package/src/api/v3/runtime/viewport.ts +9 -8
  23. package/src/api/v3/ui/_types.ts +149 -55
  24. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  25. package/src/api/v3/ui/debug.ts +115 -2
  26. package/src/api/v3/ui/index.ts +13 -0
  27. package/src/api/v3/ui/overlays.ts +0 -8
  28. package/src/api/v3/ui/surface.ts +56 -0
  29. package/src/api/v3/ui/viewport.ts +22 -9
  30. package/src/core/commands/image-commands.ts +1 -0
  31. package/src/core/commands/index.ts +6 -0
  32. package/src/core/schema/text-schema.ts +43 -5
  33. package/src/core/selection/mapping.ts +8 -1
  34. package/src/core/selection/review-anchors.ts +5 -1
  35. package/src/core/state/text-transaction.ts +8 -2
  36. package/src/io/export/serialize-revisions.ts +149 -1
  37. package/src/io/normalize/normalize-text.ts +6 -0
  38. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  39. package/src/io/ooxml/parse-fields.ts +24 -2
  40. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  41. package/src/io/ooxml/parse-main-document.ts +153 -9
  42. package/src/io/ooxml/parse-numbering.ts +20 -0
  43. package/src/io/ooxml/parse-revisions.ts +19 -8
  44. package/src/io/opc/package-reader.ts +98 -8
  45. package/src/model/anchor.ts +4 -3
  46. package/src/model/canonical-document.ts +220 -2
  47. package/src/model/canonical-hash.ts +221 -0
  48. package/src/model/canonical-layout-inputs.ts +245 -6
  49. package/src/model/layout/index.ts +1 -0
  50. package/src/model/layout/page-graph-types.ts +118 -1
  51. package/src/model/review/revision-types.ts +14 -3
  52. package/src/preservation/store.ts +20 -4
  53. package/src/review/README.md +1 -1
  54. package/src/review/store/revision-actions.ts +14 -2
  55. package/src/runtime/collab/event-types.ts +67 -1
  56. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  57. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  58. package/src/runtime/document-heading-outline.ts +147 -0
  59. package/src/runtime/document-navigation.ts +8 -243
  60. package/src/runtime/document-runtime.ts +240 -97
  61. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  62. package/src/runtime/formatting/layout-inputs.ts +38 -5
  63. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  64. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  65. package/src/runtime/geometry/caret-geometry.ts +5 -6
  66. package/src/runtime/geometry/geometry-facet.ts +60 -10
  67. package/src/runtime/geometry/geometry-index.ts +591 -20
  68. package/src/runtime/geometry/geometry-types.ts +59 -0
  69. package/src/runtime/geometry/hit-test.ts +11 -1
  70. package/src/runtime/geometry/overlay-rects.ts +5 -3
  71. package/src/runtime/geometry/project-anchors.ts +1 -1
  72. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  73. package/src/runtime/layout/index.ts +6 -0
  74. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  75. package/src/runtime/layout/layout-engine-version.ts +181 -16
  76. package/src/runtime/layout/layout-facet-types.ts +6 -0
  77. package/src/runtime/layout/page-graph.ts +21 -4
  78. package/src/runtime/layout/paginated-layout-engine.ts +139 -15
  79. package/src/runtime/layout/project-block-fragments.ts +265 -7
  80. package/src/runtime/layout/public-facet.ts +78 -24
  81. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  82. package/src/runtime/layout/table-row-split.ts +92 -35
  83. package/src/runtime/prerender/cache-envelope.ts +2 -2
  84. package/src/runtime/prerender/cache-key.ts +5 -4
  85. package/src/runtime/prerender/customxml-cache.ts +0 -1
  86. package/src/runtime/render/render-kernel.ts +1 -1
  87. package/src/runtime/revision-runtime.ts +112 -10
  88. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  89. package/src/runtime/scopes/action-validation.ts +22 -2
  90. package/src/runtime/scopes/capabilities.ts +316 -0
  91. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  92. package/src/runtime/scopes/compiler-service.ts +108 -4
  93. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  94. package/src/runtime/scopes/create-issue.ts +5 -5
  95. package/src/runtime/scopes/evidence.ts +91 -0
  96. package/src/runtime/scopes/formatting/apply.ts +2 -0
  97. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  98. package/src/runtime/scopes/index.ts +54 -0
  99. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  100. package/src/runtime/scopes/layout-evidence.ts +374 -0
  101. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  102. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  103. package/src/runtime/scopes/replacement/apply.ts +97 -34
  104. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  105. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  106. package/src/runtime/scopes/visualization.ts +28 -0
  107. package/src/runtime/surface-projection.ts +44 -5
  108. package/src/runtime/telemetry/perf-probe.ts +216 -0
  109. package/src/runtime/virtualized-rendering.ts +36 -1
  110. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  111. package/src/runtime/workflow/coordinator.ts +39 -11
  112. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  113. package/src/runtime/workflow/index.ts +3 -0
  114. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  115. package/src/runtime/workflow/overlay-lanes.ts +168 -10
  116. package/src/runtime/workflow/overlay-store.ts +2 -2
  117. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  118. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  119. package/src/session/_sync-legacy.ts +17 -27
  120. package/src/session/import/loader.ts +6 -4
  121. package/src/session/import/source-package-evidence.ts +186 -2
  122. package/src/session/index.ts +5 -6
  123. package/src/session/session.ts +30 -56
  124. package/src/session/types.ts +8 -13
  125. package/src/shell/session-bootstrap.ts +155 -81
  126. package/src/ui/WordReviewEditor.tsx +520 -12
  127. package/src/ui/editor-shell-view.tsx +14 -4
  128. package/src/ui/editor-surface-controller.tsx +5 -3
  129. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  130. package/src/ui/presence-overlay-lane.ts +0 -1
  131. package/src/ui/ui-controller-factory.ts +7 -0
  132. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  133. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  134. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  135. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  136. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  137. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  138. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  139. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  140. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  141. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  142. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  143. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  144. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  145. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  146. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  147. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  148. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  149. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  150. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  151. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  152. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  153. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  154. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  155. package/src/ui-tailwind/debug/README.md +4 -1
  156. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  157. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  158. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  159. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  160. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  161. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  162. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  163. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  164. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  165. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  166. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  167. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  168. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  169. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  170. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  171. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  172. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  173. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  174. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  175. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  176. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  177. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  178. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  179. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  180. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  181. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  182. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  183. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  184. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  185. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  186. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  187. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  188. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  189. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  190. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -128,6 +128,55 @@ function longestTextOnlyRangeInParagraph(
128
128
  return best;
129
129
  }
130
130
 
131
+ function textInParagraphRange(
132
+ paragraph: ParagraphLikeEnumeratedScope["paragraph"],
133
+ paragraphFrom: number,
134
+ target: { readonly from: number; readonly to: number },
135
+ ): string {
136
+ let cursor = paragraphFrom;
137
+ const parts: string[] = [];
138
+
139
+ const appendText = (text: string, from: number, to: number) => {
140
+ const clippedFrom = Math.max(from, target.from);
141
+ const clippedTo = Math.min(to, target.to);
142
+ if (clippedTo <= clippedFrom) return;
143
+ const localFrom = clippedFrom - from;
144
+ const localTo = clippedTo - from;
145
+ parts.push(Array.from(text).slice(localFrom, localTo).join(""));
146
+ };
147
+
148
+ const walkInline = (node: InlineNode, from: number): number => {
149
+ switch (node.type) {
150
+ case "text": {
151
+ const chars = Array.from(node.text);
152
+ appendText(node.text, from, from + chars.length);
153
+ return chars.length;
154
+ }
155
+ case "tab":
156
+ appendText("\t", from, from + 1);
157
+ return 1;
158
+ case "hard_break":
159
+ appendText("\n", from, from + 1);
160
+ return 1;
161
+ case "hyperlink":
162
+ case "field": {
163
+ let local = from;
164
+ for (const child of node.children as readonly InlineNode[]) {
165
+ local += walkInline(child, local);
166
+ }
167
+ return local - from;
168
+ }
169
+ default:
170
+ return inlineLengthLocal(node);
171
+ }
172
+ };
173
+
174
+ for (const child of paragraph.children) {
175
+ cursor += walkInline(child, cursor);
176
+ }
177
+ return parts.join("");
178
+ }
179
+
131
180
  export interface CompileParagraphOptions {
132
181
  readonly document?: CanonicalDocument;
133
182
  readonly paragraphIndex?: number;
@@ -198,13 +247,17 @@ export interface CompileParagraphReplacementOptions {
198
247
  * Slice 5 — lower a paragraph-scope replacement proposal into a
199
248
  * `RuntimeOperationPlan`. Handles two content kinds:
200
249
  *
201
- * - `kind: "text"` → single `text-replace` step spanning the
202
- * paragraph's canonical block range; passes a flat string through
203
- * the existing `text.insert` runtime command.
250
+ * - `replace` + `kind: "text"` → single `text-replace` step spanning
251
+ * the paragraph's effective scope range; passes a flat string
252
+ * through the existing `text.insert` runtime command.
253
+ * - `insert-before` / `insert-after` + `kind: "text"` → single
254
+ * collapsed `text-replace` (or tracked insert in suggest mode) at
255
+ * the effective scope edge.
204
256
  * - `kind: "structured"` → single `fragment-replace` step carrying
205
257
  * a `CanonicalDocumentFragment` payload; dispatches via the
206
- * runtime's `insertFragment` pipeline with the block range as
207
- * selection. Unblocked 2026-04-22 once L02 shipped
258
+ * runtime's `insertFragment` pipeline with the effective range as
259
+ * selection for replace, or a collapsed edge for insert-before /
260
+ * insert-after. Unblocked 2026-04-22 once L02 shipped
208
261
  * `CanonicalDocumentFragment` (`src/model/canonical-document.ts`).
209
262
  *
210
263
  * Determinism (S3): the plan is a pure projection of (blockIndex,
@@ -215,7 +268,13 @@ export function compileParagraphReplacement(
215
268
  proposed: ReplacementScope,
216
269
  options: CompileParagraphReplacementOptions,
217
270
  ): RuntimeOperationPlan | null {
218
- if (proposed.operation !== "replace") return null;
271
+ if (
272
+ proposed.operation !== "replace" &&
273
+ proposed.operation !== "insert-before" &&
274
+ proposed.operation !== "insert-after"
275
+ ) {
276
+ return null;
277
+ }
219
278
 
220
279
  const blocks = computeBlockPositions(options.document);
221
280
  const blockRange = blocks.find((b) => b.blockIndex === entry.blockIndex);
@@ -259,7 +318,10 @@ export function compileParagraphReplacement(
259
318
  // replace range to the longest contiguous text-only sub-range so
260
319
  // opaque inlines (images, charts, preserve-only fragments) survive
261
320
  // the apply at their original positions.
262
- if (proposed.preserve?.opaqueFragments === true) {
321
+ if (
322
+ proposed.operation === "replace" &&
323
+ proposed.preserve?.opaqueFragments === true
324
+ ) {
263
325
  const textOnly = longestTextOnlyRangeInParagraph(
264
326
  entry.paragraph,
265
327
  blockRange.from,
@@ -276,8 +338,22 @@ export function compileParagraphReplacement(
276
338
  rangeKind = "opaque-preserving-text";
277
339
  }
278
340
 
341
+ const operationRange =
342
+ proposed.operation === "insert-before"
343
+ ? { from: effectiveRange.from, to: effectiveRange.from }
344
+ : proposed.operation === "insert-after"
345
+ ? { from: effectiveRange.to, to: effectiveRange.to }
346
+ : effectiveRange;
347
+
279
348
  if (proposed.proposedContent.kind === "text") {
280
349
  const text = proposed.proposedContent.text ?? "";
350
+ if (
351
+ proposed.operation === "replace" &&
352
+ proposed.preserve?.opaqueFragments === true &&
353
+ textInParagraphRange(entry.paragraph, blockRange.from, operationRange) === text
354
+ ) {
355
+ return null;
356
+ }
281
357
  const stepKind =
282
358
  options.posture === "suggest-mode" ? "text-insert-tracked" : "text-replace";
283
359
  const summaryScope =
@@ -286,6 +362,16 @@ export function compileParagraphReplacement(
286
362
  : rangeKind === "opaque-preserving-text"
287
363
  ? `paragraph #${entry.blockIndex} opaque-preserving text range [${effectiveRange.from}..${effectiveRange.to}]`
288
364
  : `paragraph #${entry.blockIndex}`;
365
+ const actionVerb =
366
+ proposed.operation === "insert-before"
367
+ ? "insert before"
368
+ : proposed.operation === "insert-after"
369
+ ? "insert after"
370
+ : "replace";
371
+ const actionSummary =
372
+ proposed.operation === "replace"
373
+ ? `${actionVerb} ${summaryScope} text (len ${text.length})`
374
+ : `${actionVerb} ${summaryScope} text at ${operationRange.from} (len ${text.length})`;
289
375
  return {
290
376
  scopeId: entry.handle.scopeId,
291
377
  targetKind: "paragraph",
@@ -295,9 +381,9 @@ export function compileParagraphReplacement(
295
381
  kind: stepKind,
296
382
  summary:
297
383
  stepKind === "text-replace"
298
- ? `replace ${summaryScope} text (len ${text.length})`
299
- : `suggest-mode ${summaryScope} text replace (len ${text.length})`,
300
- range: { from: effectiveRange.from, to: effectiveRange.to },
384
+ ? actionSummary
385
+ : `suggest-mode ${actionSummary}`,
386
+ range: { from: operationRange.from, to: operationRange.to },
301
387
  text,
302
388
  ...(proposed.formatting ? { formatting: proposed.formatting } : {}),
303
389
  },
@@ -325,6 +411,16 @@ export function compileParagraphReplacement(
325
411
  : rangeKind === "opaque-preserving-text"
326
412
  ? `paragraph #${entry.blockIndex} opaque-preserving text range [${effectiveRange.from}..${effectiveRange.to}]`
327
413
  : `paragraph #${entry.blockIndex}`;
414
+ const actionVerb =
415
+ proposed.operation === "insert-before"
416
+ ? "insert before"
417
+ : proposed.operation === "insert-after"
418
+ ? "insert after"
419
+ : "replace";
420
+ const actionSummary =
421
+ proposed.operation === "replace"
422
+ ? `${actionVerb} ${summaryScope} with structured fragment (${blockCount} block(s))`
423
+ : `${actionVerb} ${summaryScope} structured fragment at ${operationRange.from} (${blockCount} block(s))`;
328
424
  return {
329
425
  scopeId: entry.handle.scopeId,
330
426
  targetKind: "paragraph",
@@ -332,8 +428,8 @@ export function compileParagraphReplacement(
332
428
  steps: Object.freeze([
333
429
  {
334
430
  kind: "fragment-replace",
335
- summary: `replace ${summaryScope} with structured fragment (${blockCount} block(s))`,
336
- range: { from: effectiveRange.from, to: effectiveRange.to },
431
+ summary: actionSummary,
432
+ range: { from: operationRange.from, to: operationRange.to },
337
433
  fragment,
338
434
  },
339
435
  ]),
@@ -257,6 +257,19 @@ export interface AIIssueSummary {
257
257
  readonly severity: "info" | "warning" | "error";
258
258
  readonly status: "open" | "resolved";
259
259
  readonly createdAtUtc?: string;
260
+ readonly statusUpdatedAtUtc?: string;
261
+ readonly resolvedAtUtc?: string;
262
+ readonly resolvedBy?: string;
263
+ readonly reopenedAtUtc?: string;
264
+ readonly reopenedBy?: string;
265
+ readonly lastTransition?: {
266
+ readonly action: "resolve" | "reopen";
267
+ readonly actorId: string;
268
+ readonly at: string;
269
+ readonly origin: string;
270
+ readonly fromStatus: "open" | "resolved";
271
+ readonly toStatus: "open" | "resolved";
272
+ };
260
273
  }
261
274
 
262
275
  export interface ScopeBundleEvidence {
@@ -264,6 +277,37 @@ export interface ScopeBundleEvidence {
264
277
  readonly reviewItemIds?: readonly string[];
265
278
  readonly overlappingWorkflowScopeIds?: readonly string[];
266
279
  readonly compatibilityFlags?: readonly string[];
280
+ /**
281
+ * Layout evidence projected from L04/L05 join surfaces when present.
282
+ * Missing layout truth is represented explicitly; Layer 08 does not infer
283
+ * page slices, continuation, or divergence rows from content or DOM state.
284
+ */
285
+ readonly layout?: ScopeLayoutEvidence;
286
+ /**
287
+ * Geometry envelope evidence projected from Layer 05 when a geometry facet
288
+ * is wired. Missing/cold geometry is represented explicitly as
289
+ * `requires-rehydration` or `unavailable`; Layer 08 never fabricates rects.
290
+ */
291
+ readonly geometry?: ScopeGeometryEvidence;
292
+ /**
293
+ * Presentation hint only. Consumers may use this to choose a cheap inline
294
+ * treatment for field-like scopes versus a broader overlay treatment for
295
+ * everything else, but it is not layout or geometry authority.
296
+ */
297
+ readonly visualization?: ScopeVisualizationHint;
298
+ /**
299
+ * Current action capability verdicts for this scope. These are evidence
300
+ * only: they explain what the shipped compiler/runtime can lower today
301
+ * without broadening the mutation surface.
302
+ */
303
+ readonly capabilities?: ScopeCapabilities;
304
+ /**
305
+ * Content-control / SDT overlap evidence. These are preserve-first wrapper
306
+ * facts: when present on a target scope, replacement capability verdicts and
307
+ * validation must surface blockers instead of treating the wrapper like
308
+ * ordinary editable text.
309
+ */
310
+ readonly contentControls?: ScopeContentControlEvidence;
267
311
  /**
268
312
  * Agent-authored explanations on this scope, read back from Layer-06
269
313
  * metadata entries keyed by `metadataId: "ai.explanation"`. Adversarial-
@@ -279,6 +323,192 @@ export interface ScopeBundleEvidence {
279
323
  readonly aiIssues?: readonly AIIssueSummary[];
280
324
  }
281
325
 
326
+ export type ScopeEvidenceAvailability =
327
+ | "available"
328
+ | "partial"
329
+ | "degraded"
330
+ | "requires-rehydration"
331
+ | "unavailable";
332
+
333
+ export interface ScopeLayoutContinuationEvidence {
334
+ readonly pageIds?: readonly string[];
335
+ readonly pageCount?: number;
336
+ readonly crossesPageBoundary?: boolean;
337
+ readonly continuedFromPreviousPage?: boolean;
338
+ readonly continuesToNextPage?: boolean;
339
+ }
340
+
341
+ export interface ScopeTableFramePageEvidence {
342
+ readonly pageId: string;
343
+ readonly pageIndex: number;
344
+ readonly fragmentId: string;
345
+ readonly rowRange?: {
346
+ readonly from: number;
347
+ readonly to: number;
348
+ readonly totalRows: number;
349
+ };
350
+ readonly continuesFromPreviousPage?: boolean;
351
+ readonly continuesToNextPage?: boolean;
352
+ readonly repeatedHeaderRowIndexes?: readonly number[];
353
+ readonly splitRowCarry?: readonly {
354
+ readonly rowIndex: number;
355
+ readonly continuesFromPreviousPage: boolean;
356
+ readonly continuesToNextPage: boolean;
357
+ }[];
358
+ readonly verticalMergeCarry?: readonly {
359
+ readonly columnIndex: number;
360
+ readonly restartRowIndex: number;
361
+ }[];
362
+ }
363
+
364
+ export interface ScopeTableFrameEvidence {
365
+ readonly source: "runtime.layout.table-frame-continuation";
366
+ readonly blockId: string;
367
+ readonly scopeKind: "table" | "table-row" | "table-cell";
368
+ readonly rowIndex?: number;
369
+ readonly cellIndex?: number;
370
+ readonly pageIds?: readonly string[];
371
+ readonly pageSliceIds?: readonly string[];
372
+ readonly layoutObjectIds?: readonly string[];
373
+ readonly rowRangesByPage?: readonly ScopeTableFramePageEvidence[];
374
+ readonly repeatedHeaderRowIndexes?: readonly number[];
375
+ readonly splitRowCarry?: readonly {
376
+ readonly rowIndex: number;
377
+ readonly continuesFromPreviousPage: boolean;
378
+ readonly continuesToNextPage: boolean;
379
+ }[];
380
+ readonly verticalMergeCarry?: readonly {
381
+ readonly columnIndex: number;
382
+ readonly restartRowIndex: number;
383
+ }[];
384
+ }
385
+
386
+ export interface ScopeLayoutEvidence {
387
+ readonly status: ScopeEvidenceAvailability;
388
+ readonly completeness:
389
+ | "complete"
390
+ | "partial"
391
+ | "degraded"
392
+ | "requires-rehydration"
393
+ | "unavailable";
394
+ readonly reason?: string;
395
+ readonly pageSliceIds?: readonly string[];
396
+ readonly layoutObjectIds?: readonly string[];
397
+ readonly continuationState?: ScopeLayoutContinuationEvidence;
398
+ readonly divergenceIds?: readonly string[];
399
+ readonly tableFrame?: ScopeTableFrameEvidence;
400
+ }
401
+
402
+ export type ScopeGeometryEvidenceStatus =
403
+ | "available"
404
+ | "requires-rehydration"
405
+ | "unavailable";
406
+
407
+ export type ScopeGeometryEvidencePrecision =
408
+ | "exact"
409
+ | "within-tolerance"
410
+ | "heuristic";
411
+
412
+ export interface ScopeGeometryEvidenceRect {
413
+ readonly x: number;
414
+ readonly y: number;
415
+ readonly width: number;
416
+ readonly height: number;
417
+ readonly space: "twips" | "frame" | "overlay";
418
+ readonly precision?: ScopeGeometryEvidencePrecision;
419
+ }
420
+
421
+ export interface ScopeGeometryEvidence {
422
+ readonly status: ScopeGeometryEvidenceStatus;
423
+ readonly requiresRehydration: boolean;
424
+ readonly reason?: string;
425
+ readonly pageIds?: readonly string[];
426
+ readonly confidence?: "exact" | "medium" | "detached";
427
+ readonly precision?: ScopeGeometryEvidencePrecision;
428
+ readonly rects?: readonly ScopeGeometryEvidenceRect[];
429
+ readonly attachPoint?: {
430
+ readonly x: number;
431
+ readonly y: number;
432
+ readonly space: "twips" | "frame" | "overlay";
433
+ };
434
+ readonly linesCrossed?: number;
435
+ readonly continuationState?: ScopeLayoutContinuationEvidence;
436
+ readonly sourceIdentity?: {
437
+ readonly storyKey?: string;
438
+ readonly blockPath?: string;
439
+ readonly tableKey?: string;
440
+ readonly rowKey?: string;
441
+ readonly cellKey?: string;
442
+ readonly scopeKey?: string;
443
+ readonly scopeId?: string;
444
+ readonly objectKey?: string;
445
+ readonly inlinePath?: string;
446
+ readonly objectKind?: string;
447
+ readonly editPosture?: string;
448
+ readonly joinKind?: string;
449
+ };
450
+ }
451
+
452
+ export type ScopeVisualizationClass = "field" | "broad";
453
+
454
+ export interface ScopeVisualizationHint {
455
+ readonly class: ScopeVisualizationClass;
456
+ readonly reason: string;
457
+ }
458
+
459
+ export type ScopeCapabilityStatus =
460
+ | "supported"
461
+ | "blocked"
462
+ | "unsupported"
463
+ | "degraded";
464
+
465
+ /**
466
+ * Action posture for compiler consumers that need to distinguish "warn and
467
+ * proceed" from a real refusal. L08 must prefer `warn-and-proceed` whenever
468
+ * the mutation can be lowered without losing preserved truth or fabricating
469
+ * layout/geometry. `hard-refusal` means the action must not dispatch.
470
+ */
471
+ export type ScopeActionPosture =
472
+ | "supported"
473
+ | "warn-and-proceed"
474
+ | "hard-refusal";
475
+
476
+ export interface ScopeCapabilityVerdict {
477
+ readonly supported: boolean;
478
+ readonly status: ScopeCapabilityStatus;
479
+ readonly posture?: ScopeActionPosture;
480
+ readonly reason?: string;
481
+ readonly blockers?: readonly string[];
482
+ readonly warnings?: readonly string[];
483
+ }
484
+
485
+ export interface ScopeCapabilities {
486
+ readonly canReplaceText: ScopeCapabilityVerdict;
487
+ readonly canReplaceFragment: ScopeCapabilityVerdict;
488
+ readonly canInsertBefore: ScopeCapabilityVerdict;
489
+ readonly canInsertAfter: ScopeCapabilityVerdict;
490
+ readonly canApplyFormatting: ScopeCapabilityVerdict;
491
+ readonly canClearFormattingLayer: ScopeCapabilityVerdict;
492
+ readonly canAttachMetadata: ScopeCapabilityVerdict;
493
+ }
494
+
495
+ export type ScopeContentControlEvidenceStatus = "none" | "present";
496
+
497
+ export interface ScopeContentControlEvidenceEntry {
498
+ readonly evidenceId: string;
499
+ readonly blockIndex: number;
500
+ readonly sdtType?: string;
501
+ readonly alias?: string;
502
+ readonly tag?: string;
503
+ readonly lock?: string;
504
+ }
505
+
506
+ export interface ScopeContentControlEvidence {
507
+ readonly status: ScopeContentControlEvidenceStatus;
508
+ readonly count: number;
509
+ readonly entries: readonly ScopeContentControlEvidenceEntry[];
510
+ }
511
+
282
512
  export interface ScopeBundle {
283
513
  readonly scope: SemanticScope;
284
514
  readonly neighborhood: ScopeBundleNeighborhood;
@@ -286,11 +516,19 @@ export interface ScopeBundle {
286
516
  readonly generatedAtUtc: string;
287
517
  }
288
518
 
289
- export type ReplacementOperationKind =
519
+ export type ScopeReplacementOperationKind =
290
520
  | "replace"
291
521
  | "insert-before"
292
- | "insert-after"
293
- | "split"
522
+ | "insert-after";
523
+
524
+ /**
525
+ * Compiler-internal proposal operation. Public replacement surfaces accept
526
+ * `ScopeReplacementOperationKind`; `annotate` is retained only for metadata
527
+ * write audit records (`ai.attachExplanation` / `ai.createIssue`) that reuse
528
+ * the `ScopeActionAudit` shape without going through text replacement.
529
+ */
530
+ export type ReplacementOperationKind =
531
+ | ScopeReplacementOperationKind
294
532
  | "annotate";
295
533
 
296
534
  export interface ReplacementPreservePolicy {
@@ -426,6 +664,7 @@ export interface ValidationApproval {
426
664
  */
427
665
  export interface ValidationResult {
428
666
  readonly safe: boolean;
667
+ readonly posture?: ScopeActionPosture;
429
668
  readonly blockedReasons: readonly string[];
430
669
  readonly warnings: readonly ValidationIssue[];
431
670
  readonly approval?: ValidationApproval;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Scope visualization hint projection.
3
+ *
4
+ * This is deliberately evidence, not geometry. Layer 08 can tell consumers
5
+ * whether a scope is structurally field-like or broad; Layer 05 remains the
6
+ * authority for rects, line spans, and overlay envelopes.
7
+ */
8
+
9
+ import type {
10
+ ScopeVisualizationHint,
11
+ SemanticScope,
12
+ } from "./semantic-scope-types.ts";
13
+
14
+ export function deriveScopeVisualization(
15
+ scope: SemanticScope,
16
+ ): ScopeVisualizationHint {
17
+ if (scope.kind === "field") {
18
+ return {
19
+ class: "field",
20
+ reason: "kind:field",
21
+ };
22
+ }
23
+
24
+ return {
25
+ class: "broad",
26
+ reason: `kind:${scope.kind}`,
27
+ };
28
+ }
@@ -149,7 +149,7 @@ export interface SurfaceProjectionOptions {
149
149
  * if you call `createEditorSurfaceSnapshot` directly, normalize first.
150
150
  */
151
151
  viewportBlockRanges?: readonly { start: number; end: number }[] | null;
152
- /** @deprecated use `viewportBlockRanges`. Kept for back-compat; wrapped into a 1-element array when supplied alone. */
152
+ /** @deprecated use `viewportBlockRanges`. Kept for back-compat; remove after the next published API pin refresh. */
153
153
  viewportBlockRange?: { start: number; end: number } | null;
154
154
  /**
155
155
  * Active markup mode. When set together with the document's
@@ -444,7 +444,9 @@ function createSurfaceBlock(
444
444
  }
445
445
 
446
446
  if (block.type === "sdt") {
447
- const descriptor = describeStructuredWrapperBlock(block);
447
+ const descriptor = describeStructuredWrapperBlock(block, {
448
+ projectVisibleTocContentControls: true,
449
+ });
448
450
  if (descriptor) {
449
451
  const blockId = `sdt-wrapper-${counters.sdt}`;
450
452
  counters.sdt += 1;
@@ -1086,6 +1088,12 @@ function createSdtBlock(
1086
1088
  const children: SurfaceBlockSnapshot[] = [];
1087
1089
  const lockedFragmentIds: string[] = [];
1088
1090
  let innerCursor = cursor;
1091
+ const descriptor = describeStructuredWrapperBlock(block);
1092
+ const recursableBlockedReasonCode = descriptor
1093
+ ? isBlockedImportFeatureKey(descriptor.featureKey)
1094
+ ? "workflow_blocked_import"
1095
+ : "workflow_preserve_only"
1096
+ : getRecursableSdtBlockedReasonCode(block);
1089
1097
 
1090
1098
  for (const child of block.children) {
1091
1099
  const result = createSurfaceBlock(
@@ -1112,11 +1120,21 @@ function createSdtBlock(
1112
1120
  ...(block.properties.alias ? { alias: block.properties.alias } : {}),
1113
1121
  ...(block.properties.tag ? { tag: block.properties.tag } : {}),
1114
1122
  ...(block.properties.lock ? { lock: block.properties.lock } : {}),
1123
+ ...(recursableBlockedReasonCode
1124
+ ? { blockedReasonCode: recursableBlockedReasonCode }
1125
+ : {}),
1115
1126
  ...(block.properties.checkbox ? { checkboxChecked: block.properties.checkbox.checked } : {}),
1116
1127
  ...(block.properties.datePicker?.fullDate ? { dateValue: block.properties.datePicker.fullDate } : {}),
1117
1128
  ...(block.properties.dropdownList ? { dropdownItems: block.properties.dropdownList } : {}),
1118
1129
  ...(block.properties.comboBox ? { comboBoxItems: block.properties.comboBox } : {}),
1119
1130
  ...(block.properties.showingPlcHdr ? { showingPlcHdr: true } : {}),
1131
+ ...(descriptor
1132
+ ? {
1133
+ label: descriptor.label,
1134
+ detail: descriptor.detail,
1135
+ featureKey: descriptor.featureKey,
1136
+ }
1137
+ : {}),
1120
1138
  children,
1121
1139
  },
1122
1140
  lockedFragmentIds,
@@ -1124,6 +1142,24 @@ function createSdtBlock(
1124
1142
  };
1125
1143
  }
1126
1144
 
1145
+ function getRecursableSdtBlockedReasonCode(
1146
+ block: Mutable<SdtNode>,
1147
+ ): "workflow_preserve_only" | null {
1148
+ const searchText = [
1149
+ block.properties.alias,
1150
+ block.properties.tag,
1151
+ block.properties.sdtType,
1152
+ block.properties.propertiesXml,
1153
+ ]
1154
+ .filter(Boolean)
1155
+ .join(" ")
1156
+ .toLowerCase();
1157
+
1158
+ return searchText.includes("table of contents") || /\btoc\b/u.test(searchText)
1159
+ ? "workflow_preserve_only"
1160
+ : null;
1161
+ }
1162
+
1127
1163
  function createParagraphBlock(
1128
1164
  paragraphIndex: number,
1129
1165
  paragraph: Mutable<ParagraphNode>,
@@ -1562,6 +1598,7 @@ function appendInlineSegments(
1562
1598
  });
1563
1599
  return { nextCursor: start + 1, lockedFragmentIds: [] };
1564
1600
  }
1601
+ const objectAnchor = surfaceAnchorFromGeometry(node.anchor);
1565
1602
  if (c.type === "chart_preview") {
1566
1603
  const parsedChartId = registerParsedChartPreview(c, document);
1567
1604
  return appendComplexPreviewSegment(
@@ -1573,7 +1610,7 @@ function appendInlineSegments(
1573
1610
  {
1574
1611
  previewMediaId: c.previewMediaId,
1575
1612
  parsedChartId,
1576
- anchor: surfaceAnchorFromGeometry(node.anchor),
1613
+ ...(objectAnchor ? { anchor: objectAnchor } : {}),
1577
1614
  },
1578
1615
  );
1579
1616
  }
@@ -1586,7 +1623,7 @@ function appendInlineSegments(
1586
1623
  `DrawingFrame smartart_preview (${node.anchor.wrapMode}).`,
1587
1624
  {
1588
1625
  previewMediaId: c.previewMediaId,
1589
- anchor: surfaceAnchorFromGeometry(node.anchor),
1626
+ ...(objectAnchor ? { anchor: objectAnchor } : {}),
1590
1627
  },
1591
1628
  );
1592
1629
  }
@@ -1596,7 +1633,9 @@ function appendInlineSegments(
1596
1633
  start,
1597
1634
  "Drawing frame",
1598
1635
  `DrawingFrame ${c.type} (${node.anchor.wrapMode}).`,
1599
- { anchor: surfaceAnchorFromGeometry(node.anchor) },
1636
+ {
1637
+ ...(objectAnchor ? { anchor: objectAnchor } : {}),
1638
+ },
1600
1639
  );
1601
1640
  }
1602
1641
  case "symbol":