@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
@@ -56,11 +56,12 @@ import {
56
56
  type MarginPresetDefinition,
57
57
  type ActiveMarginPreset,
58
58
  } from "./margin-preset-catalog.ts";
59
- import {
60
- attachScopeCardModel,
61
- collectScopeRailSegments,
62
- type ScopeRailSegment,
63
- } from "../workflow-rail-segments.ts";
59
+ // ScopeRailSegment type is still re-exported from this module so
60
+ // chrome/overlay consumers (`tw-scope-rail-layer.tsx` etc.) can
61
+ // continue to import segment types via `../../runtime/layout`. Rail
62
+ // composition itself is Layer-06 (see workflow/coordinator.ts +
63
+ // runtime.workflow) — the layout facet only supplies the page graph.
64
+ import type { ScopeRailSegment } from "../workflow/rail/compose.ts";
64
65
  import { createEditorSurfaceSnapshot } from "../surface-projection.ts";
65
66
  import { storyTargetKey } from "../story-targeting.ts";
66
67
  import { MAIN_STORY_TARGET } from "../../core/selection/mapping.ts";
@@ -68,9 +69,25 @@ import {
68
69
  createSelectionSnapshot,
69
70
  type CanonicalDocumentEnvelope,
70
71
  } from "../../core/state/editor-state.ts";
71
- import { resolveTableStyleResolution } from "../table-style-resolver.ts";
72
+ // L03 production boundary: layout delegates every table-style cascade
73
+ // through `FormattingContext.resolveTable` so the same hot-path entry is
74
+ // used in surface-projection and here. Direct imports of the table-style
75
+ // resolver from non-Layer-03 code are rejected by
76
+ // `scripts/ci-check-formatting-production-boundary.mjs`.
77
+ import { createFormattingContext } from "../formatting/formatting-context.ts";
78
+ import type { ResolvedTableStyleResolution } from "../formatting/table-style-resolver.ts";
72
79
  import { buildTableRenderPlan } from "./table-render-plan.ts";
73
- import { recordPerfSample } from "../../ui-tailwind/editor-surface/perf-probe.ts";
80
+ // Geometry helpers are no longer imported here:
81
+ // - `hitTest` + `getAnchorRects` moved to the geometry facet in the
82
+ // refactor/05 Slice 6 wrapper-deletion pass (2026-04-22).
83
+ // - `collectScopeRailSegmentsForQuery` is no longer needed because
84
+ // refactor/06 Slice 4C moved rail-segment composition to
85
+ // `runtime.workflow.*`; the layout facet supplies the page graph
86
+ // as input, not the rail output.
87
+ import { collectLineBoxesForRegion } from "../geometry/project-fragments.ts";
88
+ // `recordPerfSample` no longer imported — used only by the removed
89
+ // `hitTest` wrapper to emit `chrome.hit_test` samples. Geometry facet
90
+ // can add its own instrumentation at call site if needed.
74
91
  import type {
75
92
  SurfaceBlockSnapshot,
76
93
  } from "../../api/public-types";
