@beyondwork/docx-react-component 1.0.66 → 1.0.69

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 -931
  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 -4795
  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,233 @@
1
+ /**
2
+ * Slice 3 — scope-bundle evidence composer.
3
+ *
4
+ * Given a compiled `SemanticScope` + live document, collect the review
5
+ * items, overlapping workflow scopes, and formatting summary that ride
6
+ * alongside the scope in a `ScopeBundle`.
7
+ *
8
+ * Compatibility flags are left empty at Slice 3 — they land with the
9
+ * unified `ValidationResult` contract in Slice 4. Keeping the field on
10
+ * the returned shape (rather than omitting it) avoids a breaking shape
11
+ * change when Slice 4 fills it.
12
+ */
13
+
14
+ import type {
15
+ CanonicalAnchor,
16
+ CanonicalDocument,
17
+ CommentThread,
18
+ RevisionRecord,
19
+ } from "../../model/canonical-document.ts";
20
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
21
+ import type {
22
+ WorkflowMetadataEntry,
23
+ WorkflowOverlay,
24
+ } from "./_scope-dependencies.ts";
25
+
26
+ import { AI_EXPLANATION_METADATA_ID } from "./attach-explanation.ts";
27
+ import { AI_ISSUE_METADATA_ID } from "./create-issue.ts";
28
+ import type { EnumeratedScope } from "./enumerate-scopes.ts";
29
+ import { buildScopePositionMap, type ScopePositionRange } from "./position-map.ts";
30
+ import { rangesOverlap, resolveScopeRange } from "./scope-range.ts";
31
+ import type {
32
+ AIExplanationSummary,
33
+ AIIssueSummary,
34
+ ScopeBundleEvidence,
35
+ SemanticScope,
36
+ } from "./semantic-scope-types.ts";
37
+
38
+ function anchorRange(anchor: CanonicalAnchor): ScopePositionRange | null {
39
+ switch (anchor.kind) {
40
+ case "range":
41
+ return { from: anchor.range.from, to: anchor.range.to };
42
+ case "node":
43
+ return { from: anchor.at, to: anchor.at };
44
+ case "detached":
45
+ return anchor.lastKnownRange;
46
+ default:
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function formattingSummaryOf(scope: SemanticScope): string {
52
+ const parts: string[] = [];
53
+ if (scope.formatting.paragraphStyleId) {
54
+ parts.push(`style=${scope.formatting.paragraphStyleId}`);
55
+ }
56
+ if (typeof scope.formatting.outlineLevel === "number") {
57
+ parts.push(`outline=${scope.formatting.outlineLevel}`);
58
+ }
59
+ if (scope.formatting.numbering) {
60
+ parts.push(
61
+ `numbering=${scope.formatting.numbering.numberingInstanceId}:${scope.formatting.numbering.level}`,
62
+ );
63
+ }
64
+ if (scope.formatting.tableRole) {
65
+ parts.push(`role=${scope.formatting.tableRole}`);
66
+ }
67
+ if (scope.formatting.emphasis && scope.formatting.emphasis.length > 0) {
68
+ parts.push(`emphasis=${scope.formatting.emphasis.join("+")}`);
69
+ }
70
+ return parts.join(" ") || "(none)";
71
+ }
72
+
73
+ export interface EvidenceInputs {
74
+ readonly scope: SemanticScope;
75
+ readonly document: Pick<CanonicalDocument, "content" | "review"> | CanonicalDocumentEnvelope;
76
+ readonly overlay?: WorkflowOverlay | null;
77
+ /**
78
+ * Pre-enumerated scopes used to resolve the bundle's own scope range +
79
+ * overlap with other scopes. When absent, the composer re-enumerates.
80
+ */
81
+ readonly scopes: readonly EnumeratedScope[];
82
+ /**
83
+ * Workflow-metadata entries to project into the bundle as
84
+ * `aiExplanations` + `aiIssues`. When absent, those fields are omitted.
85
+ * Callers that have a live runtime typically pass
86
+ * `runtime.getWorkflowMetadataSnapshot().entries` through here.
87
+ */
88
+ readonly workflowMetadataEntries?: readonly WorkflowMetadataEntry[];
89
+ }
90
+
91
+ function normalizeSeverity(raw: unknown): AIIssueSummary["severity"] {
92
+ if (raw === "warning" || raw === "error") return raw;
93
+ return "info";
94
+ }
95
+
96
+ function normalizeStatus(raw: unknown): AIIssueSummary["status"] {
97
+ if (raw === "resolved") return raw;
98
+ return "open";
99
+ }
100
+
101
+ function projectAIExplanation(
102
+ entry: WorkflowMetadataEntry,
103
+ ): AIExplanationSummary | null {
104
+ const value = entry.value as
105
+ | { explanationId?: string; text?: string; createdAtUtc?: string }
106
+ | undefined;
107
+ if (!value || typeof value.text !== "string") return null;
108
+ return {
109
+ explanationId:
110
+ typeof value.explanationId === "string" && value.explanationId.length > 0
111
+ ? value.explanationId
112
+ : entry.entryId,
113
+ text: value.text,
114
+ ...(typeof value.createdAtUtc === "string"
115
+ ? { createdAtUtc: value.createdAtUtc }
116
+ : {}),
117
+ };
118
+ }
119
+
120
+ function projectAIIssue(
121
+ entry: WorkflowMetadataEntry,
122
+ ): AIIssueSummary | null {
123
+ const value = entry.value as
124
+ | {
125
+ issueId?: string;
126
+ summary?: string;
127
+ severity?: unknown;
128
+ status?: unknown;
129
+ createdAtUtc?: string;
130
+ }
131
+ | undefined;
132
+ if (!value || typeof value.summary !== "string") return null;
133
+ return {
134
+ issueId:
135
+ typeof value.issueId === "string" && value.issueId.length > 0
136
+ ? value.issueId
137
+ : entry.entryId,
138
+ summary: value.summary,
139
+ severity: normalizeSeverity(value.severity),
140
+ status: normalizeStatus(value.status),
141
+ ...(typeof value.createdAtUtc === "string"
142
+ ? { createdAtUtc: value.createdAtUtc }
143
+ : {}),
144
+ };
145
+ }
146
+
147
+ function findEntryForScope(
148
+ scope: SemanticScope,
149
+ scopes: readonly EnumeratedScope[],
150
+ ): EnumeratedScope | null {
151
+ return scopes.find((entry) => entry.handle.scopeId === scope.handle.scopeId) ?? null;
152
+ }
153
+
154
+ export function composeEvidence(inputs: EvidenceInputs): ScopeBundleEvidence {
155
+ const { scope, document, overlay, scopes } = inputs;
156
+ const positionMap = buildScopePositionMap(document);
157
+ const entry = findEntryForScope(scope, scopes);
158
+ const selfRange = entry
159
+ ? resolveScopeRange(entry, scope.handle, positionMap)
160
+ : null;
161
+
162
+ const reviewItemIds: string[] = [];
163
+ const overlappingWorkflowScopeIds: string[] = [];
164
+ const compatibilityFlags: string[] = [];
165
+
166
+ if (selfRange) {
167
+ const review = (document as {
168
+ review?: {
169
+ comments?: Record<string, CommentThread>;
170
+ revisions?: Record<string, RevisionRecord>;
171
+ };
172
+ }).review;
173
+ if (review) {
174
+ for (const thread of Object.values(review.comments ?? {})) {
175
+ const range = anchorRange(thread.anchor);
176
+ if (range && rangesOverlap(selfRange, range)) {
177
+ reviewItemIds.push(thread.commentId);
178
+ }
179
+ }
180
+ for (const rev of Object.values(review.revisions ?? {})) {
181
+ const range = anchorRange(rev.anchor);
182
+ if (range && rangesOverlap(selfRange, range)) {
183
+ reviewItemIds.push(rev.changeId);
184
+ }
185
+ }
186
+ }
187
+
188
+ if (overlay?.scopes) {
189
+ for (const other of overlay.scopes) {
190
+ if (other.scopeId === scope.handle.scopeId) continue;
191
+ const markerRange = positionMap.markerScopes.get(other.scopeId);
192
+ if (markerRange && rangesOverlap(selfRange, markerRange)) {
193
+ overlappingWorkflowScopeIds.push(other.scopeId);
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ reviewItemIds.sort();
200
+ overlappingWorkflowScopeIds.sort();
201
+
202
+ // Adversarial-close item 1 — read-side join for ai.explanation + ai.issue.
203
+ // Filter workflow-metadata entries by scopeId + metadataId; project
204
+ // into narrow summary shapes. Empty arrays are OK to include (the
205
+ // consumer sees "no explanations yet" as explicit rather than "field
206
+ // not supported").
207
+ const aiExplanations: AIExplanationSummary[] = [];
208
+ const aiIssues: AIIssueSummary[] = [];
209
+ if (inputs.workflowMetadataEntries) {
210
+ for (const entry of inputs.workflowMetadataEntries) {
211
+ if (entry.scopeId !== scope.handle.scopeId) continue;
212
+ if (entry.metadataId === AI_EXPLANATION_METADATA_ID) {
213
+ const projected = projectAIExplanation(entry);
214
+ if (projected) aiExplanations.push(projected);
215
+ } else if (entry.metadataId === AI_ISSUE_METADATA_ID) {
216
+ const projected = projectAIIssue(entry);
217
+ if (projected) aiIssues.push(projected);
218
+ }
219
+ }
220
+ }
221
+ aiExplanations.sort((a, b) => a.explanationId.localeCompare(b.explanationId));
222
+ aiIssues.sort((a, b) => a.issueId.localeCompare(b.issueId));
223
+
224
+ return {
225
+ formattingSummary: formattingSummaryOf(scope),
226
+ reviewItemIds,
227
+ overlappingWorkflowScopeIds,
228
+ compatibilityFlags,
229
+ ...(inputs.workflowMetadataEntries
230
+ ? { aiExplanations, aiIssues }
231
+ : {}),
232
+ };
233
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Layer 08 — Semantic Scope Compiler · barrel.
3
+ *
4
+ * See `docs/architecture/08-semantic-scope-compiler.md` for the end-state
5
+ * spec. Slice 1 ships:
6
+ *
7
+ * - core types (`semantic-scope-types.ts`)
8
+ * - compile dispatcher + paragraph/heading/list-item kind files
9
+ * - enumerate-scopes over the main-story paragraph stream
10
+ * - replaceability first cut
11
+ * - debug projector (producer-only; snapshot wiring is a follow-up)
12
+ *
13
+ * Purity invariant: this module must not import from src/api/v3, src/ui-,
14
+ * src/ui/, or src/io. Enforced by
15
+ * scripts/ci-check-scope-compiler-purity.mjs.
16
+ */
17
+
18
+ export { isStructuredReplacementContent } from "./semantic-scope-types.ts";
19
+ export type {
20
+ AIExplanationSummary,
21
+ AIIssueSummary,
22
+ StructuredReplacementContent,
23
+ GeometryRect,
24
+ Replaceability,
25
+ ReplaceabilityLevel,
26
+ ReplacementOperationKind,
27
+ ReplacementPreservePolicy,
28
+ ReplacementScope,
29
+ RuntimeOperationPlan,
30
+ RuntimeOperationStep,
31
+ RuntimeOperationStepKind,
32
+ ScopeActionAudit,
33
+ ScopeBundle,
34
+ ScopeBundleEvidence,
35
+ ScopeBundleNeighborhood,
36
+ ScopeCompilerDebugEntry,
37
+ ScopeHandle,
38
+ ScopeProvenance,
39
+ ScopeStableRef,
40
+ SemanticScope,
41
+ SemanticScopeAudit,
42
+ SemanticScopeContent,
43
+ SemanticScopeFormatting,
44
+ SemanticScopeGeometry,
45
+ SemanticScopeKind,
46
+ SemanticScopeLayout,
47
+ SemanticScopeLineSpan,
48
+ SemanticScopeNumbering,
49
+ SemanticScopeWorkflow,
50
+ ValidationApproval,
51
+ ValidationIssue,
52
+ ValidationResult,
53
+ } from "./semantic-scope-types.ts";
54
+
55
+ export {
56
+ buildParagraphIndexMap,
57
+ buildSectionIndexByBlockIndex,
58
+ compileScope,
59
+ type CompileScopeOptions,
60
+ } from "./compile-scope.ts";
61
+ export {
62
+ compileScopeBundle,
63
+ compileScopeBundleById,
64
+ type ScopeBundleInputs,
65
+ } from "./compile-scope-bundle.ts";
66
+ export {
67
+ enumerateScopes,
68
+ type CommentThreadEnumeratedScope,
69
+ type EnumerateScopesInputs,
70
+ type EnumeratedScope,
71
+ type FieldEnumeratedScope,
72
+ type ParagraphLikeEnumeratedScope,
73
+ type RevisionEnumeratedScope,
74
+ type TableCellEnumeratedScope,
75
+ type TableEnumeratedScope,
76
+ type TableRowEnumeratedScope,
77
+ } from "./enumerate-scopes.ts";
78
+ export { composeEvidence, type EvidenceInputs } from "./evidence.ts";
79
+ export {
80
+ buildScopePositionMap,
81
+ computeBlockPositions,
82
+ type BlockPositionEntry,
83
+ type ScopePositionMap,
84
+ type ScopePositionRange,
85
+ } from "./position-map.ts";
86
+ export {
87
+ composeReviewBundle,
88
+ type ComposeReviewBundleInputs,
89
+ type ReviewBundle,
90
+ type ReviewBundleCommentSummary,
91
+ type ReviewBundleRevisionSummary,
92
+ } from "./review-bundle.ts";
93
+ export {
94
+ resolveReference,
95
+ type ResolveReferenceInputs,
96
+ type ResolveReferenceResult,
97
+ type ScopeReference,
98
+ } from "./resolve-reference.ts";
99
+ export { deriveReplaceability } from "./replaceability.ts";
100
+ export {
101
+ composeScopeValidation,
102
+ type ComposeScopeValidationInputs,
103
+ } from "./action-validation.ts";
104
+ export {
105
+ computePreservationVerdict,
106
+ type PreservationVerdict,
107
+ } from "./preservation-boundary.ts";
108
+ export { projectScopeDebugEntry, type ProjectScopeOptions } from "./projector.ts";
109
+ export {
110
+ buildScopeActionAudit,
111
+ emitScopeActionAudit,
112
+ type EmitScopeActionAuditInputs,
113
+ } from "./audit-bundle.ts";
114
+ export {
115
+ proposeReplacement,
116
+ type ReplacementProposalInput,
117
+ } from "./replacement/propose.ts";
118
+ export {
119
+ compileReplacement,
120
+ type CompileReplacementInputs,
121
+ } from "./replacement/compile.ts";
122
+ export {
123
+ applyScopeReplacement,
124
+ type ApplyScopeReplacementInputs,
125
+ type ApplyScopeReplacementResult,
126
+ type ApplyScopeReplacementSink,
127
+ } from "./replacement/apply.ts";
128
+ export {
129
+ attachExplanation,
130
+ AI_EXPLANATION_METADATA_ID,
131
+ type AttachExplanationInput,
132
+ type AttachExplanationResult,
133
+ } from "./attach-explanation.ts";
134
+ export {
135
+ createIssue,
136
+ AI_ISSUE_METADATA_ID,
137
+ type CreateIssueInput,
138
+ type CreateIssueResult,
139
+ type IssueSeverity,
140
+ type IssueStatus,
141
+ } from "./create-issue.ts";
142
+ export {
143
+ createScopeCompilerService,
144
+ type ApplyReplacementRequest,
145
+ type AttachExplanationRequest,
146
+ type CompileScopeByIdResult,
147
+ type CompilerServiceRuntime,
148
+ type CreateIssueRequest,
149
+ type ScopeCompilerService,
150
+ } from "./compiler-service.ts";
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Slice 3 — scope position map.
3
+ *
4
+ * Computes a `{ from, to }` range in PM-style document coordinates for every
5
+ * enumerated scope. Offset / range / marker-backed overlap lookups consume
6
+ * it. The algorithm mirrors `src/runtime/scope-resolver.ts::walkParagraphs`
7
+ * so derived and marker-backed positions line up byte-for-byte.
8
+ *
9
+ * Two precision tiers shipped here:
10
+ * - Block ranges — one `[from, to]` per document-root block, matching the
11
+ * canonical walker (`scope-resolver.ts::walkParagraphs`). Used for
12
+ * paragraph-like and table-like kinds.
13
+ * - Inline ranges — one `[from, to]` per paragraph-child slot. Used for
14
+ * nested inline kinds (fields) so scope resolution can snap to the
15
+ * actual field span inside a paragraph rather than the whole paragraph.
16
+ *
17
+ * Pagination / geometric enrichment lives in layers 04 + 05; this map is
18
+ * purely canonical walk arithmetic.
19
+ */
20
+
21
+ import type {
22
+ CanonicalDocument,
23
+ DocumentRootNode,
24
+ InlineNode,
25
+ ParagraphNode,
26
+ TableNode,
27
+ } from "../../model/canonical-document.ts";
28
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
29
+
30
+ import { collectScopeLocations } from "../workflow/scope-resolver.ts";
31
+
32
+ export interface ScopePositionRange {
33
+ readonly from: number;
34
+ readonly to: number;
35
+ }
36
+
37
+ function inlineLength(node: InlineNode): number {
38
+ switch (node.type) {
39
+ case "text":
40
+ return Array.from(node.text).length;
41
+ case "hyperlink":
42
+ case "field":
43
+ return node.children.reduce(
44
+ (total, child) => total + inlineLength(child as InlineNode),
45
+ 0,
46
+ );
47
+ case "bookmark_start":
48
+ case "bookmark_end":
49
+ case "scope_marker_start":
50
+ case "scope_marker_end":
51
+ return 0;
52
+ default:
53
+ return 1;
54
+ }
55
+ }
56
+
57
+ export interface BlockPositionEntry {
58
+ readonly blockIndex: number;
59
+ readonly from: number;
60
+ readonly to: number;
61
+ }
62
+
63
+ /**
64
+ * Walk the root's block children and compute each block's start/end
65
+ * position. Matches the walker inside `scope-resolver.ts` — cursor starts
66
+ * at 0, each paragraph's content length adds to the cursor, and every
67
+ * non-last block adds 1 for its boundary.
68
+ */
69
+ export function computeBlockPositions(
70
+ document: Pick<CanonicalDocument, "content"> | CanonicalDocumentEnvelope,
71
+ ): BlockPositionEntry[] {
72
+ const envelope = document as CanonicalDocumentEnvelope;
73
+ const root: DocumentRootNode =
74
+ "content" in envelope
75
+ ? (envelope.content as DocumentRootNode)
76
+ : (document as unknown as DocumentRootNode);
77
+
78
+ const out: BlockPositionEntry[] = [];
79
+ let cursor = 0;
80
+ for (let index = 0; index < root.children.length; index += 1) {
81
+ const block = root.children[index];
82
+ const from = cursor;
83
+ let length = 0;
84
+ if (block && block.type === "paragraph") {
85
+ length = (block as ParagraphNode).children.reduce(
86
+ (total, child) => total + inlineLength(child as InlineNode),
87
+ 0,
88
+ );
89
+ } else {
90
+ // Tables + other block atoms occupy one slot in the document walker.
91
+ length = 1;
92
+ }
93
+ cursor += length;
94
+ const to = cursor;
95
+ out.push({ blockIndex: index, from, to });
96
+ if (index < root.children.length - 1) {
97
+ cursor += 1;
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+
103
+ /**
104
+ * Build inline-offset ranges for every child of every paragraph. The key
105
+ * convention matches the `field` enumeration in `enumerate-scopes.ts`:
106
+ * `${blockIndex}:${inlineIndex}`. Zero-width inlines (bookmarks, markers)
107
+ * get `[cursor, cursor]`; real inlines get `[cursor, cursor + length]`.
108
+ */
109
+ export function computeInlinePositions(
110
+ document: Pick<CanonicalDocument, "content"> | CanonicalDocumentEnvelope,
111
+ blocks: readonly BlockPositionEntry[],
112
+ ): Map<string, ScopePositionRange> {
113
+ const envelope = document as CanonicalDocumentEnvelope;
114
+ const root: DocumentRootNode =
115
+ "content" in envelope
116
+ ? (envelope.content as DocumentRootNode)
117
+ : (document as unknown as DocumentRootNode);
118
+ const out = new Map<string, ScopePositionRange>();
119
+ for (let idx = 0; idx < root.children.length; idx += 1) {
120
+ const block = root.children[idx];
121
+ if (!block || block.type !== "paragraph") continue;
122
+ const blockEntry = blocks[idx];
123
+ if (!blockEntry) continue;
124
+ let cursor = blockEntry.from;
125
+ const paragraph = block as ParagraphNode;
126
+ for (let i = 0; i < paragraph.children.length; i += 1) {
127
+ const child = paragraph.children[i] as InlineNode;
128
+ const length = inlineLength(child);
129
+ out.set(`${idx}:${i}`, { from: cursor, to: cursor + length });
130
+ cursor += length;
131
+ }
132
+ }
133
+ return out;
134
+ }
135
+
136
+ /**
137
+ * Range for a single table row, computed as a proportional subrange of the
138
+ * table's 1-slot canonical range. Tables are collapsed into a single walker
139
+ * slot (`+1`) so precise row/cell offsets do not exist in canonical
140
+ * coordinates; we synthesize deterministic non-overlapping subranges here
141
+ * using a scaled `[0, 1)` partitioning against `table.rows.length`.
142
+ *
143
+ * The returned range carries fractional `from` / `to`. Callers that need
144
+ * integer positions should treat the row's container block as authoritative
145
+ * and rely on specificity ordering (see `resolve-reference.ts`). These
146
+ * fractional ranges exist so `rangesOverlap` can distinguish one row from
147
+ * another within the same table.
148
+ */
149
+ export function computeTableRowRange(
150
+ table: TableNode,
151
+ rowIndex: number,
152
+ tableRange: ScopePositionRange,
153
+ ): ScopePositionRange {
154
+ const rowCount = Math.max(1, table.rows.length);
155
+ const span = tableRange.to - tableRange.from;
156
+ if (span <= 0) {
157
+ // Degenerate — fall back to identity. This preserves the pre-fix
158
+ // behaviour (tie on range) but marks each row with a unique offset
159
+ // within the tie via `scopeSpecificity`.
160
+ return tableRange;
161
+ }
162
+ const step = span / rowCount;
163
+ const from = tableRange.from + step * rowIndex;
164
+ const to = tableRange.from + step * (rowIndex + 1);
165
+ return { from, to };
166
+ }
167
+
168
+ /**
169
+ * Range for a single cell, computed as a proportional subrange of the
170
+ * enclosing row's range.
171
+ */
172
+ export function computeTableCellRange(
173
+ row: TableNode["rows"][number],
174
+ cellIndex: number,
175
+ rowRange: ScopePositionRange,
176
+ ): ScopePositionRange {
177
+ const cellCount = Math.max(1, row.cells.length);
178
+ const span = rowRange.to - rowRange.from;
179
+ if (span <= 0) return rowRange;
180
+ const step = span / cellCount;
181
+ const from = rowRange.from + step * cellIndex;
182
+ const to = rowRange.from + step * (cellIndex + 1);
183
+ return { from, to };
184
+ }
185
+
186
+ export interface ScopePositionMap {
187
+ /** Marker-backed scopeId → PM-position range. */
188
+ readonly markerScopes: ReadonlyMap<string, ScopePositionRange>;
189
+ /** Derived block scopeId → PM-position range. `blockIndex` is the key. */
190
+ readonly blocks: ReadonlyMap<number, ScopePositionRange>;
191
+ /** Inline-position ranges, keyed `${blockIndex}:${inlineIndex}`. */
192
+ readonly inlines: ReadonlyMap<string, ScopePositionRange>;
193
+ }
194
+
195
+ export function buildScopePositionMap(
196
+ document: Pick<CanonicalDocument, "content"> | CanonicalDocumentEnvelope,
197
+ ): ScopePositionMap {
198
+ const locations = collectScopeLocations(document);
199
+ const markerScopes = new Map<string, ScopePositionRange>();
200
+ for (const [scopeId, loc] of locations) {
201
+ if (loc.startPos === undefined || loc.endPos === undefined) continue;
202
+ markerScopes.set(scopeId, {
203
+ from: Math.min(loc.startPos, loc.endPos),
204
+ to: Math.max(loc.startPos, loc.endPos),
205
+ });
206
+ }
207
+ const blockEntries = computeBlockPositions(document);
208
+ const blocks = new Map<number, ScopePositionRange>();
209
+ for (const entry of blockEntries) {
210
+ blocks.set(entry.blockIndex, { from: entry.from, to: entry.to });
211
+ }
212
+ const inlines = computeInlinePositions(document, blockEntries);
213
+ return { markerScopes, blocks, inlines };
214
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Layer 08 — preservation-boundary helper (Slice 4 interim).
3
+ *
4
+ * Computes whether a scope's canonical range crosses any preserve-only
5
+ * boundary that would be destroyed by a replace/split/insert operation.
6
+ * Sources consulted:
7
+ *
8
+ * - `document.preservation.opaqueFragments` — `OpaqueFragmentRecord[]`
9
+ * with `lastKnownRange` + `featureClass: "preserve-only"`. Any
10
+ * overlap is a blocker.
11
+ * - `document.preservation.packageParts` — package-level parts
12
+ * (image blobs, embed parts). Not range-keyed; their presence is
13
+ * not by itself a scope-level blocker — the opaque-fragment records
14
+ * are the scope-visible projection of package-level preservation.
15
+ * - scope markers inside the target range (handled via the scope's own
16
+ * range computation — scope markers that bracket THIS scope are the
17
+ * scope's identity; markers *inside* the range would be destroyed by
18
+ * a replace, and are surfaced here).
19
+ *
20
+ * Scope-local, plain-value, synchronous. Purity allowlist
21
+ * (`scripts/ci-check-scope-compiler-purity.mjs`) already permits imports
22
+ * from `src/model/**`.
23
+ *
24
+ * Slice 7 can extract this helper into `src/preservation/**` if a
25
+ * cross-layer home makes sense. Until then it stays scope-local so the
26
+ * composer has no cross-lane dependency.
27
+ */
28
+
29
+ import type { CanonicalDocument } from "../../model/canonical-document.ts";
30
+ import { findOpaqueFragmentsIntersectingRange } from "../../preservation/store.ts";
31
+
32
+ import type { ScopePositionMap, ScopePositionRange } from "./position-map.ts";
33
+
34
+ export interface PreservationVerdict {
35
+ readonly replaceable: boolean;
36
+ readonly reasons: readonly string[];
37
+ }
38
+
39
+ /**
40
+ * Return the preservation verdict for a scope's canonical range. The
41
+ * returned `reasons` array carries stable suffix keys that the composer
42
+ * prefixes with `preserve:` before surfacing them on
43
+ * `ValidationResult.blockedReasons`.
44
+ *
45
+ * Callers without a position map or without a resolved range get a
46
+ * permissive verdict — the composer treats that as "preservation signal
47
+ * unavailable, no blockers from this source". The architecture contract
48
+ * (S4 — validate before apply) still requires callers to have a resolved
49
+ * scope range before any apply; this helper is not the place to enforce
50
+ * that, it's the composer's.
51
+ */
52
+ export function computePreservationVerdict(
53
+ document: CanonicalDocument | null | undefined,
54
+ range: ScopePositionRange | null,
55
+ positionMap: ScopePositionMap | null | undefined,
56
+ ): PreservationVerdict {
57
+ if (!document || !range) {
58
+ return { replaceable: true, reasons: [] };
59
+ }
60
+ const reasons: string[] = [];
61
+
62
+ const fragments = findOpaqueFragmentsIntersectingRange(
63
+ document.preservation,
64
+ { from: range.from, to: range.to },
65
+ );
66
+ for (const fragment of fragments) {
67
+ reasons.push(
68
+ `opaque-fragment:${fragment.fragmentId}:${fragment.payloadKind}`,
69
+ );
70
+ }
71
+
72
+ if (positionMap) {
73
+ for (const [scopeId, markerRange] of positionMap.markerScopes) {
74
+ // A marker range strictly inside the target range would be
75
+ // destroyed by replace. Equality (marker pair bracketing the scope
76
+ // itself) is *not* a blocker — that marker IS the scope's identity.
77
+ if (
78
+ markerRange.from > range.from &&
79
+ markerRange.to < range.to
80
+ ) {
81
+ reasons.push(`scope-marker-inside:${scopeId}`);
82
+ }
83
+ }
84
+ }
85
+
86
+ reasons.sort();
87
+ return {
88
+ replaceable: reasons.length === 0,
89
+ reasons: Object.freeze(reasons),
90
+ };
91
+ }