@beyondwork/docx-react-component 1.0.67 → 1.0.70

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 (384) hide show
  1. package/README.md +75 -932
  2. package/package.json +26 -27
  3. package/src/api/anchor-conversion.ts +43 -0
  4. package/src/api/editor-state-types.ts +2 -1
  5. package/src/api/public-types.ts +504 -101
  6. package/src/api/session-state.ts +4 -0
  7. package/src/api/v3/README.md +91 -0
  8. package/src/api/v3/_create.ts +146 -0
  9. package/src/api/v3/_layer-metadata.ts +362 -0
  10. package/src/api/v3/_mocks.ts +84 -0
  11. package/src/api/v3/_runtime-handle.ts +162 -0
  12. package/src/api/v3/_ux-response.ts +73 -0
  13. package/src/api/v3/ai/_metadata-audit.ts +225 -0
  14. package/src/api/v3/ai/attach.ts +235 -0
  15. package/src/api/v3/ai/bundle.ts +132 -0
  16. package/src/api/v3/ai/explain.ts +144 -0
  17. package/src/api/v3/ai/export.ts +54 -0
  18. package/src/api/v3/ai/inspect.ts +118 -0
  19. package/src/api/v3/ai/policy.ts +77 -0
  20. package/src/api/v3/ai/replacement.ts +341 -0
  21. package/src/api/v3/ai/resolve.ts +133 -0
  22. package/src/api/v3/index.ts +79 -0
  23. package/src/api/v3/runtime/chart.ts +310 -0
  24. package/src/api/v3/runtime/clipboard.ts +81 -0
  25. package/src/api/v3/runtime/collab.ts +331 -0
  26. package/src/api/v3/runtime/content.ts +236 -0
  27. package/src/api/v3/runtime/document.ts +282 -0
  28. package/src/api/v3/runtime/formatting.ts +186 -0
  29. package/src/api/v3/runtime/geometry.ts +349 -0
  30. package/src/api/v3/runtime/layout.ts +108 -0
  31. package/src/api/v3/runtime/review.ts +129 -0
  32. package/src/api/v3/runtime/search.ts +74 -0
  33. package/src/api/v3/runtime/table.ts +63 -0
  34. package/src/api/v3/runtime/workflow.ts +434 -0
  35. package/src/api/v3/ui/_context.ts +86 -0
  36. package/src/api/v3/ui/_create.ts +65 -0
  37. package/src/api/v3/ui/_types.ts +520 -0
  38. package/src/api/v3/ui/chrome-composition.ts +342 -0
  39. package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
  40. package/src/api/v3/ui/chrome.ts +476 -0
  41. package/src/api/v3/ui/debug.ts +124 -0
  42. package/src/api/v3/ui/index.ts +64 -0
  43. package/src/api/v3/ui/overlays-visibility.ts +170 -0
  44. package/src/api/v3/ui/overlays.ts +427 -0
  45. package/src/api/v3/ui/scope.ts +71 -0
  46. package/src/api/v3/ui/session.ts +100 -0
  47. package/src/api/v3/ui/surface.ts +170 -0
  48. package/src/api/v3/ui/viewport.ts +303 -0
  49. package/src/core/commands/index.ts +28 -6
  50. package/src/core/commands/list-commands.ts +3 -2
  51. package/src/core/commands/section-layout-commands.ts +9 -8
  52. package/src/core/schema/text-schema.ts +16 -0
  53. package/src/core/selection/mapping.ts +33 -72
  54. package/src/core/state/editor-state.ts +96 -189
  55. package/src/index.ts +23 -4
  56. package/src/io/chart-preview-resolver.ts +1 -1
  57. package/src/io/docx-session.ts +36 -4797
  58. package/src/io/export/build-app-properties-xml.ts +1 -1
  59. package/src/io/export/serialize-comments.ts +1 -1
  60. package/src/io/export/serialize-headers-footers.ts +6 -1
  61. package/src/io/export/serialize-main-document.ts +45 -0
  62. package/src/io/export/serialize-run-formatting.ts +17 -2
  63. package/src/io/export/twip.ts +1 -1
  64. package/src/io/normalize/normalize-text.ts +27 -20
  65. package/src/io/ooxml/chart/parse-series.ts +1 -1
  66. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  67. package/src/io/ooxml/chart/types.ts +1 -1
  68. package/src/io/ooxml/classify-embedding.ts +83 -33
  69. package/src/io/ooxml/parse-fill.ts +1 -1
  70. package/src/io/ooxml/parse-main-document.ts +71 -1
  71. package/src/io/ooxml/parse-object.ts +14 -10
  72. package/src/io/ooxml/parse-run-formatting.ts +47 -1
  73. package/src/io/ooxml/property-grab-bag.ts +2 -2
  74. package/src/io/ooxml/units.ts +11 -0
  75. package/src/io/ooxml/workflow-payload.ts +282 -7
  76. package/src/model/anchor.ts +85 -0
  77. package/src/model/canonical-document.ts +351 -15
  78. package/src/model/chart-types.ts +1 -1
  79. package/src/model/layout/index.ts +83 -0
  80. package/src/model/layout/page-graph-types.ts +181 -0
  81. package/src/model/layout/page-layout-snapshot.ts +105 -0
  82. package/src/model/layout/resolved-layout-types.ts +47 -0
  83. package/src/model/layout/runtime-page-graph-types.ts +102 -0
  84. package/src/model/paragraph-scope-ids.ts +72 -0
  85. package/src/model/review/comment-types.ts +112 -0
  86. package/src/model/review/index.ts +2 -0
  87. package/src/model/review/revision-types.ts +215 -0
  88. package/src/model/snapshot.ts +32 -0
  89. package/src/review/store/comment-store.ts +21 -47
  90. package/src/review/store/revision-types.ts +40 -198
  91. package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
  92. package/src/runtime/collab/runtime-collab-sync.ts +13 -3
  93. package/src/runtime/collab-session.ts +1 -1
  94. package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
  95. package/src/runtime/debug/event-ring-buffer.ts +64 -0
  96. package/src/runtime/debug/probability-sampler.ts +18 -0
  97. package/src/runtime/debug/runtime-debug-facet.ts +67 -0
  98. package/src/runtime/debug/stage-tokens.ts +31 -0
  99. package/src/runtime/debug/telemetry-bus.ts +271 -0
  100. package/src/runtime/debug/types.ts +275 -0
  101. package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
  102. package/src/runtime/document-layout.ts +8 -6
  103. package/src/runtime/document-runtime.ts +843 -1141
  104. package/src/runtime/document-search.ts +1 -1
  105. package/src/runtime/edit-ops/index.ts +1 -1
  106. package/src/runtime/external-send-runtime.ts +1 -1
  107. package/src/runtime/formatting/document-lookup.ts +235 -0
  108. package/src/runtime/formatting/field/registry.ts +41 -0
  109. package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
  110. package/src/runtime/formatting/font-resolution.ts +83 -0
  111. package/src/runtime/formatting/formatting-context.ts +903 -0
  112. package/src/runtime/formatting/formatting-types.ts +157 -0
  113. package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
  114. package/src/runtime/formatting/index.ts +125 -0
  115. package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
  116. package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
  117. package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
  118. package/src/runtime/formatting/projector.ts +75 -0
  119. package/src/runtime/formatting/resolve-effective.ts +407 -0
  120. package/src/runtime/formatting/revision-display.ts +105 -0
  121. package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
  122. package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
  123. package/src/runtime/formatting/telemetry-bridge.ts +106 -0
  124. package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
  125. package/src/runtime/geometry/caret-geometry.ts +164 -0
  126. package/src/runtime/geometry/geometry-facet.ts +364 -0
  127. package/src/runtime/geometry/geometry-types.ts +256 -0
  128. package/src/runtime/geometry/hit-test.ts +125 -0
  129. package/src/runtime/geometry/index.ts +71 -0
  130. package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
  131. package/src/runtime/geometry/invalidation.ts +35 -0
  132. package/src/runtime/geometry/object-handles.ts +77 -0
  133. package/src/runtime/geometry/overlay-rects.ts +85 -0
  134. package/src/runtime/geometry/project-anchors.ts +100 -0
  135. package/src/runtime/geometry/project-fragments.ts +216 -0
  136. package/src/runtime/geometry/projector.ts +129 -0
  137. package/src/runtime/geometry/replacement-envelope.ts +130 -0
  138. package/src/runtime/geometry/viewport.ts +218 -0
  139. package/src/runtime/layout/compat-input-ledger.ts +211 -0
  140. package/src/runtime/layout/index.ts +6 -1
  141. package/src/runtime/layout/inert-layout-facet.ts +12 -7
  142. package/src/runtime/layout/layout-engine-instance.ts +189 -11
  143. package/src/runtime/layout/layout-engine-version.ts +450 -1
  144. package/src/runtime/layout/layout-facet-types.ts +60 -0
  145. package/src/runtime/layout/layout-measurement-provider.ts +13 -0
  146. package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
  147. package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
  148. package/src/runtime/layout/page-graph.ts +62 -209
  149. package/src/runtime/layout/page-story-resolver.ts +7 -12
  150. package/src/runtime/layout/paginated-layout-engine.ts +186 -11
  151. package/src/runtime/layout/project-block-fragments.ts +11 -0
  152. package/src/runtime/layout/projector.ts +90 -0
  153. package/src/runtime/layout/public-facet.ts +187 -442
  154. package/src/runtime/layout/resolved-formatting-state.ts +158 -26
  155. package/src/runtime/layout/table-render-plan.ts +1 -1
  156. package/src/runtime/prerender/cache-envelope.ts +6 -1
  157. package/src/runtime/prerender/prerender-document.ts +18 -23
  158. package/src/runtime/render/decoration-resolver.ts +1 -1
  159. package/src/runtime/render/render-frame-types.ts +20 -0
  160. package/src/runtime/render/render-kernel.ts +94 -25
  161. package/src/runtime/scopes/_formatting-seam.ts +262 -0
  162. package/src/runtime/scopes/_scope-dependencies.ts +49 -0
  163. package/src/runtime/scopes/action-validation.ts +356 -0
  164. package/src/runtime/scopes/attach-explanation.ts +102 -0
  165. package/src/runtime/scopes/audit-bundle.ts +71 -0
  166. package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
  167. package/src/runtime/scopes/compile-scope.ts +262 -0
  168. package/src/runtime/scopes/compiler-service.ts +431 -0
  169. package/src/runtime/scopes/create-issue.ts +107 -0
  170. package/src/runtime/scopes/enumerate-scopes.ts +543 -0
  171. package/src/runtime/scopes/evidence.ts +233 -0
  172. package/src/runtime/scopes/index.ts +150 -0
  173. package/src/runtime/scopes/position-map.ts +214 -0
  174. package/src/runtime/scopes/preservation-boundary.ts +91 -0
  175. package/src/runtime/scopes/projector.ts +49 -0
  176. package/src/runtime/scopes/replaceability.ts +87 -0
  177. package/src/runtime/scopes/replacement/apply.ts +228 -0
  178. package/src/runtime/scopes/replacement/compile.ts +59 -0
  179. package/src/runtime/scopes/replacement/propose.ts +42 -0
  180. package/src/runtime/scopes/resolve-reference.ts +347 -0
  181. package/src/runtime/scopes/review-bundle.ts +141 -0
  182. package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
  183. package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
  184. package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
  185. package/src/runtime/scopes/scope-kinds/field.ts +65 -0
  186. package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
  187. package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
  188. package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
  189. package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
  190. package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
  191. package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
  192. package/src/runtime/scopes/scope-kinds/table.ts +55 -0
  193. package/src/runtime/scopes/scope-range.ts +208 -0
  194. package/src/runtime/scopes/semantic-scope-types.ts +454 -0
  195. package/src/runtime/scopes/workflow-overlap.ts +92 -0
  196. package/src/runtime/selection/index.ts +1 -1
  197. package/src/runtime/structure-ops/fragment-insert.ts +1 -1
  198. package/src/runtime/structure-ops/index.ts +1 -1
  199. package/src/runtime/surface-projection.ts +232 -262
  200. package/src/runtime/units.ts +4 -2
  201. package/src/runtime/workflow/coordinator.ts +1348 -0
  202. package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
  203. package/src/runtime/workflow/index.ts +25 -0
  204. package/src/runtime/workflow/markup-mode-policy.ts +98 -0
  205. package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
  206. package/src/runtime/workflow/metadata-persistence.ts +306 -0
  207. package/src/runtime/workflow/metadata-writer.ts +123 -0
  208. package/src/runtime/workflow/overlay-store.ts +690 -0
  209. package/src/runtime/workflow/projector.ts +127 -0
  210. package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
  211. package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
  212. package/src/runtime/workflow/rail/types.ts +198 -0
  213. package/src/runtime/workflow/scope-rail-composer.ts +39 -0
  214. package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
  215. package/src/runtime/workflow/scope-writer.ts +188 -0
  216. package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
  217. package/src/runtime/workflow/visibility-policy.ts +129 -0
  218. package/src/session/_sync-legacy.ts +66 -0
  219. package/src/session/export/embedded-reconstitute.ts +104 -0
  220. package/src/session/export/export-diagnostics.ts +85 -0
  221. package/src/session/export/export-validation.ts +110 -0
  222. package/src/session/export/index.ts +34 -0
  223. package/src/session/export/preservation-reattach.ts +30 -0
  224. package/src/session/export/serialize-dispatch.ts +165 -0
  225. package/src/session/export/stateful-export-pipeline.ts +432 -0
  226. package/src/session/export/stateful-export.ts +684 -0
  227. package/src/session/import/canonical-assembly.ts +227 -0
  228. package/src/session/import/diagnostics-session.ts +54 -0
  229. package/src/session/import/embedded-discovery.ts +225 -0
  230. package/src/session/import/embedded-offload.ts +337 -0
  231. package/src/session/import/import-diagnostics.ts +69 -0
  232. package/src/session/import/loader-types.ts +313 -0
  233. package/src/session/import/loader.ts +1834 -0
  234. package/src/session/import/normalize.ts +195 -0
  235. package/src/session/import/package-parts.ts +217 -0
  236. package/src/session/import/package-read.ts +195 -0
  237. package/src/session/import/parse-orchestration.ts +105 -0
  238. package/src/session/import/part-constants.ts +70 -0
  239. package/src/session/import/part-discovery.ts +94 -0
  240. package/src/session/import/preservation-index.ts +46 -0
  241. package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
  242. package/src/session/import/review-import.ts +508 -0
  243. package/src/session/import/styles-consolidation.ts +281 -0
  244. package/src/session/import/workflow-scope-import.ts +256 -0
  245. package/src/session/index.ts +37 -0
  246. package/src/session/session-state.ts +69 -0
  247. package/src/session/session.ts +532 -0
  248. package/src/session/shared/protection.ts +228 -0
  249. package/src/session/shared/session-utils.ts +82 -0
  250. package/src/session/types.ts +499 -0
  251. package/src/shell/chart-snapshots.ts +96 -0
  252. package/src/shell/media-previews.ts +85 -0
  253. package/src/shell/overlay-anchor-bridge.ts +53 -0
  254. package/src/shell/paste-adapter.ts +23 -0
  255. package/src/shell/ref-commands.ts +1697 -0
  256. package/src/shell/ref-utilities.ts +48 -0
  257. package/src/shell/search.ts +51 -0
  258. package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
  259. package/src/shell/ui-subscriber-channels.ts +81 -0
  260. package/src/shell/use-collab-sync.ts +116 -0
  261. package/src/ui/WordReviewEditor.tsx +496 -2051
  262. package/src/ui/editor-shell-view.tsx +30 -1
  263. package/src/ui/editor-surface-controller.tsx +49 -1
  264. package/src/ui/headless/revision-decoration-model.ts +83 -0
  265. package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
  266. package/src/ui/headless/scoped-chrome-policy.ts +2 -2
  267. package/src/ui/headless/selection-tool-context.ts +1 -1
  268. package/src/ui/headless/selection-tool-resolver.ts +1 -1
  269. package/src/ui/runtime-shortcut-dispatch.ts +46 -1
  270. package/src/ui/ui-controller-factory.ts +221 -0
  271. package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
  272. package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
  273. package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
  274. package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
  275. package/src/ui-tailwind/chart/render/area.tsx +3 -3
  276. package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
  277. package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
  278. package/src/ui-tailwind/chart/render/combo.tsx +2 -2
  279. package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
  280. package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
  281. package/src/ui-tailwind/chart/render/line.tsx +3 -3
  282. package/src/ui-tailwind/chart/render/pie.tsx +6 -6
  283. package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
  284. package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
  285. package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
  286. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
  287. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
  288. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
  289. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
  290. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
  291. package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
  292. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
  293. package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
  294. package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
  295. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
  296. package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
  297. package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
  298. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
  299. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
  300. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
  301. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
  302. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
  303. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
  304. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
  305. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
  306. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
  307. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
  308. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
  309. package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
  310. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
  311. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
  312. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
  313. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
  314. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
  315. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
  316. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
  317. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
  318. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
  319. package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
  320. package/src/ui-tailwind/debug/README.md +57 -0
  321. package/src/ui-tailwind/debug/index.ts +3 -0
  322. package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
  323. package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
  324. package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
  325. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
  326. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
  327. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
  328. package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
  329. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
  330. package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
  331. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
  332. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
  333. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
  334. package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
  335. package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
  336. package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
  337. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
  338. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
  339. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
  340. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
  341. package/src/ui-tailwind/index.ts +0 -5
  342. package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
  343. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
  344. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
  345. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
  346. package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
  347. package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
  348. package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
  349. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
  350. package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
  351. package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
  352. package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
  353. package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
  354. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
  355. package/src/ui-tailwind/review-workspace/types.ts +408 -0
  356. package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
  357. package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
  358. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
  359. package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
  360. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
  361. package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
  362. package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
  363. package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
  364. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
  365. package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
  366. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
  367. package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
  368. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
  369. package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
  370. package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
  371. package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
  372. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
  373. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  374. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
  375. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
  376. package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
  377. package/src/ui-tailwind/ui-api-context.tsx +43 -0
  378. package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
  379. package/src/validation/compatibility-engine.ts +6 -6
  380. package/src/runtime/styles-cascade.ts +0 -33
  381. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
  382. /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
  383. /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
  384. /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Scope-local formatting seam — **bounded semantic-summary projection**,