@@ -84,6 +101,31 @@ export type {
84
101
 
85
102
  export type { ScopeRailSegment };
86
103
 
104
+ // ---------------------------------------------------------------------------
105
+ // Module-level warning emitter (D1 silent-success probes)
106
+ // ---------------------------------------------------------------------------
107
+
108
+ type LayoutWarningEmitter = (type: string, guard: string, inputs: Record<string, unknown>) => void;
109
+ let _activeLayoutWarningEmitter: LayoutWarningEmitter | undefined;
110
+
111
+ export function setActiveLayoutWarningEmitter(fn: LayoutWarningEmitter | undefined): void {
112
+ _activeLayoutWarningEmitter = fn;
113
+ }
114
+
115
+ /**
116
+ * Exported so debug-infra D1 probes at layout-semantic guards that have
117
+ * physically moved into `src/runtime/geometry/**` (Slice 2a+ of refactor/05)
118
+ * can continue to emit through the same `setActiveLayoutWarningEmitter`
119
+ * pipeline as the remaining in-facet probes. The emit surface is unchanged;
120
+ * only the call-site location moved.
121
+ */
122
+ export function emitLayoutGuardWarning(
123
+ guard: string,
124
+ inputs: Record<string, unknown>,
125
+ ): void {
126
+ _activeLayoutWarningEmitter?.(`layout.guard.return-empty`, guard, inputs);
127
+ }
128
+
87
129
  // ---------------------------------------------------------------------------
88
130
  // Public read model types (shape-stable, cloned at the facet boundary)
89
131
  // ---------------------------------------------------------------------------
@@ -397,6 +439,13 @@ export interface WordReviewEditorLayoutFacet {
397
439
  // Per-page semantic reads ---------------------------------------------
398
440
  getActiveStoriesOnPage(pageIndex: number): PublicResolvedPageStories | null;
399
441
  getDisplayPageNumber(pageIndex: number): number | null;
442
+ /**
443
+ * @deprecated Use `GeometryFacet.getLineBoxes` (refactor/05 layer 05).
444
+ * Both paths route through
445
+ * `src/runtime/geometry/project-fragments.ts::collectLineBoxesForRegion`;
446
+ * this wrapper is retained for back-compat and will be deleted after
447
+ * consumers migrate.
448
+ */
400
449
  getLineBoxes(
401
450
  pageIndex: number,
402
451
  options?: { region?: PublicPageRegion["kind"]; columnIndex?: number },
@@ -448,66 +497,31 @@ export interface WordReviewEditorLayoutFacet {
448
497
  getMarginPresetCatalog(): readonly MarginPresetDefinition[];
449
498
  getActiveMarginPreset(sectionIndex: number): ActiveMarginPreset | null;
450
499
 
451
- // Render-frame access (R1) --------------------------------------------
452
- /**
453
- * Return the active `RenderFrame` produced by the runtime-owned render
454
- * kernel. Optional when the host runtime has not yet installed a kernel
455
- * returns `null` in that case so consumers can fall back to the page-
456
- * graph reads above.
457
- */
458
- getRenderFrame?(
459
- options?: import("../render/index.ts").RenderFrameQueryOptions,
460
- ): import("../render/index.ts").RenderFrame | null;
461
-
462
- /** Return the render-kernel zoom, if a kernel is installed. */
463
- getRenderZoom?(): import("../render/index.ts").RenderZoom | null;
464
-
465
- /**
466
- * Hit-test a point in the mounted shell's coordinate space against the
467
- * current render frame. Returns the deepest matching region with runtime
468
- * offset, block id, fragment id, and line index so chrome surfaces can
469
- * turn mouse coordinates into canonical positions without consulting the
470
- * DOM. Returns `null` when no kernel is installed, or when the point
471
- * falls outside every page.
472
- */
473
- hitTest?(
474
- pointInRoot: import("../render/index.ts").RenderPoint,
475
- ): import("../render/index.ts").RenderHitResult | null;
476
-
477
- /**
478
- * Resolve anchor rects for a `RenderAnchorQuery`. Returns `[]` when the
479
- * kernel is absent or the query does not match any anchor. Chrome
480
- * surfaces that need a single rect can read `getAnchorRects(q)[0]`;
481
- * selection-spanning surfaces read the full list and union as needed.
482
- */
483
- getAnchorRects?(
484
- query: import("../render/index.ts").RenderAnchorQuery,
485
- ): readonly import("../render/index.ts").RenderFrameRect[];
486
-
487
- // Scope rail segments (R3a) -------------------------------------------
488
- /**
489
- * Return workflow rail segments active on a given page. Returns an empty
490
- * list when the host runtime did not supply workflow data to the facet.
491
- */
492
- getScopeRailSegments(
493
- pageIndex: number,
494
- ): readonly import("../workflow-rail-segments.ts").ScopeRailSegment[];
495
- /** Return every scope rail segment across the document. */
496
- getAllScopeRailSegments(): readonly import("../workflow-rail-segments.ts").ScopeRailSegment[];
497
-
498
- /**
499
- * Scope-card projection consumed by the chrome overlay's card layer
500
- * (scope-card-overlay P1). Joins every unique scope segment with
501
- * its attached issue metadata (R2 `ISSUE_METADATA_ID`), its
502
- * work-item id, and its `primaryAnchorRect` via the render kernel's
503
- * anchor index. P2 fields (`suggestionGroupIds`,
504
- * `reviewActionCount`, `agentPending`) are defaulted to empty /
505
- * zero / false in P1 and populated by later phases.
506
- *
507
- * Returns an empty list when no workflow rail input has been wired
508
- * or when no scopes are currently on the active story.
509
- */
510
- getAllScopeCardModels(): readonly import("../../api/public-types.ts").ScopeCardModel[];
500
+ // `getRenderFrame` + `getRenderZoom` — REMOVED from the layout facet
501
+ // in the refactor/05 cross-lane-coord §8.4–§8.6 pass (2026-04-22).
502
+ // Both are pure render-kernel passthroughs architecturally
503
+ // geometry-layer concerns, not layout. Consumers must call them
504
+ // through `runtime.geometry.getRenderFrame` / `.getRenderZoom`.
505
+ // Post-deletion the layout facet exposes layout-only reads; all five
506
+ // UI chrome consumers (selection-anchor resolver, object-selection
507
+ // overlay, table-grip layer, scope-rail layer, PM surface) were
508
+ // migrated to `runtime.geometry` in the same pass.
509
+
510
+ // `hitTest` + `getAnchorRects` — REMOVED in the refactor/05 Slice 6
511
+ // wrapper-deletion pass (2026-04-22). Both operate on pixel rects
512
+ // projected from the render frame — pure Layer-05 concerns. Consumers
513
+ // must call them through `runtime.geometry.hitTest` /
514
+ // `runtime.geometry.getAnchorRects`. The pure helpers live at
515
+ // `src/runtime/geometry/hit-test.ts::resolveHitTest` and
516
+ // `src/runtime/geometry/project-anchors.ts::resolveAnchorRects`.
517
+
518
+ // Scope-rail / scope-card composition is Layer-06 owned (W7).
519
+ // Consume via `runtime.workflow.getRailSegments(pageIndex)` /
520
+ // `runtime.workflow.getAllRailSegments()` /
521
+ // `runtime.workflow.getAllScopeCardModels()`. Layer-06 Slice 4
522
+ // (rail-seam inversion, 2026-04-22) removed the deprecated shims
523
+ // that previously lived here. The layout facet supplies page-graph
524
+ // input only; it is not the consumer seam for workflow data.
511
525
 
512
526
  // Measurement exposure -------------------------------------------------
513
527
  getResolvedFormatting(blockId: string): PublicResolvedParagraphFormatting | null;
@@ -546,20 +560,6 @@ export interface WordReviewEditorLayoutFacet {
546
560
  pageIndex: number,
547
561
  ): import("./table-render-plan.ts").TableRenderPlan | null;
548
562
 
549
- // Table render plan (P3e consumed by the render kernel, P4) ------------
550
- /**
551
- * Build a `TableRenderPlan` for a table block on a given page. Returns
552
- * `null` when the blockId does not resolve to a table in the current
553
- * surface. The plan carries columnsTwips, bandClasses, verticalMerges,
554
- * repeatedHeaderRows, and columnResizeHandles so chrome can render
555
- * band-aware cell styling and place column-resize grips without
556
- * walking canonical state.
557
- */
558
- getTableRenderPlan(
559
- blockId: string,
560
- pageIndex: number,
561
- ): import("./table-render-plan.ts").TableRenderPlan | null;
562
-
563
563
  /**
564
564
  * Returns the Y offset (in twips from the top of the page body) where this
565
565
  * table fragment starts on the given page. Computed by summing
@@ -612,30 +612,12 @@ export interface CreateLayoutFacetInput {
612
612
  | import("../render/index.ts").RenderKernel
613
613
  | null
614
614
  | undefined;
615
- /**
616
- * Optional workflow-segments accessor. When supplied, the facet computes
617
- * `getScopeRailSegments` / `getAllScopeRailSegments` by joining the host-
618
- * supplied workflow state with the current page graph. When omitted,
619
- * both methods return empty arrays.
620
- */
621
- getWorkflowRailInput?: () =>
622
- | Omit<
623
- import("../workflow-rail-segments.ts").CollectScopeRailSegmentsInput,
624
- "pageGraph"
625
- >
626
- | null
627
- | undefined;
628
- /**
629
- * Optional workflow-metadata accessor for scope-card issue resolution
630
- * (R2 / scope-card-overlay P1). Returns the
631
- * `WorkflowMarkupSnapshot.metadata` array so `getAllScopeCardModels`
632
- * can attach issue values to their scopes. Omit when the host does
633
- * not push metadata entries.
634
- */
635
- getWorkflowMarkupMetadata?: () =>
636
- | readonly import("../../api/public-types.ts").WorkflowMetadataMarkup[]
637
- | null
638
- | undefined;
615
+ // Scope-rail input accessors (`getWorkflowRailInput`,
616
+ // `getWorkflowMarkupMetadata`, `getSuggestionsSnapshot`) removed by
617
+ // Layer-06 Slice 4. Workflow rail / scope-card composition is now
618
+ // owned end-to-end by the coordinator + runtime.workflow facet; the
619
+ // layout facet only supplies the page graph as input, it does not
620
+ // consume workflow data.
639
621
  /**
640
622
  * L7 Phase 2 — optional viewport culling hooks wired from the owning
641
623
  * `DocumentRuntime`. When supplied, `facet.setVisibleBlockRange` /
@@ -644,15 +626,6 @@ export interface CreateLayoutFacetInput {
644
626
  */
645
627
  setVisibleBlockRange?: (range: { start: number; end: number }) => void;
646
628
  requestViewportRefresh?: () => void;
647
- /**
648
- * R3 — optional suggestions snapshot accessor. Used by
649
- * `getAllScopeCardModels` to attach `SuggestionGroup` entries whose
650
- * `issueId` matches the scope's issue. Omit to skip group wiring.
651
- */
652
- getSuggestionsSnapshot?: () =>
653
- | import("../../api/public-types.ts").SuggestionsSnapshot
654
- | null
655
- | undefined;
656
629
  }
657
630
 
658
631
  export function createLayoutFacet(
@@ -832,10 +805,20 @@ export function createLayoutFacet(
832
805
  return resolveFootnoteAreaRegionBlocks(node, document);
833
806
  }
834
807
  if (region === "column") {
835
- // Multi-column body splitting is a P10 follow-up. For now reuse
836
- // the body fragments for the indexed column.
837
- void columnIndex;
838
- return resolveBodyRegionBlocks(node, graph, findBodyBlockById);
808
+ // Refactor/04 Slice 4 follow-up resolve per-column fragment
809
+ // ids against the live `regions.columns[columnIndex].fragmentIds`
810
+ // populated by `buildRegions`. Single-column pages leave
811
+ // `regions.columns === undefined` (F01 + CCEP single-column
812
+ // regression preserved), so the column branch returns empty
813
+ // there. Out-of-range `columnIndex` also returns empty rather
814
+ // than wrapping or throwing — the caller's query was outside
815
+ // the published column count for this page.
816
+ return resolveColumnRegionBlocks(
817
+ node,
818
+ graph,
819
+ findBodyBlockById,
820
+ columnIndex ?? 0,
821
+ );
839
822
  }
840
823
  // endnote-area: empty per-page; document-end via getDocumentEndnoteBlocks.
841
824
  return Object.freeze([]);
@@ -931,7 +914,10 @@ export function createLayoutFacet(
931
914
  getLineBoxes(pageIndex, options) {
932
915
  const graph = currentGraph();
933
916
  const node = graph.pages[pageIndex];
934
- if (!node) return [];
917
+ if (!node) {
918
+ emitLayoutGuardWarning("getLineBoxes", { pageIndex });
919
+ return [];
920
+ }
935
921
  const region = options?.region ?? "body";
936
922
  return collectLineBoxesForRegion(
937
923
  node,
@@ -944,7 +930,10 @@ export function createLayoutFacet(
944
930
  getLineBoxesForRegion(pageIndex, region, options) {
945
931
  const graph = currentGraph();
946
932
  const node = graph.pages[pageIndex];
947
- if (!node) return [];
933
+ if (!node) {
934
+ emitLayoutGuardWarning("getLineBoxesForRegion", { pageIndex, region });
935
+ return [];
936
+ }
948
937
  return collectLineBoxesForRegion(
949
938
  node,
950
939
  region,
@@ -956,7 +945,10 @@ export function createLayoutFacet(
956
945
  getStoryRegionsOnPage(pageIndex) {
957
946
  const graph = currentGraph();
958
947
  const node = graph.pages[pageIndex];
959
- if (!node) return [];
948
+ if (!node) {
949
+ emitLayoutGuardWarning("getStoryRegionsOnPage", { pageIndex });
950
+ return [];
951
+ }
960
952
  const result: PublicPageRegion[] = [];
961
953
  // Render order: header → body → columns → footer → footnote-area.
962
954
  if (node.regions.header) {
@@ -1089,75 +1081,20 @@ export function createLayoutFacet(
1089
1081
  });
1090
1082
  },
1091
1083
 
1092
- getRenderFrame(options) {
1093
- const kernel = input.renderKernel?.();
1094
- if (!kernel) return null;
1095
- return kernel.getRenderFrame(options);
1096
- },
1097
-
1098
- getRenderZoom() {
1099
- const kernel = input.renderKernel?.();
1100
- if (!kernel) return null;
1101
- return kernel.getZoom();
1102
- },
1103
-
1104
- hitTest(pointInRoot) {
1105
- const kernel = input.renderKernel?.();
1106
- if (!kernel) return null;
1107
- const t0 = typeof performance !== "undefined" ? performance.now() : 0;
1108
- const frame = kernel.getRenderFrame();
1109
- const result = resolveHitTest(frame, pointInRoot);
1110
- if (t0 > 0) recordPerfSample("chrome.hit_test", performance.now() - t0);
1111
- return result;
1112
- },
1113
-
1114
- getAnchorRects(query) {
1115
- const kernel = input.renderKernel?.();
1116
- if (!kernel) return [];
1117
- const frame = kernel.getRenderFrame();
1118
- return resolveAnchorRects(frame, query);
1119
- },
1120
-
1121
- getScopeRailSegments(pageIndex) {
1122
- return collectScopeRailSegmentsForQuery(
1123
- input.getWorkflowRailInput?.(),
1124
- currentGraph(),
1125
- ).filter((segment) => segment.pageIndex === pageIndex);
1126
- },
1127
-
1128
- getAllScopeRailSegments() {
1129
- return collectScopeRailSegmentsForQuery(
1130
- input.getWorkflowRailInput?.(),
1131
- currentGraph(),
1132
- );
1133
- },
1134
-
1135
- getAllScopeCardModels() {
1136
- const railInput = input.getWorkflowRailInput?.();
1137
- const segments = collectScopeRailSegmentsForQuery(railInput, currentGraph());
1138
- if (segments.length === 0) return [];
1139
- const kernel = input.renderKernel?.();
1140
- const anchorIndex = kernel?.getRenderFrame?.()?.anchorIndex ?? null;
1141
- const metadata = input.getWorkflowMarkupMetadata?.();
1142
- const suggestions = input.getSuggestionsSnapshot?.() ?? null;
1143
- // The workflow-markup entries carry BOTH issue and review-action
1144
- // metadata. The projector filters to REVIEW_ACTION_METADATA_ID
1145
- // internally, so we can just pass the whole list.
1146
- return attachScopeCardModel({
1147
- segments,
1148
- scopes: railInput?.scopes ?? [],
1149
- metadata: metadata ?? undefined,
1150
- anchorIndex,
1151
- suggestions,
1152
- reviewActionMetadata: metadata ?? undefined,
1153
- candidates: railInput?.candidates ?? undefined,
1154
- });
1155
- },
1084
+ // `getRenderFrame` + `getRenderZoom` — removed in refactor/05
1085
+ // cross-lane-coord §8 pass. Geometry concerns; call through
1086
+ // `runtime.geometry.getRenderFrame` / `.getRenderZoom`.
1087
+ // `hitTest` + `getAnchorRects` — removed in refactor/05 Slice 6
1088
+ // wrapper-deletion pass. Geometry concerns; call through
1089
+ // `runtime.geometry.hitTest` / `runtime.geometry.getAnchorRects`.
1156
1090
 
1157
1091
  getFragmentsForPage(pageIndex) {
1158
1092
  const graph = currentGraph();
1159
1093
  const node = graph.pages[pageIndex];
1160
- if (!node) return [];
1094
+ if (!node) {
1095
+ emitLayoutGuardWarning("getFragmentsForPage", { pageIndex });
1096
+ return [];
1097
+ }
1161
1098
  return graph.fragments
1162
1099
  .filter((f) => f.pageId === node.pageId)
1163
1100
  .map((f) => toPublicBlockFragment(f, graph));
@@ -1659,257 +1596,22 @@ function findPageForOffsetAndStory(
1659
1596
  return null;
1660
1597
  }
1661
1598
 
1662
- /**
1663
- * Select line boxes that belong to a given region on a page.
1664
- *
1665
- * `body` returns the engine's per-page measured line boxes (one per
1666
- * line of paginated body content).
1667
- *
1668
- * `header` / `footer` / `footnote-area` / `column` (P4): synthesizes
1669
- * one line box per fragment in the region. Header/footer fragments
1670
- * aren't paginated themselves the engine reserves space for the
1671
- * entire header story per page so per-line measurements are not
1672
- * available. The synthesized line box uses each fragment's
1673
- * `heightTwips` as the line height with cumulative `baselineTwips`
1674
- * from the region origin. This gives consumers (page stack, P8
1675
- * region rendering) a uniform iteration shape across regions.
1676
- *
1677
- * `endnote-area` always returns empty: endnote bodies are emitted at
1678
- * the document end as a separate region whose layout sits outside
1679
- * the per-page accounting.
1680
- */
1681
- function collectLineBoxesForRegion(
1682
- node: RuntimePageNode,
1683
- region: PublicPageRegion["kind"],
1684
- graph: RuntimePageGraph,
1685
- columnIndex: number | undefined,
1686
- ): readonly RuntimeLineBoxAlias[] {
1687
- if (region === "body") {
1688
- return node.lineBoxes;
1689
- }
1690
- // P4: synthesize line boxes from the region's fragments.
1691
- const regionEntry = resolveRegionEntry(node, region, columnIndex);
1692
- if (!regionEntry || regionEntry.fragmentIds.length === 0) {
1693
- return EMPTY_LINE_BOXES;
1694
- }
1695
- const fragmentsById = new Map(
1696
- graph.fragments.map((f) => [f.fragmentId, f] as const),
1697
- );
1698
- const result: RuntimeLineBoxAlias[] = [];
1699
- let cursorTwips = 0;
1700
- let lineIndex = 0;
1701
- for (const fragmentId of regionEntry.fragmentIds) {
1702
- const fragment = fragmentsById.get(fragmentId);
1703
- if (!fragment) continue;
1704
- const heightTwips = Math.max(1, fragment.heightTwips);
1705
- result.push({
1706
- fragmentId,
1707
- lineIndex: lineIndex++,
1708
- baselineTwips: cursorTwips + heightTwips,
1709
- heightTwips,
1710
- widthTwips: regionEntry.widthTwips,
1711
- });
1712
- cursorTwips += heightTwips;
1713
- }
1714
- return result;
1715
- }
1716
-
1717
- function resolveRegionEntry(
1718
- node: RuntimePageNode,
1719
- region: PublicPageRegion["kind"],
1720
- columnIndex: number | undefined,
1721
- ): RuntimePageRegion | undefined {
1722
- switch (region) {
1723
- case "header":
1724
- return node.regions.header;
1725
- case "footer":
1726
- return node.regions.footer;
1727
- case "column": {
1728
- const columns = node.regions.columns ?? [];
1729
- if (columns.length === 0) return undefined;
1730
- const idx = columnIndex ?? 0;
1731
- return columns[idx];
1732
- }
1733
- case "footnote-area": {
1734
- // Footnote area sits at the bottom of the body region per
1735
- // OOXML. `RuntimePageRegions.footnotes` is a stable seam
1736
- // declared by P4; the page-graph builder populates entries
1737
- // when P8 lands. Returns the first footnote region (page
1738
- // layouts allocate one block of footnotes per page; multi-
1739
- // block layouts come later).
1740
- const footnoteRegionList = node.regions.footnotes;
1741
- if (footnoteRegionList && footnoteRegionList.length > 0) {
1742
- return footnoteRegionList[0];
1743
- }
1744
- return undefined;
1745
- }
1746
- default:
1747
- return undefined;
1748
- }
1749
- }
1750
-
1751
- // Use a shared alias so the region helper doesn't import the runtime
1752
- // `RuntimeLineBox` type redundantly (`lineBoxes` is already strongly typed on
1753
- // `RuntimePageNode`).
1754
- type RuntimeLineBoxAlias = RuntimePageNode["lineBoxes"][number];
1755
- const EMPTY_LINE_BOXES: readonly RuntimeLineBoxAlias[] = Object.freeze([]);
1756
-
1757
- /**
1758
- * Join the host-supplied workflow rail input with the current page graph.
1759
- * Returns an empty array when no input is available so callers can always
1760
- * iterate without null-checking.
1761
- */
1762
- function collectScopeRailSegmentsForQuery(
1763
- input:
1764
- | Omit<
1765
- import("../workflow-rail-segments.ts").CollectScopeRailSegmentsInput,
1766
- "pageGraph"
1767
- >
1768
- | null
1769
- | undefined,
1770
- graph: RuntimePageGraph,
1771
- ): ScopeRailSegment[] {
1772
- if (!input) return [];
1773
- return collectScopeRailSegments({ ...input, pageGraph: graph });
1774
- }
1775
-
1776
- function resolveHitTest(
1777
- frame: import("../render/index.ts").RenderFrame | null,
1778
- point: import("../render/index.ts").RenderPoint,
1779
- ): import("../render/index.ts").RenderHitResult | null {
1780
- if (!frame) return null;
1781
- for (const page of frame.pages) {
1782
- if (!containsPoint(page.frame, point)) continue;
1783
- const regionHit = hitTestRegion(page, point, "body");
1784
- if (regionHit) return regionHit;
1785
- const header = hitTestRegion(page, point, "header");
1786
- if (header) return header;
1787
- const footer = hitTestRegion(page, point, "footer");
1788
- if (footer) return footer;
1789
- }
1790
- return null;
1791
- }
1792
-
1793
- function hitTestRegion(
1794
- page: import("../render/index.ts").RenderPage,
1795
- point: import("../render/index.ts").RenderPoint,
1796
- kind: "body" | "header" | "footer",
1797
- ): import("../render/index.ts").RenderHitResult | null {
1798
- const region =
1799
- kind === "body"
1800
- ? page.regions.body
1801
- : kind === "header"
1802
- ? page.regions.header
1803
- : page.regions.footer;
1804
- if (!region) return null;
1805
- if (!containsPoint(region.frame, point)) return null;
1806
- for (const block of region.blocks) {
1807
- if (!containsPoint(block.frame, point)) continue;
1808
- let bestLineIndex = -1;
1809
- let bestLineDistance = Number.POSITIVE_INFINITY;
1810
- for (let i = 0; i < block.lines.length; i++) {
1811
- const line = block.lines[i]!;
1812
- const midY = line.frame.topPx + line.frame.heightPx / 2;
1813
- const distance = Math.abs(midY - point.yPx);
1814
- if (distance < bestLineDistance) {
1815
- bestLineDistance = distance;
1816
- bestLineIndex = i;
1817
- }
1818
- }
1819
- const lineIndex = bestLineIndex >= 0 ? bestLineIndex : 0;
1820
- const line = block.lines[lineIndex];
1821
- const runtimeOffset = line?.anchors[0]?.runtimeOffset ?? block.fragment.from;
1822
- return {
1823
- pageIndex: page.page.pageIndex,
1824
- regionKind: region.region.kind,
1825
- blockId: block.fragment.blockId,
1826
- fragmentId: block.fragment.fragmentId,
1827
- lineIndex,
1828
- runtimeOffset,
1829
- };
1830
- }
1831
- // Point fell inside the region but between blocks — snap to the nearest block.
1832
- let nearestBlock: import("../render/index.ts").RenderBlock | null = null;
1833
- let nearestDistance = Number.POSITIVE_INFINITY;
1834
- for (const block of region.blocks) {
1835
- const distance = Math.min(
1836
- Math.abs(point.yPx - block.frame.topPx),
1837
- Math.abs(point.yPx - (block.frame.topPx + block.frame.heightPx)),
1838
- );
1839
- if (distance < nearestDistance) {
1840
- nearestBlock = block;
1841
- nearestDistance = distance;
1842
- }
1843
- }
1844
- if (!nearestBlock) return null;
1845
- return {
1846
- pageIndex: page.page.pageIndex,
1847
- regionKind: region.region.kind,
1848
- blockId: nearestBlock.fragment.blockId,
1849
- fragmentId: nearestBlock.fragment.fragmentId,
1850
- lineIndex: 0,
1851
- runtimeOffset: nearestBlock.fragment.from,
1852
- };
1853
- }
1854
-
1855
- function containsPoint(
1856
- rect: import("../render/index.ts").RenderFrameRect,
1857
- point: import("../render/index.ts").RenderPoint,
1858
- ): boolean {
1859
- return (
1860
- point.xPx >= rect.leftPx &&
1861
- point.xPx <= rect.leftPx + rect.widthPx &&
1862
- point.yPx >= rect.topPx &&
1863
- point.yPx <= rect.topPx + rect.heightPx
1864
- );
1865
- }
1866
-
1867
- function resolveAnchorRects(
1868
- frame: import("../render/index.ts").RenderFrame | null,
1869
- query: import("../render/index.ts").RenderAnchorQuery,
1870
- ): readonly import("../render/index.ts").RenderFrameRect[] {
1871
- if (!frame) return [];
1872
- switch (query.kind) {
1873
- case "runtime-offset": {
1874
- const offset =
1875
- typeof query.value === "number" ? query.value : Number(query.value);
1876
- if (!Number.isFinite(offset)) return [];
1877
- const rect = frame.anchorIndex.byRuntimeOffset(offset, query.story);
1878
- return rect ? [rect] : [];
1879
- }
1880
- case "block-id": {
1881
- const rect = frame.anchorIndex.byBlockId(String(query.value));
1882
- return rect ? [rect] : [];
1883
- }
1884
- case "fragment-id": {
1885
- const rect = frame.anchorIndex.byFragmentId(String(query.value));
1886
- return rect ? [rect] : [];
1887
- }
1888
- case "page-index": {
1889
- const pageIndex =
1890
- typeof query.value === "number" ? query.value : Number(query.value);
1891
- if (!Number.isFinite(pageIndex)) return [];
1892
- const rect = frame.anchorIndex.byPageIndex(pageIndex);
1893
- return rect ? [rect] : [];
1894
- }
1895
- case "scope-id": {
1896
- return frame.anchorIndex.byScopeId(String(query.value));
1897
- }
1898
- case "comment-id": {
1899
- const rect = frame.anchorIndex.byCommentId(String(query.value));
1900
- return rect ? [rect] : [];
1901
- }
1902
- case "revision-id": {
1903
- const rect = frame.anchorIndex.byRevisionId(String(query.value));
1904
- return rect ? [rect] : [];
1905
- }
1906
- default: {
1907
- const exhaustive: never = query.kind;
1908
- void exhaustive;
1909
- return [];
1910
- }
1911
- }
1912
- }
1599
+ // Line-box collection (`collectLineBoxesForRegion`, `resolveRegionEntry`,
1600
+ // `EMPTY_LINE_BOXES`, `RuntimeLineBoxAlias`) and workflow-rail query
1601
+ // (`collectScopeRailSegmentsForQuery`) moved to
1602
+ // `src/runtime/geometry/project-fragments.ts` +
1603
+ // `src/runtime/geometry/project-anchors.ts` in refactor/05 Slice 2b. The
1604
+ // facet's `getLineBoxes` / `getScopeRailSegments` / `getAllScopeRailSegments`
1605
+ // / `getAllScopeCardModels` methods above now import from there. The D1
1606
+ // `emitLayoutGuardWarning` probes (including the
1607
+ // `collectScopeRailSegmentsForQuery` null-input probe) are ported alongside
1608
+ // via the exported `emitLayoutGuardWarning` symbolthey continue to
1609
+ // fire through `setActiveLayoutWarningEmitter`.
1610
+ //
1611
+ // Hit-test (`resolveHitTest`, `hitTestRegion`, `containsPoint`) and
1612
+ // anchor-rect resolution (`resolveAnchorRects`) were moved in Slice 2a.
1613
+ // Deletion of the remaining layout-facet wrappers lands in Slice 6 once
1614
+ // consumers have flipped to the geometry facet.
1913
1615
 
1914
1616
  // ---------------------------------------------------------------------------
1915
1617
  // Table render plan helpers (P4)
@@ -1968,17 +1670,20 @@ function findTableBlockByBlockId(
1968
1670
  function resolveTableStyleResolutionForPlan(
1969
1671
  surfaceTable: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
1970
1672
  document: LayoutEngineQueryInput["document"],
1971
- ): ReturnType<typeof resolveTableStyleResolution> | null {
1673
+ ): ResolvedTableStyleResolution | null {
1972
1674
  const canonicalTable = findCanonicalTableByBlockId(
1973
1675
  document.content.children,
1974
1676
  surfaceTable.blockId,
1975
1677
  { counter: { value: 0 } },
1976
1678
  );
1977
1679
  if (!canonicalTable) return null;
1978
- return resolveTableStyleResolution(
1979
- canonicalTable,
1980
- document.styles.tables ?? {},
1981
- );
1680
+ // L03 boundary: delegate through a FormattingContext for this single
1681
+ // call. The context is cheap to construct for a one-shot read; when
1682
+ // `getTableRenderPlan` is called in a batch, the caller's own context
1683
+ // would be preferable, but the current facet signature doesn't thread
1684
+ // one through. Keeping this narrow keeps the production boundary
1685
+ // consistent (no direct sub-resolver imports outside Layer 03).
1686
+ return createFormattingContext(document).resolveTable(canonicalTable);
1982
1687
  }
1983
1688
 
1984
1689
  /**
@@ -2058,6 +1763,46 @@ function resolveBodyRegionBlocks(
2058
1763
  return Object.freeze(blocks);
2059
1764
  }
2060
1765
 
1766
+ function resolveColumnRegionBlocks(
1767
+ node: RuntimePageNode,
1768
+ graph: RuntimePageGraph,
1769
+ findBodyBlockById: (blockId: string) => SurfaceBlockSnapshot | undefined,
1770
+ columnIndex: number,
1771
+ ): readonly PublicRegionBlock[] {
1772
+ // Refactor/04 Slice 4 — per-column block resolution. Reads the live
1773
+ // `regions.columns[columnIndex].fragmentIds` list that `buildRegions`
1774
+ // partitions when the section paginated under a multi-column layout.
1775
+ // Single-column pages leave `regions.columns` undefined; out-of-range
1776
+ // `columnIndex` is a caller contract error we treat gracefully.
1777
+ const columns = node.regions.columns;
1778
+ if (!columns) return Object.freeze([]);
1779
+ const column = columns[columnIndex];
1780
+ if (!column) return Object.freeze([]);
1781
+
1782
+ const fragmentsById = new Map<string, RuntimeBlockFragment>();
1783
+ for (const fragment of graph.fragments) {
1784
+ fragmentsById.set(fragment.fragmentId, fragment);
1785
+ }
1786
+ const blocks: PublicRegionBlock[] = [];
1787
+ for (const fragmentId of column.fragmentIds) {
1788
+ const fragment = fragmentsById.get(fragmentId);
1789
+ if (!fragment) continue;
1790
+ const blockSnapshot = findBodyBlockById(fragment.blockId);
1791
+ if (!blockSnapshot) continue;
1792
+ blocks.push({
1793
+ blockId: fragment.blockId,
1794
+ fragmentId,
1795
+ pageIndex: node.pageIndex,
1796
+ regionKind: "column",
1797
+ runtimeFromOffset: fragment.from,
1798
+ runtimeToOffset: fragment.to,
1799
+ heightTwips: fragment.heightTwips,
1800
+ blockSnapshot,
1801
+ });
1802
+ }
1803
+ return Object.freeze(blocks);
1804
+ }
1805
+
2061
1806
  function resolveHeaderFooterRegionBlocks(
2062
1807
  pageIndex: number,
2063
1808
  regionKind: "header" | "footer",