@beyondwork/docx-react-component 1.0.105 → 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 (193) 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 +10 -2
  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-reference.ts +28 -0
  11. package/src/api/v3/ai/_audit-time.ts +5 -0
  12. package/src/api/v3/ai/_pe2-evidence.ts +310 -6
  13. package/src/api/v3/ai/attach.ts +29 -4
  14. package/src/api/v3/ai/bundle.ts +6 -2
  15. package/src/api/v3/ai/inspect.ts +6 -2
  16. package/src/api/v3/ai/replacement.ts +112 -18
  17. package/src/api/v3/ai/resolve.ts +2 -2
  18. package/src/api/v3/ai/review.ts +177 -3
  19. package/src/api/v3/index.ts +8 -0
  20. package/src/api/v3/runtime/collab.ts +462 -0
  21. package/src/api/v3/runtime/document.ts +503 -20
  22. package/src/api/v3/runtime/geometry.ts +97 -0
  23. package/src/api/v3/runtime/layout.ts +744 -0
  24. package/src/api/v3/runtime/perf-probe.ts +14 -0
  25. package/src/api/v3/runtime/viewport.ts +9 -8
  26. package/src/api/v3/ui/_types.ts +202 -55
  27. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  28. package/src/api/v3/ui/debug.ts +115 -2
  29. package/src/api/v3/ui/index.ts +17 -0
  30. package/src/api/v3/ui/overlays.ts +0 -8
  31. package/src/api/v3/ui/surface.ts +56 -0
  32. package/src/api/v3/ui/viewport.ts +119 -9
  33. package/src/core/commands/image-commands.ts +1 -0
  34. package/src/core/commands/index.ts +6 -0
  35. package/src/core/schema/text-schema.ts +43 -5
  36. package/src/core/selection/mapping.ts +8 -1
  37. package/src/core/selection/review-anchors.ts +5 -1
  38. package/src/core/state/text-transaction.ts +8 -2
  39. package/src/io/export/serialize-revisions.ts +149 -1
  40. package/src/io/normalize/normalize-text.ts +6 -0
  41. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  42. package/src/io/ooxml/parse-fields.ts +24 -2
  43. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  44. package/src/io/ooxml/parse-main-document.ts +153 -9
  45. package/src/io/ooxml/parse-numbering.ts +20 -0
  46. package/src/io/ooxml/parse-revisions.ts +19 -8
  47. package/src/io/opc/package-reader.ts +98 -8
  48. package/src/model/anchor.ts +4 -3
  49. package/src/model/canonical-document.ts +220 -2
  50. package/src/model/canonical-hash.ts +221 -0
  51. package/src/model/canonical-layout-inputs.ts +245 -6
  52. package/src/model/layout/index.ts +1 -0
  53. package/src/model/layout/page-graph-types.ts +147 -1
  54. package/src/model/review/revision-types.ts +14 -3
  55. package/src/preservation/store.ts +20 -4
  56. package/src/review/README.md +1 -1
  57. package/src/review/store/revision-actions.ts +14 -2
  58. package/src/runtime/collab/event-types.ts +67 -1
  59. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  60. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  61. package/src/runtime/document-heading-outline.ts +147 -0
  62. package/src/runtime/document-navigation.ts +8 -243
  63. package/src/runtime/document-runtime.ts +279 -115
  64. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  65. package/src/runtime/formatting/layout-inputs.ts +38 -5
  66. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  67. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  68. package/src/runtime/geometry/caret-geometry.ts +5 -6
  69. package/src/runtime/geometry/geometry-facet.ts +60 -10
  70. package/src/runtime/geometry/geometry-index.ts +661 -16
  71. package/src/runtime/geometry/geometry-types.ts +59 -0
  72. package/src/runtime/geometry/hit-test.ts +11 -1
  73. package/src/runtime/geometry/overlay-rects.ts +5 -3
  74. package/src/runtime/geometry/project-anchors.ts +1 -1
  75. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  76. package/src/runtime/layout/index.ts +6 -0
  77. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  78. package/src/runtime/layout/layout-engine-version.ts +188 -16
  79. package/src/runtime/layout/layout-facet-types.ts +6 -0
  80. package/src/runtime/layout/page-graph.ts +23 -4
  81. package/src/runtime/layout/paginated-layout-engine.ts +149 -15
  82. package/src/runtime/layout/project-block-fragments.ts +351 -14
  83. package/src/runtime/layout/public-facet.ts +162 -24
  84. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  85. package/src/runtime/layout/table-row-split.ts +92 -35
  86. package/src/runtime/prerender/cache-envelope.ts +2 -2
  87. package/src/runtime/prerender/cache-key.ts +5 -4
  88. package/src/runtime/prerender/customxml-cache.ts +0 -1
  89. package/src/runtime/render/render-kernel.ts +1 -1
  90. package/src/runtime/revision-runtime.ts +112 -10
  91. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  92. package/src/runtime/scopes/action-validation.ts +22 -2
  93. package/src/runtime/scopes/capabilities.ts +316 -0
  94. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  95. package/src/runtime/scopes/compiler-service.ts +108 -4
  96. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  97. package/src/runtime/scopes/create-issue.ts +5 -5
  98. package/src/runtime/scopes/evidence.ts +91 -0
  99. package/src/runtime/scopes/formatting/apply.ts +2 -0
  100. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  101. package/src/runtime/scopes/index.ts +54 -0
  102. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  103. package/src/runtime/scopes/layout-evidence.ts +374 -0
  104. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  105. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  106. package/src/runtime/scopes/replacement/apply.ts +97 -34
  107. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  108. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  109. package/src/runtime/scopes/visualization.ts +28 -0
  110. package/src/runtime/surface-projection.ts +44 -5
  111. package/src/runtime/telemetry/perf-probe.ts +216 -0
  112. package/src/runtime/virtualized-rendering.ts +36 -1
  113. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  114. package/src/runtime/workflow/coordinator.ts +39 -11
  115. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  116. package/src/runtime/workflow/index.ts +4 -0
  117. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  118. package/src/runtime/workflow/overlay-lanes.ts +386 -0
  119. package/src/runtime/workflow/overlay-store.ts +2 -2
  120. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  121. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  122. package/src/session/_sync-legacy.ts +17 -27
  123. package/src/session/import/loader.ts +6 -4
  124. package/src/session/import/source-package-evidence.ts +186 -2
  125. package/src/session/index.ts +5 -6
  126. package/src/session/session.ts +30 -56
  127. package/src/session/types.ts +8 -13
  128. package/src/shell/session-bootstrap.ts +155 -81
  129. package/src/ui/WordReviewEditor.tsx +520 -12
  130. package/src/ui/editor-shell-view.tsx +14 -4
  131. package/src/ui/editor-surface-controller.tsx +5 -3
  132. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  133. package/src/ui/presence-overlay-lane.ts +130 -0
  134. package/src/ui/ui-controller-factory.ts +17 -0
  135. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  136. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  137. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  138. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  139. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  140. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  141. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  142. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  143. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  144. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  145. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  146. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  147. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  148. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  149. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  150. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  151. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  152. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  153. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  154. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  155. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  156. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  157. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  158. package/src/ui-tailwind/debug/README.md +4 -1
  159. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  160. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  161. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  162. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  163. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  164. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  165. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  166. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  167. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  168. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  169. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  170. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  171. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  172. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  173. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  174. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  175. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  176. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  177. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  178. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  179. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  180. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  181. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  182. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  183. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  184. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  185. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  186. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  187. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  188. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  189. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  190. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  191. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  192. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  193. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -1,15 +1,25 @@