3
+ * NOT formatting semantics.
4
+ *
5
+ * ## Contract (tightened 2026-04-22)
6
+ *
7
+ * Layer 08 does NOT own formatting. Layer 03 owns formatting semantics
8
+ * (style cascade, theme colour resolution, run-level accurate mark
9
+ * computation, numbering-instance evaluation, etc.). This seam's job
10
+ * is narrow:
11
+ *
12
+ * 1. **Delegate the cascade-sensitive fields** — `paragraphStyleId`,
13
+ * `outlineLevel`, resolved `numbering.label` — to Layer 03's
14
+ * `resolveEffectiveFormatting` (`src/runtime/formatting/`).
15
+ * When `doc` is threaded, the seam is a pure pass-through for
16
+ * these fields. If Layer 03 disagrees with this seam on any of
17
+ * them, Layer 03 wins.
18
+ *
19
+ * 2. **Compute a scope-level summary** the compiler adds on top — and
20
+ * ONLY on top. Specifically:
21
+ * - `emphasis` — majority-emphasis aggregation across the
22
+ * paragraph's runs (scope-level; Layer 03 only emits per-run
23
+ * accurate marks).
24
+ * - `tableRole` (via `deriveTableCellRole`) — structural role
25
+ * derived from the row's `isHeader` + position.
26
+ * These summaries do NOT re-derive the cascade; they compose
27
+ * scope-level signal from canonical structure Layer 03 already
28
+ * resolves.
29
+ *
30
+ * Anything else — font resolution, character-pair widths, theme
31
+ * colours, list-item run marks, numbering instance advancement — is
32
+ * out of scope. Callers needing those go through Layer 03 directly.
33
+ *
34
+ * ## Fallback posture
35
+ *
36
+ * When the caller only has a bare `ParagraphNode` (no `doc` / no
37
+ * `paragraphIndex`), the seam returns a honest degraded projection
38
+ * built from paragraph fields only. The `formatting` fields the scope
39
+ * then carries are narrower but never wrong. Callers that require the
40
+ * full cascade-accurate projection must thread `doc` + `paragraphIndex`.
41
+ *
42
+ * ## Contracts it honors:
43
+ * F1 — single Layer 03 entry. We call `resolveEffectiveFormatting`
44
+ * for `{kind: "paragraph", blockId}` when `doc` is available.
45
+ * F2 — no state stored here. Every call is pure over its inputs.
46
+ * S9 — returns plain immutable values. No runtime-instance leakage.
47
+ */
48
+
49
+ import type {
50
+ CanonicalDocument,
51
+ ParagraphNode,
52
+ TableCellNode,
53
+ TableNode,
54
+ TableRowNode,
55
+ } from "../../model/canonical-document.ts";
56
+ import { resolveEffectiveFormatting } from "../formatting/resolve-effective.ts";
57
+
58
+ export interface EffectiveNumbering {
59
+ readonly numberingInstanceId: string;
60
+ readonly level: number;
61
+ readonly label?: string;
62
+ }
63
+
64
+ export interface EffectiveFormatting {
65
+ readonly paragraphStyleId?: string;
66
+ readonly emphasis: readonly string[];
67
+ readonly numbering?: EffectiveNumbering;
68
+ readonly outlineLevel?: number;
69
+ }
70
+
71
+ export interface ScopeFormattingInputs {
72
+ readonly paragraph: ParagraphNode;
73
+ /**
74
+ * Full canonical document. When present, the seam delegates cascade-
75
+ * sensitive fields (`paragraphStyleId`, `outlineLevel`, resolved
76
+ * `numbering.label`) to Layer 03's production entry. When absent, the
77
+ * seam returns the degraded projection built from bare paragraph
78
+ * fields only — still honest, just narrower.
79
+ */
80
+ readonly document?: CanonicalDocument;
81
+ /**
82
+ * Paragraph-type index matching Layer 03's `blockId` synthesis
83
+ * convention (`paragraph-${N}` counts paragraphs in `doc.content.children`
84
+ * order, not all blocks). Required alongside `document` — the seam
85
+ * calls `resolveEffectiveFormatting(doc, { kind: "paragraph", blockId })`.
86
+ */
87
+ readonly paragraphIndex?: number;
88
+ }
89
+
90
+ /**
91
+ * Majority-emphasis summary across the paragraph's text runs. This is a
92
+ * scope-level aggregation the AI context prompt reads ("is this paragraph
93
+ * mostly bold?"); Layer 03 owns the per-run cascade and does NOT express
94
+ * paragraph-wide emphasis as a derived field. Keeping this local is
95
+ * architecturally correct — it does not duplicate cascade logic.
96
+ */
97
+ function deriveEmphasisSummary(paragraph: ParagraphNode): string[] {
98
+ const counts = new Map<string, number>();
99
+ let runCount = 0;
100
+ for (const child of paragraph.children) {
101
+ if (child.type !== "text") continue;
102
+ runCount += 1;
103
+ if (!child.marks) continue;
104
+ for (const mark of child.marks) {
105
+ if (
106
+ mark.type === "bold" ||
107
+ mark.type === "italic" ||
108
+ mark.type === "underline" ||
109
+ mark.type === "strikethrough"
110
+ ) {
111
+ counts.set(mark.type, (counts.get(mark.type) ?? 0) + 1);
112
+ }
113
+ }
114
+ }
115
+ if (runCount === 0) return [];
116
+ const majority: string[] = [];
117
+ for (const [mark, count] of counts) {
118
+ if (count * 2 >= runCount) majority.push(mark);
119
+ }
120
+ return majority.sort();
121
+ }
122
+
123
+ /**
124
+ * Degraded-path outline level. Used ONLY when no canonical document is
125
+ * threaded — in that case the cascade-resolved outline level is not
126
+ * reachable; we expose the paragraph's own `outlineLevel` or a bounded
127
+ * `Heading\d` fallback parsed from the styleId. This is narrower than
128
+ * the Layer-03 cascade result (which follows style inheritance chains),
129
+ * and documented as such so downstream consumers can reason about the
130
+ * loss when `document` is omitted.
131
+ */
132
+ function degradedOutlineLevel(paragraph: ParagraphNode): number | undefined {
133
+ if (typeof paragraph.outlineLevel === "number") return paragraph.outlineLevel;
134
+ const match = /^heading(\d)$/i.exec(paragraph.styleId ?? "");
135
+ if (match) return Math.max(0, Number(match[1]) - 1);
136
+ return undefined;
137
+ }
138
+
139
+ function resolveViaLayer03(
140
+ doc: CanonicalDocument,
141
+ paragraph: ParagraphNode,
142
+ paragraphIndex: number,
143
+ ): Pick<EffectiveFormatting, "paragraphStyleId" | "outlineLevel" | "numbering"> {
144
+ const eff = resolveEffectiveFormatting(doc, {
145
+ kind: "paragraph",
146
+ blockId: `paragraph-${paragraphIndex}`,
147
+ });
148
+ if (eff.kind !== "paragraph") {
149
+ // Defensive: the façade always returns a paragraph branch for a
150
+ // paragraph nodeRef. If the shape drifts, degrade rather than crash.
151
+ return {
152
+ ...(paragraph.styleId ? { paragraphStyleId: paragraph.styleId } : {}),
153
+ ...(typeof paragraph.outlineLevel === "number"
154
+ ? { outlineLevel: paragraph.outlineLevel }
155
+ : {}),
156
+ ...(paragraph.numbering
157
+ ? {
158
+ numbering: {
159
+ numberingInstanceId: paragraph.numbering.numberingInstanceId,
160
+ level: paragraph.numbering.level,
161
+ },
162
+ }
163
+ : {}),
164
+ };
165
+ }
166
+ // `EffectiveParagraphFormatting` extends `CanonicalParagraphFormatting`
167
+ // (pPr body) — it does NOT expose `styleId` directly because a paragraph's
168
+ // styleId is a tree field on `ParagraphNode`, not a pPr property. The
169
+ // canonical source of the paragraph style id is `paragraph.styleId`; the
170
+ // cascade uses that id as an input, not an output. We surface the
171
+ // canonical field directly here.
172
+ const paragraphStyleId = paragraph.styleId;
173
+ const outlineLevel =
174
+ typeof eff.paragraph.outlineLevel === "number"
175
+ ? eff.paragraph.outlineLevel
176
+ : degradedOutlineLevel(paragraph);
177
+ // Numbering identity (instanceId + level) comes from the canonical
178
+ // paragraph field — Layer 03's `EffectiveNumbering` carries the resolved
179
+ // marker glyph + indentation, not the instance id. We combine the two
180
+ // here: identity from canonical, label from Layer 03's cascade.
181
+ let numbering: EffectiveNumbering | undefined;
182
+ const canonicalNum = paragraph.numbering;
183
+ if (canonicalNum) {
184
+ const label = eff.paragraph.numbering?.marker;
185
+ numbering = {
186
+ numberingInstanceId: canonicalNum.numberingInstanceId,
187
+ level: canonicalNum.level,
188
+ ...(label ? { label } : {}),
189
+ };
190
+ }
191
+ return {
192
+ ...(paragraphStyleId ? { paragraphStyleId } : {}),
193
+ ...(typeof outlineLevel === "number" ? { outlineLevel } : {}),
194
+ ...(numbering ? { numbering } : {}),
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Narrow paragraph-level projection used by paragraph / heading / list-item
200
+ * / clause scope kinds. Delegates cascade-sensitive fields to Layer 03
201
+ * when `inputs.document` is present; falls back to bare paragraph fields
202
+ * otherwise. Either way, `emphasis` is computed as a scope-level
203
+ * majority-summary — not a cascade result.
204
+ *
205
+ * Callers that pass `document` + `paragraphIndex` receive the cascade-
206
+ * accurate `paragraphStyleId` / `outlineLevel` / `numbering.label`.
207
+ * Callers that omit them receive a projection marked implicitly narrower
208
+ * (the caller is expected to set `partial: true` on the resulting
209
+ * `SemanticScope`).
210
+ */
211
+ export function resolveEffectiveFormattingForScope(
212
+ paragraphOrInputs: ParagraphNode | ScopeFormattingInputs,
213
+ ): EffectiveFormatting {
214
+ const inputs: ScopeFormattingInputs =
215
+ "paragraph" in paragraphOrInputs
216
+ ? paragraphOrInputs
217
+ : { paragraph: paragraphOrInputs };
218
+ const { paragraph, document, paragraphIndex } = inputs;
219
+ const emphasis = deriveEmphasisSummary(paragraph);
220
+ if (document && typeof paragraphIndex === "number") {
221
+ const base = resolveViaLayer03(document, paragraph, paragraphIndex);
222
+ return { emphasis, ...base };
223
+ }
224
+ const outlineLevel = degradedOutlineLevel(paragraph);
225
+ return {
226
+ emphasis,
227
+ ...(paragraph.styleId ? { paragraphStyleId: paragraph.styleId } : {}),
228
+ ...(paragraph.numbering
229
+ ? {
230
+ numbering: {
231
+ numberingInstanceId: paragraph.numbering.numberingInstanceId,
232
+ level: paragraph.numbering.level,
233
+ },
234
+ }
235
+ : {}),
236
+ ...(outlineLevel !== undefined ? { outlineLevel } : {}),
237
+ };
238
+ }
239
+
240
+ export interface TableCellRoleInput {
241
+ readonly row: TableRowNode;
242
+ readonly rowIndex: number;
243
+ readonly table: TableNode;
244
+ }
245
+
246
+ /**
247
+ * Structural role for a table cell. This is NOT a formatting cascade
248
+ * question — role derives from the row's `isHeader` flag + positional
249
+ * convention (first row → header, last row → footer, middle rows →
250
+ * body). Keeping the derivation local is architecturally correct;
251
+ * Layer 03's `resolveEffectiveFormatting` for `kind: "table-cell"`
252
+ * exposes cascade-resolved paragraph + run formatting, not row role.
253
+ */
254
+ export function deriveTableCellRole(
255
+ input: TableCellRoleInput,
256
+ _cell: TableCellNode,
257
+ ): "header" | "body" | "footer" {
258
+ if (input.row.isHeader === true) return "header";
259
+ if (input.rowIndex === 0) return "header";
260
+ if (input.rowIndex === input.table.rows.length - 1) return "footer";
261
+ return "body";
262
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Layer 08 · dependency bridge — upstream types from the shared
3
+ * vocabulary.
4
+ *
5
+ * The adversarial-close pass (2026-04-22) flagged that the compiler was
6
+ * reaching directly into `src/api/public-types.ts` from many files.
7
+ * Honest fix: **every upstream type Layer 08 depends on crosses through
8
+ * this single bridge**. All other files under `src/runtime/scopes/**`
9
+ * import from this module. The compiler-purity guard now enforces the
10
+ * choke point — only this file is allowed to import from
11
+ * `src/api/public-types.ts`.
12
+ *
13
+ * This does NOT eliminate the upward dependency (a full move of these
14
+ * shared-vocabulary types into a runtime-internal home would ripple
15
+ * through ~30 runtime files outside Layer 08's scope; see `docs/plans/
16
+ * cross-layer-coord-08.md` item 5 for the deferred refactor path). It
17
+ * DOES:
18
+ *
19
+ * 1. Reduce the leak from ~11 import sites to 1.
20
+ * 2. Document explicitly which shared-vocabulary types Layer 08 depends on.
21
+ * 3. Make a future type-migration a one-file change.
22
+ *
23
+ * If a new type is needed from `src/api/public-types.ts`, add the
24
+ * re-export here and nowhere else. If a new type CAN be defined in
25
+ * runtime-internal code, prefer that.
26
+ */
27
+
28
+ export type {
29
+ CompatibilityFeatureEntry,
30
+ CompatibilityReport,
31
+ EditorAnchorProjection,
32
+ EditorStoryTarget,
33
+ EffectiveSelectionMode,
34
+ InteractionGuardSnapshot,
35
+ WorkflowBlockedCommandReason,
36
+ WorkflowMetadataEntry,
37
+ WorkflowMetadataSnapshot,
38
+ WorkflowOverlay,
39
+ WorkflowScope,
40
+ WorkflowScopeMetadataField,
41
+ } from "../../api/public-types.ts";
42
+
43
+ /**
44
+ * The session-state shape the compiler-service reads. Declared as a
45
+ * Pick rather than re-exporting `EditorSessionState` whole so the
46
+ * dependency surface stays narrow to exactly the one field used.
47
+ */
48
+ import type { EditorSessionState } from "../../api/public-types.ts";
49
+ export type ScopeSessionState = Pick<EditorSessionState, "documentId">;
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Layer 08 · Slice 4 — unified action-validation composer.
3
+ *
4
+ * Composes four independent policy surfaces into one `ValidationResult`
5
+ * verdict that callers read before applying a replacement:
6
+ *
7
+ * 1. workflow guard — `runtime.getInteractionGuardSnapshot()` plus
8
+ * the scope's overlay-overlap projection
9
+ * (`scope.workflow.effectiveMode` +
10
+ * `scope.workflow.blockedReasons`). Scope-level
11
+ * posture is authoritative because guard-snapshot
12
+ * is selection-scoped; the scope may live in a
13
+ * view-mode overlay even when the cursor does not.
14
+ * 2. preservation — `computePreservationVerdict` reads the document
15
+ * preservation store + scope markers inside the
16
+ * range. Any overlap is a blocker.
17
+ * 3. compatibility — `runtime.getCompatibilityReport()` — current
18
+ * `unsupported-fatal` feature entries surface as
19
+ * warnings (`source: "compat"`); `blockExport`
20
+ * surfaces as a blocker so apply never produces
21
+ * an undeliverable document.
22
+ * 4. AI-action policy — `getAIActionPolicy(actionId)` — `blocked` /
23
+ * `unsupported` support is a blocker; `high` /
24
+ * `critical` risk or `userConfirmation`
25
+ * requirements surface as `approval.required`.
26
+ *
27
+ * Contract highlights (see architecture 08 §"Contracts" + Slice 4
28
+ * handover):
29
+ * - `safe === true` iff `blockedReasons.length === 0`. Warnings do
30
+ * NOT degrade safety — they surface as advisory.
31
+ * - `approval.required` is orthogonal to `safe`. A low-risk action can
32
+ * still need approval; a blocked action can still
33
+ * report `approval: undefined` (the block is the
34
+ * primary signal).
35
+ * - `blockedReasons` strings use a stable prefix grammar:
36
+ * `guard:…` / `preserve:…` / `compat:…` / `policy:…`. UIs and BW
37
+ * workblocks routing keys match on the prefix; grammar is a
38
+ * compatibility event.
39
+ * - Every `ValidationIssue` carries a `source` tag so consumers can
40
+ * route warnings by origin.
41
+ * - Deterministic (S3): no wall-clock reads, no PRNG, no state mutation.
42
+ *
43
+ * Slice 4 ships the composer + v3 adapter graduation for
44
+ * `ai.validateReplacementScope`. Slice 5 will consume `composeScopeValidation`
45
+ * from `replacement/apply.ts` before dispatching any runtime plan.
46
+ */
47
+
48
+ import type { CanonicalDocument } from "../../model/canonical-document.ts";
49
+ import type {
50
+ CompatibilityReport,
51
+ InteractionGuardSnapshot,
52
+ } from "./_scope-dependencies.ts";
53
+ import { getAIActionPolicy, type AIAction } from "../workflow/ai-action-policy.ts";
54
+
55
+ import type { EnumeratedScope } from "./enumerate-scopes.ts";
56
+ import {
57
+ buildScopePositionMap,
58
+ type ScopePositionMap,
59
+ } from "./position-map.ts";
60
+ import {
61
+ computePreservationVerdict,
62
+ } from "./preservation-boundary.ts";
63
+ import { resolveScopeRange } from "./scope-range.ts";
64
+ import type {
65
+ ReplacementOperationKind,
66
+ ReplacementScope,
67
+ SemanticScope,
68
+ ValidationApproval,
69
+ ValidationIssue,
70
+ ValidationResult,
71
+ } from "./semantic-scope-types.ts";
72
+
73
+ /**
74
+ * Minimal runtime surface the composer reads. Declared inline rather
75
+ * than `Pick<RuntimeApiHandle, …>` because the scope compiler must not
76
+ * import from `src/api/v3/**` (purity guard). The v3 `RuntimeApiHandle`
77
+ * naturally satisfies this interface because its members carry the
78
+ * same `InteractionGuardSnapshot` / `CompatibilityReport` shapes from
79
+ * `src/api/public-types.ts`.
80
+ */
81
+ export interface ScopeValidationRuntime {
82
+ getInteractionGuardSnapshot(): InteractionGuardSnapshot;
83
+ getCompatibilityReport(): CompatibilityReport;
84
+ }
85
+
86
+ export interface ComposeScopeValidationInputs {
87
+ readonly scope: SemanticScope;
88
+ readonly operation: ReplacementOperationKind;
89
+ readonly proposedContent: ReplacementScope["proposedContent"];
90
+ readonly runtime: ScopeValidationRuntime;
91
+ /**
92
+ * Canonical document — enables preservation-boundary check. Omitting
93
+ * this field skips the preservation step (no blockers contributed).
94
+ */
95
+ readonly document?: CanonicalDocument;
96
+ /**
97
+ * Optional precomputed position map — if omitted, built from
98
+ * `document` (allocation cost) or skipped when document is absent.
99
+ */
100
+ readonly positionMap?: ScopePositionMap;
101
+ /**
102
+ * Optional enumerated-scope entry, used to resolve the scope's
103
+ * canonical range when the scope was compiled with a position map the
104
+ * caller no longer has. Omit to use `scope.handle` + position-map
105
+ * marker lookup only.
106
+ */
107
+ readonly enumeratedScope?: EnumeratedScope;
108
+ /**
109
+ * AI-action-policy id. Overrides the operation-derived default. When
110
+ * the id is unknown to `getAIActionPolicy`, the policy step contributes
111
+ * no blockers / warnings / approval.
112
+ */
113
+ readonly actionId?: AIAction;
114
+ }
115
+
116
+ /**
117
+ * Default action-id derivation when the caller did not provide one.
118
+ * Maps replacement-operation + proposed-content shape to the closest
119
+ * entry in the 37-op policy matrix.
120
+ */
121
+ function inferActionId(
122
+ operation: ReplacementOperationKind,
123
+ content: ReplacementScope["proposedContent"],
124
+ ): AIAction {
125
+ switch (operation) {
126
+ case "replace":
127
+ return content.kind === "text" ? "rewrite_paragraph" : "generate_text";
128
+ case "insert-before":
129
+ case "insert-after":
130
+ return "generate_text";
131
+ case "split":
132
+ return "rewrite_paragraph";
133
+ case "annotate":
134
+ return "suggest_comment_response";
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Scope-level guard step — consults both the scope's own overlay-overlap
140
+ * projection (deepest, most specific signal) and the runtime's
141
+ * selection-scoped `InteractionGuardSnapshot` (catches global posture
142
+ * overrides like read-only + document-mode). The most restrictive signal
143
+ * produces the blocker.
144
+ */
145
+ function collectGuardVerdict(
146
+ scope: SemanticScope,
147
+ runtime: ComposeScopeValidationInputs["runtime"],
148
+ blockedReasons: string[],
149
+ warnings: ValidationIssue[],
150
+ ): void {
151
+ const scopeMode = scope.workflow.effectiveMode;
152
+ if (scopeMode === "view") {
153
+ blockedReasons.push("guard:view-mode-active");
154
+ warnings.push({
155
+ code: "guard:view-mode-active",
156
+ message: "scope is inside a view-mode workflow overlay",
157
+ source: "guard",
158
+ });
159
+ } else if (scopeMode === "blocked") {
160
+ // Coord-06 §13e — `guard:blocked` becomes typed `guard:block-<reason>`
161
+ // so agents can route intelligently. This path is a defensive catchall
162
+ // (scope.workflow.effectiveMode is normally edit/suggest/comment/view;
163
+ // the overlay-compiler doesn't emit "blocked"). Mark with the
164
+ // specific sub-reason so the grammar is consistent across both
165
+ // emission sites.
166
+ blockedReasons.push("guard:block-scope-overlay");
167
+ warnings.push({
168
+ code: "guard:block-scope-overlay",
169
+ message: "scope is inside a blocked workflow overlay",
170
+ source: "guard",
171
+ });
172
+ } else if (scopeMode === "comment") {
173
+ blockedReasons.push("guard:comment-only");
174
+ warnings.push({
175
+ code: "guard:comment-only",
176
+ message: "scope is inside a comment-only workflow overlay",
177
+ source: "guard",
178
+ });
179
+ } else if (scopeMode === "suggest") {
180
+ // Suggest is not a blocker — the call site converts the replacement
181
+ // into a tracked-change in Slice 5. Still emit a warning so the
182
+ // caller can switch to a suggest-shaped apply without surprise.
183
+ warnings.push({
184
+ code: "guard:suggest-mode",
185
+ message:
186
+ "scope is in suggest mode; replacement must be issued as a tracked change",
187
+ source: "guard",
188
+ });
189
+ }
190
+ for (const reason of scope.workflow.blockedReasons ?? []) {
191
+ warnings.push({
192
+ code: `guard:${reason}`,
193
+ message: `workflow overlay signal: ${reason}`,
194
+ source: "guard",
195
+ });
196
+ }
197
+
198
+ const guard = runtime.getInteractionGuardSnapshot();
199
+ if (
200
+ guard.effectiveMode === "view" &&
201
+ !blockedReasons.includes("guard:view-mode-active")
202
+ ) {
203
+ blockedReasons.push("guard:view-mode-active");
204
+ }
205
+ if (guard.effectiveMode === "blocked") {
206
+ // Coord-06 §13e — promote the bare `guard:blocked` blocker to a typed
207
+ // `guard:block-<reason>` suffix so agents can route intelligently on
208
+ // boundary-paragraph / system-paragraph / read-only / protected-range
209
+ // situations. The specific sub-reason is the first code on
210
+ // `guard.blockedReasons` (coordinator's `evaluateBlockedReasons`
211
+ // output). When the array is empty (defensive — shouldn't happen;
212
+ // coordinator sets `effectiveMode: "blocked"` only when at least
213
+ // one blockedReason fires) fall back to `guard:block-unspecified`.
214
+ const primaryCode = guard.blockedReasons?.[0]?.code;
215
+ const suffix = typeof primaryCode === "string" && primaryCode.length > 0
216
+ ? primaryCode
217
+ : "unspecified";
218
+ const typedBlocker = `guard:block-${suffix}`;
219
+ if (!blockedReasons.some((existing) => existing === typedBlocker)) {
220
+ blockedReasons.push(typedBlocker);
221
+ }
222
+ }
223
+ for (const reason of guard.blockedReasons ?? []) {
224
+ // `reason` is a `WorkflowBlockedCommandReason` object — the legacy
225
+ // template emitted the bare object here, producing `guard:selection-
226
+ // [object Object]` in serialized warnings. Use `.code` so downstream
227
+ // receivers see a stable string identifier (§13e / coord-09 routing).
228
+ const code = typeof reason === "object" && reason !== null && "code" in reason
229
+ ? (reason as { code: unknown }).code
230
+ : reason;
231
+ const codeStr = typeof code === "string" ? code : JSON.stringify(code);
232
+ warnings.push({
233
+ code: `guard:selection-${codeStr}`,
234
+ message: `selection guard signal: ${codeStr}`,
235
+ source: "guard",
236
+ });
237
+ }
238
+ }
239
+
240
+ function collectPreservationVerdict(
241
+ inputs: ComposeScopeValidationInputs,
242
+ blockedReasons: string[],
243
+ ): void {
244
+ const { document, scope, positionMap } = inputs;
245
+ if (!document) return;
246
+ const pm = positionMap ?? buildScopePositionMap(document);
247
+ const range = inputs.enumeratedScope
248
+ ? resolveScopeRange(inputs.enumeratedScope, scope.handle, pm)
249
+ : scope.handle.stableRef.kind === "scope-id"
250
+ ? (pm.markerScopes.get(scope.handle.stableRef.value) ?? null)
251
+ : null;
252
+ const verdict = computePreservationVerdict(document, range, pm);
253
+ if (!verdict.replaceable) {
254
+ for (const reason of verdict.reasons) {
255
+ blockedReasons.push(`preserve:${reason}`);
256
+ }
257
+ }
258
+ }
259
+
260
+ function collectCompatibilityVerdict(
261
+ runtime: ComposeScopeValidationInputs["runtime"],
262
+ blockedReasons: string[],
263
+ warnings: ValidationIssue[],
264
+ ): void {
265
+ const report = runtime.getCompatibilityReport();
266
+ if (report.blockExport) {
267
+ blockedReasons.push("compat:block-export");
268
+ }
269
+ for (const entry of report.featureEntries) {
270
+ if (entry.featureClass === "unsupported-fatal") {
271
+ warnings.push({
272
+ code: `compat:unsupported-fatal-${entry.featureKey}`,
273
+ message: entry.message,
274
+ source: "compat",
275
+ });
276
+ }
277
+ }
278
+ }
279
+
280
+ function collectPolicyVerdict(
281
+ actionId: AIAction,
282
+ blockedReasons: string[],
283
+ warnings: ValidationIssue[],
284
+ ): ValidationApproval | undefined {
285
+ // `getAIActionPolicy` throws on unknown actions. The composer absorbs
286
+ // that as "no policy signal" rather than propagating — the caller's
287
+ // actionId choice is a best-effort mapping, not a contract.
288
+ let policy;
289
+ try {
290
+ policy = getAIActionPolicy(actionId);
291
+ } catch {
292
+ return undefined;
293
+ }
294
+
295
+ if (policy.support === "blocked") {
296
+ blockedReasons.push(`policy:blocked-${policy.action}`);
297
+ warnings.push({
298
+ code: `policy:blocked-${policy.action}`,
299
+ message: policy.rationale,
300
+ source: "policy",
301
+ });
302
+ } else if (policy.support === "unsupported") {
303
+ blockedReasons.push(`policy:unsupported-${policy.action}`);
304
+ warnings.push({
305
+ code: `policy:unsupported-${policy.action}`,
306
+ message: policy.rationale,
307
+ source: "policy",
308
+ });
309
+ }
310
+
311
+ // Note: `policy.constraints.preservedContentRespect` and
312
+ // `compatibilityValidation` are *enforcement requirements* — the
313
+ // composer already enforces both (preservation step + compatibility
314
+ // step emit the actual blockers/warnings when they fire). Surfacing
315
+ // them as always-on policy advisories would double-report, so we
316
+ // consult them implicitly through the other steps rather than
317
+ // emitting a `policy:*` warning here.
318
+
319
+ const needsApproval =
320
+ policy.risk === "high" ||
321
+ policy.risk === "critical" ||
322
+ policy.requirements.userConfirmation;
323
+ if (needsApproval) {
324
+ return { required: true, reason: policy.rationale };
325
+ }
326
+ return undefined;
327
+ }
328
+
329
+ /**
330
+ * Compose a single validation verdict for a proposed scope replacement.
331
+ * Synchronous, deterministic, free of side effects. Safe to call in hot
332
+ * paths; the composer reuses a single position-map when the caller
333
+ * provides one.
334
+ */
335
+ export function composeScopeValidation(
336
+ inputs: ComposeScopeValidationInputs,
337
+ ): ValidationResult {
338
+ const blockedReasons: string[] = [];
339
+ const warnings: ValidationIssue[] = [];
340
+
341
+ collectGuardVerdict(inputs.scope, inputs.runtime, blockedReasons, warnings);
342
+ collectPreservationVerdict(inputs, blockedReasons);
343
+ collectCompatibilityVerdict(inputs.runtime, blockedReasons, warnings);
344
+
345
+ const actionId =
346
+ inputs.actionId ?? inferActionId(inputs.operation, inputs.proposedContent);
347
+ const approval = collectPolicyVerdict(actionId, blockedReasons, warnings);
348
+
349
+ const safe = blockedReasons.length === 0;
350
+ return {
351
+ safe,
352
+ blockedReasons: Object.freeze([...blockedReasons]),
353
+ warnings: Object.freeze([...warnings]),
354
+ ...(approval ? { approval } : {}),
355
+ };
356
+ }