1
+ import type { EditorStoryTarget } from "../api/public-types.ts";
1
2
  import type { CanonicalDocumentEnvelope } from "../core/state/editor-state.ts";
2
3
  import {
3
4
  getReviewCommandIntent,
4
5
  isSingleRevisionReviewCommand,
5
6
  type ReviewCommand,
6
7
  } from "../core/commands/review-commands.ts";
7
- import type { TransactionMapping } from "../core/selection/mapping.ts";
8
+ import {
9
+ MAIN_STORY_TARGET,
10
+ storyTargetsEqual,
11
+ type TransactionMapping,
12
+ } from "../core/selection/mapping.ts";
8
13
  import {
9
14
  applyRevisionAction,
15
+ type ApplyRevisionActionResult,
10
16
  type RevisionActionOutcome,
11
17
  } from "../review/store/revision-actions.ts";
12
- import { type RevisionStore } from "../review/store/revision-store.ts";
18
+ import {
19
+ createRevisionStore,
20
+ type RevisionStore,
21
+ } from "../review/store/revision-store.ts";
22
+ import { getStoryBlocks, replaceStoryBlocks } from "./story-targeting.ts";
13
23
 
14
24
  export interface RevisionRuntimeState {
15
25
  document: CanonicalDocumentEnvelope;
@@ -30,7 +40,12 @@ export interface RevisionRuntimeCommandEffects {
30
40
 
31
41
  export interface RevisionRuntimeCommandResult extends RevisionRuntimeState {
32
42
  outcomes: RevisionActionOutcome[];
33
- mappings: Array<{ revisionId: string; mapping: TransactionMapping; steps: number }>;
43
+ mappings: Array<{
44
+ revisionId: string;
45
+ mapping: TransactionMapping;
46
+ steps: number;
47
+ storyTarget: EditorStoryTarget;
48
+ }>;
34
49
  effects: RevisionRuntimeCommandEffects;
35
50
  }
36
51
 
@@ -44,30 +59,31 @@ export function applyRevisionRuntimeCommand(
44
59
  const revisionIds = expandLinkedMovePartners(rawRevisionIds, options.state.store);
45
60
 
46
61
  const outcomes: RevisionActionOutcome[] = [];
47
- const mappings: Array<{ revisionId: string; mapping: TransactionMapping; steps: number }> = [];
62
+ const mappings: RevisionRuntimeCommandResult["mappings"] = [];
48
63
  const appliedRevisionIds: string[] = [];
49
64
  const detachedRevisionIds = new Set<string>();
50
65
 
51
66
  let state = options.state;
52
67
 
53
68
  for (const revisionId of revisionIds) {
69
+ const revision = state.store.revisions[revisionId];
70
+ const storyTarget = getRevisionStoryTarget(revision);
71
+ const scopedState = createStoryScopedRevisionState(state, storyTarget);
54
72
  const result = applyRevisionAction({
55
- document: state.document,
56
- store: state.store,
73
+ document: scopedState.document,
74
+ store: scopedState.store,
57
75
  revisionId,
58
76
  intent: getReviewCommandIntent(options.command),
59
77
  timestamp: options.timestamp,
60
78
  });
61
79
 
62
- state = {
63
- document: result.document,
64
- store: result.store,
65
- };
80
+ state = mergeStoryScopedRevisionResult(state, storyTarget, scopedState, result);
66
81
  outcomes.push(result.outcome);
67
82
  mappings.push({
68
83
  revisionId,
69
84
  mapping: result.mapping,
70
85
  steps: result.mapping.steps.length,
86
+ storyTarget,
71
87
  });
72
88
 
73
89
  if (result.outcome.kind === "applied") {
@@ -94,6 +110,92 @@ export function applyRevisionRuntimeCommand(
94
110
  };
95
111
  }
96
112
 
113
+ interface StoryScopedRevisionState extends RevisionRuntimeState {
114
+ storyRevisionIds: readonly string[];
115
+ }
116
+
117
+ function createStoryScopedRevisionState(
118
+ state: RevisionRuntimeState,
119
+ storyTarget: EditorStoryTarget,
120
+ ): StoryScopedRevisionState {
121
+ if (storyTargetsEqual(storyTarget, MAIN_STORY_TARGET)) {
122
+ return {
123
+ ...state,
124
+ storyRevisionIds: Object.keys(state.store.revisions),
125
+ };
126
+ }
127
+
128
+ const storyRevisionIds = Object.entries(state.store.revisions)
129
+ .filter(([, revision]) =>
130
+ storyTargetsEqual(getRevisionStoryTarget(revision), storyTarget)
131
+ )
132
+ .map(([revisionId]) => revisionId);
133
+ const storyRevisions = Object.fromEntries(
134
+ storyRevisionIds.map((revisionId) => [
135
+ revisionId,
136
+ state.store.revisions[revisionId]!,
137
+ ]),
138
+ );
139
+
140
+ return {
141
+ document: {
142
+ ...state.document,
143
+ content: {
144
+ ...state.document.content,
145
+ children: [...getStoryBlocks(state.document, storyTarget)],
146
+ },
147
+ },
148
+ store: createRevisionStore(storyRevisions),
149
+ storyRevisionIds,
150
+ };
151
+ }
152
+
153
+ function mergeStoryScopedRevisionResult(
154
+ previousState: RevisionRuntimeState,
155
+ storyTarget: EditorStoryTarget,
156
+ scopedState: StoryScopedRevisionState,
157
+ result: ApplyRevisionActionResult,
158
+ ): RevisionRuntimeState {
159
+ if (storyTargetsEqual(storyTarget, MAIN_STORY_TARGET)) {
160
+ return {
161
+ document: result.document,
162
+ store: result.store,
163
+ };
164
+ }
165
+
166
+ const nextStore = createRevisionStore({
167
+ ...previousState.store.revisions,
168
+ ...Object.fromEntries(
169
+ scopedState.storyRevisionIds
170
+ .filter((revisionId) => result.store.revisions[revisionId])
171
+ .map((revisionId) => [
172
+ revisionId,
173
+ result.store.revisions[revisionId]!,
174
+ ]),
175
+ ),
176
+ });
177
+
178
+ return {
179
+ document: replaceStoryBlocks(
180
+ {
181
+ ...previousState.document,
182
+ updatedAt: result.document.updatedAt,
183
+ },
184
+ storyTarget,
185
+ result.document.content.children,
186
+ ),
187
+ store: nextStore,
188
+ };
189
+ }
190
+
191
+ function getRevisionStoryTarget(
192
+ revision: RevisionStore["revisions"][string] | undefined,
193
+ ): EditorStoryTarget {
194
+ return revision?.metadata.storyTarget
195
+ ? { ...revision.metadata.storyTarget }
196
+ : MAIN_STORY_TARGET;
197
+ }
198
+
97
199
  function expandLinkedMovePartners(
98
200
  revisionIds: readonly string[],
99
201
  store: RevisionStore,
@@ -34,6 +34,7 @@ export type {
34
34
  InteractionGuardSnapshot,
35
35
  TextFormattingDirective,
36
36
  WorkflowBlockedCommandReason,
37
+ WorkflowEventOrigin,
37
38
  WorkflowMetadataEntry,
38
39
  WorkflowMetadataSnapshot,
39
40
  WorkflowOverlay,
@@ -63,6 +63,7 @@ import {
63
63
  import { resolveScopeRange } from "./scope-range.ts";
64
64
  import type {
65
65
  ReplacementScope,
66
+ ScopeActionPosture,
66
67
  ScopeActionOperationKind,
67
68
  SemanticScope,
68
69
  ValidationApproval,
@@ -144,8 +145,6 @@ function inferActionId(
144
145
  case "insert-before":
145
146
  case "insert-after":
146
147
  return "generate_text";
147
- case "split":
148
- return "rewrite_paragraph";
149
148
  case "annotate":
150
149
  return "suggest_comment_response";
151
150
  }
@@ -296,6 +295,17 @@ function collectPreservationVerdict(
296
295
  ): void {
297
296
  const { document, scope, positionMap } = inputs;
298
297
  if (inputs.skipPreservation === true) return;
298
+ // Insert-before / insert-after lower to collapsed insertions at the
299
+ // effective scope edge. They do not replace or delete the scope's
300
+ // current contents, so preserve-only payloads and nested markers
301
+ // inside the scope are not at risk in the same way they are for
302
+ // `replace`.
303
+ if (
304
+ inputs.operation === "insert-before" ||
305
+ inputs.operation === "insert-after"
306
+ ) {
307
+ return;
308
+ }
299
309
  if (!document) return;
300
310
  const pm = positionMap ?? buildScopePositionMap(document);
301
311
  const range = inputs.enumeratedScope
@@ -397,6 +407,15 @@ function collectPolicyVerdict(
397
407
  return undefined;
398
408
  }
399
409
 
410
+ function validationPosture(
411
+ blockedReasons: readonly string[],
412
+ warnings: readonly ValidationIssue[],
413
+ ): ScopeActionPosture {
414
+ if (blockedReasons.length > 0) return "hard-refusal";
415
+ if (warnings.length > 0) return "warn-and-proceed";
416
+ return "supported";
417
+ }
418
+
400
419
  /**
401
420
  * Compose a single validation verdict for a proposed scope replacement.
402
421
  * Synchronous, deterministic, free of side effects. Safe to call in hot
@@ -420,6 +439,7 @@ export function composeScopeValidation(
420
439
  const safe = blockedReasons.length === 0;
421
440
  return {
422
441
  safe,
442
+ posture: validationPosture(blockedReasons, warnings),
423
443
  blockedReasons: Object.freeze([...blockedReasons]),
424
444
  warnings: Object.freeze([...warnings]),
425
445
  ...(approval ? { approval } : {}),
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Scope action capability projection.
3
+ *
4
+ * This module reports what the shipped L08 compiler/runtime can lower today.
5
+ * It does not validate a specific payload and does not authorize a mutation;
6
+ * every apply path still re-runs validation and compile.
7
+ */
8
+
9
+ import type {
10
+ ScopeContentControlEvidence,
11
+ ScopeGeometryEvidence,
12
+ ScopeLayoutEvidence,
13
+ ScopeCapabilities,
14
+ ScopeCapabilityVerdict,
15
+ SemanticScope,
16
+ SemanticScopeKind,
17
+ } from "./semantic-scope-types.ts";
18
+ import {
19
+ MULTI_PARAGRAPH_REPLACEMENT_REFUSAL,
20
+ multiParagraphReplacementBlockers,
21
+ } from "./multi-paragraph-refusal.ts";
22
+
23
+ const PARAGRAPH_LIKE = new Set<SemanticScopeKind>([
24
+ "paragraph",
25
+ "heading",
26
+ "list-item",
27
+ ]);
28
+
29
+ const FORMATTING_SUPPORTED = new Set<SemanticScopeKind>([
30
+ "paragraph",
31
+ "heading",
32
+ "list-item",
33
+ "scope",
34
+ ]);
35
+
36
+ const METADATA_SUPPORTED = new Set<SemanticScopeKind>([
37
+ "paragraph",
38
+ "heading",
39
+ "list-item",
40
+ "table",
41
+ "table-row",
42
+ "table-cell",
43
+ "scope",
44
+ ]);
45
+
46
+ export interface ScopeCapabilityContext {
47
+ readonly layout?: ScopeLayoutEvidence;
48
+ readonly geometry?: ScopeGeometryEvidence;
49
+ readonly contentControls?: ScopeContentControlEvidence;
50
+ }
51
+
52
+ function freezeList(values: readonly string[]): readonly string[] {
53
+ return Object.freeze([...values]);
54
+ }
55
+
56
+ function supported(
57
+ reason: string,
58
+ warnings: readonly string[] = [],
59
+ ): ScopeCapabilityVerdict {
60
+ const hasWarnings = warnings.length > 0;
61
+ return {
62
+ supported: true,
63
+ status: hasWarnings ? "degraded" : "supported",
64
+ posture: hasWarnings ? "warn-and-proceed" : "supported",
65
+ reason,
66
+ ...(hasWarnings ? { warnings: freezeList(warnings) } : {}),
67
+ };
68
+ }
69
+
70
+ function unsupported(
71
+ reason: string,
72
+ blockers: readonly string[] = [reason],
73
+ warnings: readonly string[] = [],
74
+ ): ScopeCapabilityVerdict {
75
+ return {
76
+ supported: false,
77
+ status: "unsupported",
78
+ posture: "hard-refusal",
79
+ reason,
80
+ blockers: freezeList(blockers),
81
+ ...(warnings.length > 0 ? { warnings: freezeList(warnings) } : {}),
82
+ };
83
+ }
84
+
85
+ function blocked(
86
+ reason: string,
87
+ blockers: readonly string[] = [reason],
88
+ ): ScopeCapabilityVerdict {
89
+ return {
90
+ supported: false,
91
+ status: "blocked",
92
+ posture: "hard-refusal",
93
+ reason,
94
+ blockers: freezeList(blockers),
95
+ };
96
+ }
97
+
98
+ function guardBlocker(scope: SemanticScope): string | null {
99
+ switch (scope.workflow.effectiveMode) {
100
+ case "view":
101
+ return "guard:view-mode-active";
102
+ case "comment":
103
+ return "guard:comment-only";
104
+ case "blocked": {
105
+ const [first] = scope.workflow.blockedReasons ?? [];
106
+ return first ? `guard:block-${first}` : "guard:block-scope-overlay";
107
+ }
108
+ default:
109
+ return null;
110
+ }
111
+ }
112
+
113
+ function operationRefusal(scope: SemanticScope): string {
114
+ return `compile-refused:${scope.kind}`;
115
+ }
116
+
117
+ function contentControlBlockers(
118
+ context: ScopeCapabilityContext | undefined,
119
+ ): readonly string[] {
120
+ const entries = context?.contentControls?.entries ?? [];
121
+ if (entries.length === 0) return [];
122
+ return freezeList(entries.map((entry) => `preserve:content-control:${entry.evidenceId}`));
123
+ }
124
+
125
+ function evidenceWarnings(
126
+ context: ScopeCapabilityContext | undefined,
127
+ ): readonly string[] {
128
+ const warnings: string[] = [];
129
+ if (context?.layout) {
130
+ if (
131
+ context.layout.status === "requires-rehydration" ||
132
+ context.layout.status === "unavailable" ||
133
+ context.layout.completeness !== "complete"
134
+ ) {
135
+ warnings.push(`layout:${context.layout.completeness}`);
136
+ }
137
+ }
138
+ if (context?.geometry) {
139
+ if (context.geometry.requiresRehydration) {
140
+ warnings.push("geometry:requires-rehydration");
141
+ } else if (context.geometry.status === "unavailable") {
142
+ warnings.push("geometry:unavailable");
143
+ }
144
+ if (context.geometry.continuationState?.crossesPageBoundary) {
145
+ warnings.push("geometry:crosses-page-boundary");
146
+ }
147
+ }
148
+ return freezeList(warnings);
149
+ }
150
+
151
+ function insertCapability(
152
+ scope: SemanticScope,
153
+ edge: "before" | "after",
154
+ context?: ScopeCapabilityContext,
155
+ ): ScopeCapabilityVerdict {
156
+ const guard = guardBlocker(scope);
157
+ if (guard) return blocked(guard);
158
+
159
+ if (!PARAGRAPH_LIKE.has(scope.kind)) {
160
+ return unsupported(operationRefusal(scope));
161
+ }
162
+
163
+ if (
164
+ scope.replaceability.level === "blocked" ||
165
+ scope.replaceability.level === "preserve-only"
166
+ ) {
167
+ const reason = scope.replaceability.reason
168
+ ? `replaceability:${scope.replaceability.reason}`
169
+ : `replaceability:${scope.replaceability.level}`;
170
+ return unsupported(reason);
171
+ }
172
+
173
+ return supported(
174
+ `compile-supported:paragraph-like:insert-${edge}`,
175
+ [
176
+ ...(scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : []),
177
+ ...evidenceWarnings(context),
178
+ ],
179
+ );
180
+ }
181
+
182
+ function replaceTextCapability(
183
+ scope: SemanticScope,
184
+ context?: ScopeCapabilityContext,
185
+ ): ScopeCapabilityVerdict {
186
+ const guard = guardBlocker(scope);
187
+ if (guard) return blocked(guard);
188
+
189
+ const contentControls = contentControlBlockers(context);
190
+ if (contentControls.length > 0) {
191
+ return blocked("preserve:content-control", contentControls);
192
+ }
193
+
194
+ if (!PARAGRAPH_LIKE.has(scope.kind)) {
195
+ const reason =
196
+ scope.kind === "scope" && scope.replaceability.reason
197
+ ? MULTI_PARAGRAPH_REPLACEMENT_REFUSAL
198
+ : `compile-refused:${scope.kind}`;
199
+ return unsupported(
200
+ reason,
201
+ scope.kind === "scope"
202
+ ? multiParagraphReplacementBlockers("text")
203
+ : [reason],
204
+ evidenceWarnings(context),
205
+ );
206
+ }
207
+
208
+ if (
209
+ scope.replaceability.level === "blocked" ||
210
+ scope.replaceability.level === "preserve-only" ||
211
+ scope.replaceability.level === "formatting-only"
212
+ ) {
213
+ const reason = scope.replaceability.reason
214
+ ? `replaceability:${scope.replaceability.reason}`
215
+ : `replaceability:${scope.replaceability.level}`;
216
+ return unsupported(reason);
217
+ }
218
+
219
+ return supported(
220
+ "compile-supported:paragraph-like:text-replace",
221
+ [
222
+ ...(scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : []),
223
+ ...evidenceWarnings(context),
224
+ ],
225
+ );
226
+ }
227
+
228
+ function replaceFragmentCapability(
229
+ scope: SemanticScope,
230
+ context?: ScopeCapabilityContext,
231
+ ): ScopeCapabilityVerdict {
232
+ const guard = guardBlocker(scope);
233
+ if (guard) return blocked(guard);
234
+
235
+ const contentControls = contentControlBlockers(context);
236
+ if (contentControls.length > 0) {
237
+ return blocked("preserve:content-control", contentControls);
238
+ }
239
+
240
+ if (!PARAGRAPH_LIKE.has(scope.kind)) {
241
+ const reason =
242
+ scope.kind === "scope" && scope.replaceability.reason
243
+ ? MULTI_PARAGRAPH_REPLACEMENT_REFUSAL
244
+ : `compile-refused:${scope.kind}`;
245
+ return unsupported(
246
+ reason,
247
+ scope.kind === "scope"
248
+ ? multiParagraphReplacementBlockers("fragment")
249
+ : [reason],
250
+ evidenceWarnings(context),
251
+ );
252
+ }
253
+
254
+ if (scope.workflow.effectiveMode === "suggest") {
255
+ return unsupported(
256
+ `compile-refused:${scope.kind}:structured-suggesting-not-implemented`,
257
+ [`compile-refused:${scope.kind}:structured-suggesting-not-implemented`],
258
+ ["guard:suggest-mode"],
259
+ );
260
+ }
261
+
262
+ return supported(
263
+ "compile-supported:paragraph-like:fragment-replace",
264
+ evidenceWarnings(context),
265
+ );
266
+ }
267
+
268
+ function formattingCapability(
269
+ scope: SemanticScope,
270
+ context?: ScopeCapabilityContext,
271
+ ): ScopeCapabilityVerdict {
272
+ const guard = guardBlocker(scope);
273
+ if (guard) return blocked(guard);
274
+
275
+ if (!FORMATTING_SUPPORTED.has(scope.kind)) {
276
+ return unsupported(`compile-refused:${scope.kind}:formatting-not-implemented`);
277
+ }
278
+
279
+ if (scope.workflow.effectiveMode === "suggest") {
280
+ return unsupported(
281
+ `compile-refused:${scope.kind}:formatting-suggesting-not-implemented`,
282
+ [`compile-refused:${scope.kind}:formatting-suggesting-not-implemented`],
283
+ ["guard:suggest-mode"],
284
+ );
285
+ }
286
+
287
+ return supported("compile-supported:scope-formatting", evidenceWarnings(context));
288
+ }
289
+
290
+ function metadataCapability(scope: SemanticScope): ScopeCapabilityVerdict {
291
+ if (METADATA_SUPPORTED.has(scope.kind)) {
292
+ return supported(
293
+ scope.kind === "table" ||
294
+ scope.kind === "table-row" ||
295
+ scope.kind === "table-cell"
296
+ ? "metadata-supported:derived-table-family"
297
+ : "metadata-supported:scope-id",
298
+ );
299
+ }
300
+ return unsupported(`scope-not-resolvable:${scope.handle.scopeId}`);
301
+ }
302
+
303
+ export function deriveScopeCapabilities(
304
+ scope: SemanticScope,
305
+ context: ScopeCapabilityContext = {},
306
+ ): ScopeCapabilities {
307
+ return {
308
+ canReplaceText: replaceTextCapability(scope, context),
309
+ canReplaceFragment: replaceFragmentCapability(scope, context),
310
+ canInsertBefore: insertCapability(scope, "before", context),
311
+ canInsertAfter: insertCapability(scope, "after", context),
312
+ canApplyFormatting: formattingCapability(scope, context),
313
+ canClearFormattingLayer: formattingCapability(scope, context),
314
+ canAttachMetadata: metadataCapability(scope),
315
+ };
316
+ }
@@ -17,6 +17,7 @@ import type {
17
17
  WorkflowMetadataEntry,
18
18
  WorkflowOverlay,
19
19
  } from "./_scope-dependencies.ts";
20
+ import type { ScopeGeometryEvidenceProvider } from "./geometry-evidence.ts";
20
21
 
21
22
  import {
22
23
  buildParagraphIndexMap,
@@ -26,6 +27,7 @@ import {
26
27
  import type { EnumeratedScope } from "./enumerate-scopes.ts";
27
28
  import { enumerateScopes } from "./enumerate-scopes.ts";
28
29
  import { composeEvidence } from "./evidence.ts";
30
+ import type { ScopeLayoutEvidenceProvider } from "./layout-evidence.ts";
29
31
  import type {
30
32
  ScopeBundle,
31
33
  ScopeBundleNeighborhood,
@@ -47,6 +49,16 @@ export interface ScopeBundleInputs {
47
49
  * to walk the metadata snapshot.
48
50
  */
49
51
  readonly workflowMetadataEntries?: readonly WorkflowMetadataEntry[];
52
+ /**
53
+ * Optional Layer-05 replacement-envelope read seam. When omitted, bundle
54
+ * evidence records geometry as unavailable rather than synthesizing rects.
55
+ */
56
+ readonly geometry?: ScopeGeometryEvidenceProvider;
57
+ /**
58
+ * Optional lower-layer layout evidence seam. When absent, bundle evidence
59
+ * records layout as unavailable instead of deriving page slices in L08.
60
+ */
61
+ readonly layout?: ScopeLayoutEvidenceProvider;
50
62
  }
51
63
 
52
64
  /**
@@ -126,6 +138,8 @@ export function compileScopeBundle(
126
138
  ...(inputs.workflowMetadataEntries
127
139
  ? { workflowMetadataEntries: inputs.workflowMetadataEntries }
128
140
  : {}),
141
+ ...(inputs.geometry ? { geometry: inputs.geometry } : {}),
142
+ ...(inputs.layout ? { layout: inputs.layout } : {}),
129
143
  });
130
144
  return {
131
145
  scope,