@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
@@ -42,33 +42,50 @@ export interface LayoutTabStop {
42
42
 
43
43
  /**
44
44
  * Average character width in twips per half-point of font size.
45
- * These are empirical values from measuring common document fonts.
46
- * Key insight: Word uses font metrics from the OS font table; we use
47
- * bounded approximations that are close enough for page composition.
45
+ *
46
+ * Calibrated 2026-04-23 against published OpenType font-metrics average
47
+ * advance widths (xAvgCharWidth / (unitsPerEm × 2)) rather than the
48
+ * pre-calibration "empirical" values that were systematically ~80%
49
+ * higher than real Word measurements. The pre-calibration values
50
+ * produced a 50-73% page overcount vs LibreOffice oracle baselines on
51
+ * CCEP contracts; trusted-oracle verification post truth-baseline's
52
+ * 2026-04-23 re-extraction confirmed the runtime-side miscalibration
53
+ * (cross-layer-coord-04.md §1.15 / §1.18).
54
+ *
55
+ * Unit: twips per half-point of font size. At an `effectiveSize` of 22
56
+ * (11-pt text), Calibri's factor of 5.0 yields 110 twips per character —
57
+ * which matches Calibri's published `xAvgCharWidth = 1037 / 2048 em`
58
+ * (~0.506 em = 5.57 pt = 111 twips at 11 pt). The same derivation gives
59
+ * the other values.
60
+ *
61
+ * The canvas backend does not consult this table — it measures real
62
+ * glyph widths via Canvas2D `measureText`. This table is only the
63
+ * empirical / SSR fallback used by the layout engine when a canvas
64
+ * context is unavailable.
48
65
  */
49
66
  const FONT_AVG_CHAR_WIDTH: Record<string, number> = {
50
- // Proportional serif
51
- "times new roman": 9.2,
52
- "georgia": 10.0,
53
- "garamond": 8.8,
54
- "book antiqua": 9.6,
55
- "palatino linotype": 9.8,
56
- "cambria": 9.6,
57
- // Proportional sans-serif
58
- "arial": 10.4,
59
- "calibri": 9.0,
60
- "helvetica": 10.4,
61
- "verdana": 12.0,
62
- "tahoma": 10.6,
63
- "segoe ui": 9.8,
64
- "trebuchet ms": 10.2,
65
- // Monospace
66
- "courier new": 12.0,
67
- "consolas": 11.0,
68
- "lucida console": 12.0,
67
+ // Proportional serif (xAvgCharWidth ≈ 0.48-0.52 em for body text)
68
+ "times new roman": 5.1,
69
+ "georgia": 5.6,
70
+ "garamond": 4.9,
71
+ "book antiqua": 5.3,
72
+ "palatino linotype": 5.5,
73
+ "cambria": 5.3,
74
+ // Proportional sans-serif (xAvgCharWidth ≈ 0.50-0.60 em)
75
+ "arial": 5.8,
76
+ "calibri": 5.0,
77
+ "helvetica": 5.8,
78
+ "verdana": 6.7,
79
+ "tahoma": 5.9,
80
+ "segoe ui": 5.4,
81
+ "trebuchet ms": 5.6,
82
+ // Monospace (every glyph ≈ 0.60-0.67 em)
83
+ "courier new": 6.7,
84
+ "consolas": 6.1,
85
+ "lucida console": 6.7,
69
86
  };
70
87
 
71
- const DEFAULT_FONT_AVG_CHAR_WIDTH = 10.0; // reasonable fallback
88
+ const DEFAULT_FONT_AVG_CHAR_WIDTH = 5.5; // reasonable fallback (between serif + sans proportional)
72
89
  const DEFAULT_FONT_SIZE_HALF_POINTS = 24; // 12pt
73
90
  const DEFAULT_LINE_HEIGHT_FACTOR = 1.15; // Word default for Calibri body
74
91
  const TWIPS_PER_POINT = 20;
@@ -277,15 +294,34 @@ function resolveIndentation(
277
294
  function resolveDominantFont(
278
295
  block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
279
296
  ): { fontSizeHalfPoints: number; avgCharWidth: number; fontFamily: string | undefined } {
280
- // Find the dominant font from the segments (most common or first text run)
297
+ // L03 L04 contract: read formatting from the canonical L03 output
298
+ // (`segment.resolvedRunFormatting`) first; fall back to the surface
299
+ // projection intermediate (`segment.markAttrs`) only when the
300
+ // resolved value is absent (viewport-culled block).
301
+ //
302
+ // Architecture §C2: layout must consume formatting from L03, not
303
+ // re-derive it from a rendering intermediate. Research doc §"Where
304
+ // to combine layers for performance" permits layout to ride the
305
+ // surface snapshot for locality, but the authoritative values must
306
+ // come from L03.
281
307
  let fontFamily: string | undefined;
282
308
  let fontSizeHalfPoints: number | undefined;
283
309
  let maxTextLength = 0;
284
310
 
285
311
  for (const segment of block.segments) {
286
312
  if (segment.kind !== "text") continue;
287
- const segFontFamily = segment.markAttrs?.fontFamily;
288
- const segFontSize = segment.markAttrs?.fontSize;
313
+ const resolved = segment.resolvedRunFormatting;
314
+ // Prefer L03 resolved output over projected markAttrs. The cascade
315
+ // already layered docDefaults → paragraph style → character style →
316
+ // direct overrides; the markAttrs fallback only applies when the
317
+ // block was viewport-culled (resolvedRunFormatting absent).
318
+ const segFontFamily =
319
+ resolved?.fontFamilyAscii ??
320
+ resolved?.fontFamily ??
321
+ resolved?.fontFamilyHAnsi ??
322
+ segment.markAttrs?.fontFamily;
323
+ const segFontSize =
324
+ resolved?.fontSizeHalfPoints ?? segment.markAttrs?.fontSize;
289
325
  const textLength = segment.text.length;
290
326
 
291
327
  if (textLength > maxTextLength) {
@@ -457,3 +493,99 @@ export function resolveNumberingPrefixLength(
457
493
  : 0;
458
494
  return prefix.length + suffix;
459
495
  }
496
+
497
+ /**
498
+ * Build a `Map<RunId, ResolvedRunFormatting>` for a paragraph block so
499
+ * the measurement provider's `measureLineFragments` call receives real
500
+ * per-run metrics instead of a placeholder empty map. Task 3 of
501
+ * refactor/04 post-closure (2026-04-23).
502
+ *
503
+ * Stable key convention: `${blockId}:${segmentId}`, matching the
504
+ * canvas backend's lookup (`measurement-backend-canvas.ts:156`).
505
+ *
506
+ * Source precedence per text segment:
507
+ * 1. `segment.resolvedRunFormatting` (L03 canonical cascade output)
508
+ * 2. `segment.markAttrs` + `segment.marks` (surface-projection intermediate)
509
+ *
510
+ * The shape emitted matches `ResolvedRunFormatting` from
511
+ * `resolved-formatting-document.ts` (structural compatibility — no
512
+ * import cycle). Canvas backend consumes `fontFamily`,
513
+ * `fontSizeHalfPoints`, `bold`, `italic`. The rest are populated
514
+ * best-effort for future consumers.
515
+ */
516
+ export function buildRunFormattingMap(
517
+ block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
518
+ ): Map<
519
+ string,
520
+ {
521
+ fontFamily?: string;
522
+ fontSizeHalfPoints?: number;
523
+ bold: boolean;
524
+ italic: boolean;
525
+ underline: boolean;
526
+ strikethrough: boolean;
527
+ color?: string;
528
+ highlight?: string;
529
+ verticalAlign: "baseline" | "superscript" | "subscript";
530
+ }
531
+ > {
532
+ const runs = new Map<
533
+ string,
534
+ {
535
+ fontFamily?: string;
536
+ fontSizeHalfPoints?: number;
537
+ bold: boolean;
538
+ italic: boolean;
539
+ underline: boolean;
540
+ strikethrough: boolean;
541
+ color?: string;
542
+ highlight?: string;
543
+ verticalAlign: "baseline" | "superscript" | "subscript";
544
+ }
545
+ >();
546
+ for (const segment of block.segments) {
547
+ if (segment.kind !== "text") continue;
548
+ const resolved = segment.resolvedRunFormatting;
549
+ const markAttrs = segment.markAttrs ?? {};
550
+ const markSet = new Set(segment.marks ?? []);
551
+ const fontFamily =
552
+ resolved?.fontFamilyAscii ??
553
+ resolved?.fontFamily ??
554
+ resolved?.fontFamilyHAnsi ??
555
+ (typeof markAttrs.fontFamily === "string" ? markAttrs.fontFamily : undefined);
556
+ const fontSizeHalfPoints =
557
+ resolved?.fontSizeHalfPoints ??
558
+ (typeof markAttrs.fontSize === "number" ? markAttrs.fontSize : undefined);
559
+ const bold = resolved?.bold ?? markSet.has("bold");
560
+ const italic = resolved?.italic ?? markSet.has("italic");
561
+ const underline =
562
+ (resolved?.underline !== undefined && resolved.underline !== "none") ||
563
+ markSet.has("underline");
564
+ const strikethrough =
565
+ resolved?.strikethrough === true ||
566
+ markSet.has("strikethrough") ||
567
+ markSet.has("doubleStrikethrough");
568
+ const verticalAlign: "baseline" | "superscript" | "subscript" =
569
+ resolved?.verticalAlign === "superscript" || markSet.has("superscript")
570
+ ? "superscript"
571
+ : resolved?.verticalAlign === "subscript" || markSet.has("subscript")
572
+ ? "subscript"
573
+ : "baseline";
574
+ runs.set(`${block.blockId}:${segment.segmentId}`, {
575
+ ...(fontFamily ? { fontFamily } : {}),
576
+ ...(fontSizeHalfPoints !== undefined ? { fontSizeHalfPoints } : {}),
577
+ bold: Boolean(bold),
578
+ italic: Boolean(italic),
579
+ underline: Boolean(underline),
580
+ strikethrough: Boolean(strikethrough),
581
+ ...(typeof markAttrs.textColor === "string"
582
+ ? { color: markAttrs.textColor }
583
+ : {}),
584
+ ...(typeof markAttrs.backgroundColor === "string"
585
+ ? { highlight: markAttrs.backgroundColor }
586
+ : {}),
587
+ verticalAlign,
588
+ });
589
+ }
590
+ return runs;
591
+ }
@@ -23,7 +23,7 @@ import type {
23
23
  SurfaceTableRowSnapshot,
24
24
  } from "../../api/public-types";
25
25
  import type { TableStyleConditionalRegion } from "../../model/canonical-document.ts";
26
- import type { ResolvedTableStyleResolution } from "../table-style-resolver.ts";
26
+ import type { ResolvedTableStyleResolution } from "../formatting/table-style-resolver.ts";
27
27
 
28
28
  // ─── Public shapes ───────────────────────────────────────────────────────────
29
29
 
@@ -1,7 +1,12 @@
1
1
  import type { EditorSurfaceSnapshot } from "../../api/public-types";
2
2
  import type { CanonicalDocument } from "../../model/canonical-document.ts";
3
3
  import type { CompatibilityReport } from "../../core/state/editor-state.ts";
4
- import type { RuntimePageGraph } from "../layout/page-graph.ts";
4
+ // Pass 2 (2026-04-22) — `RuntimePageGraph` relocated out of
5
+ // `src/runtime/layout/` to `src/model/layout/runtime-page-graph-types.ts`
6
+ // so this envelope (and the `tryReadLaycacheEnvelope` probe that
7
+ // produces it) can be consumed from `src/session/**` without a
8
+ // `src/runtime/**` transit. See `docs/plans/cross-layer-coord-04.md §1.12`.
9
+ import type { RuntimePageGraph } from "../../model/layout/runtime-page-graph-types.ts";
5
10
 
6
11
  /**
7
12
  * L7 Phase 2.5 — prerender cache envelope shape.
@@ -1,9 +1,8 @@
1
1
  import type { EditorSurfaceSnapshot } from "../../api/public-types";
2
2
  import { createSelectionSnapshot } from "../../core/state/editor-state.ts";
3
- import { loadDocxEditorSessionAsync } from "../../io/docx-session.ts";
4
- import { createLoadScheduler } from "../../io/load-scheduler.ts";
5
3
  import { readOpcPackage } from "../../io/opc/package-reader.ts";
6
4
  import { writeOpcPackage } from "../../io/opc/package-writer.ts";
5
+ import { DocxSession } from "../../session/index.ts";
7
6
  import {
8
7
  LAYCACHE_SCHEMA_VERSION,
9
8
  LAYOUT_ENGINE_VERSION,
@@ -44,8 +43,8 @@ import { canonicalizeGraph } from "./graph-canonicalize.ts";
44
43
  * but do not enter the envelope.
45
44
  *
46
45
  * Node-safety:
47
- * - Uses `createLoadScheduler({ backendOverride: "sync" })` so the
48
- * async loader runs without DOM scheduler.yield.
46
+ * - Forwards `schedulerBackend: "sync"` to `DocxSession.open()` so
47
+ * the async loader runs without DOM scheduler.yield.
49
48
  * - Layout engine is constructed with
50
49
  * `autoUpgradeToCanvasBackend: false` so the empirical measurement
51
50
  * backend is used (no Canvas2D, no node-canvas).
@@ -118,26 +117,21 @@ export async function prerenderDocument(
118
117
  const bytes = toUint8Array(input);
119
118
  const fontFingerprint = options.fontFingerprint ?? resolveFontFingerprint();
120
119
 
121
- const scheduler = createLoadScheduler({ backendOverride: "sync" });
122
- let session;
123
- try {
124
- session = await loadDocxEditorSessionAsync({
125
- documentId: PRERENDER_DOCUMENT_ID,
126
- bytes,
127
- editorBuild: "prerender",
128
- scheduler,
129
- });
130
- } finally {
131
- scheduler.dispose();
132
- }
120
+ const openResult = await new DocxSession().open(bytes, {
121
+ documentId: PRERENDER_DOCUMENT_ID,
122
+ editorBuild: "prerender",
123
+ schedulerBackend: "sync",
124
+ });
133
125
 
134
- if (session.fatalError) {
126
+ // Slice 5e-1 promoted `fatalError` onto `OpenResult`, so no legacy
127
+ // view lookup is needed on this path anymore.
128
+ if (openResult.fatalError) {
135
129
  throw new Error(
136
- `prerenderDocument: failed to load input — ${session.fatalError.message ?? "fatal error"}`,
130
+ `prerenderDocument: failed to load input — ${openResult.fatalError.message ?? "fatal error"}`,
137
131
  );
138
132
  }
139
133
 
140
- // Normalize session-birth timestamps. `loadDocxEditorSessionAsync` sets
134
+ // Normalize session-birth timestamps. `DocxSession.open()` sets
141
135
  // `createdAt`/`updatedAt` from `new Date().toISOString()`; without this
142
136
  // override, two sequential prerender calls on identical bytes would
143
137
  // produce different envelopes → different customXml bytes → determinism
@@ -146,8 +140,8 @@ export async function prerenderDocument(
146
140
  // eliminating the only remaining source of non-determinism. The live
147
141
  // session's updatedAt is re-populated by runtime mutations anyway, so
148
142
  // the normalized value is irrelevant at runtime.
149
- const envelope: typeof session.initialSessionState.canonicalDocument = {
150
- ...session.initialSessionState.canonicalDocument,
143
+ const envelope: typeof openResult.canonicalDocument = {
144
+ ...openResult.canonicalDocument,
151
145
  createdAt: PRERENDER_NORMALIZED_TIMESTAMP,
152
146
  updatedAt: PRERENDER_NORMALIZED_TIMESTAMP,
153
147
  };
@@ -172,7 +166,8 @@ export async function prerenderDocument(
172
166
  });
173
167
 
174
168
  // Phase 2 Finale C3: pre-compute `compatibilityReport` so the warm Plan B
175
- // short-circuit in `loadDocxEditorSessionAsync` can skip the live
169
+ // short-circuit inside `DocxSession.open()` (which still transits the
170
+ // legacy async loader) can skip the live
176
171
  // `buildCompatibilityReport` call (~60–100 ms on extra-large). The report
177
172
  // is a pure function of `canonicalDocument` + `generatedAt`; pinning
178
173
  // `generatedAt` to `CACHE_NORMALIZED_GENERATED_AT` keeps the envelope
@@ -196,7 +191,7 @@ export async function prerenderDocument(
196
191
 
197
192
  // Plan B B.5 — persistToCustomXml: inject the envelope into the docx's
198
193
  // workflow-payload part. Re-parses the OPC package from bytes (~17 ms on
199
- // extra-large) because `LoadedDocxEditorSession` does not expose
194
+ // extra-large) because `DocxSession.open()` does not expose
200
195
  // `sourcePackage` publicly. Acceptable cost on the one-shot ingest path.
201
196
  let docWithCustomXml = bytes;
202
197
  let persistedToCustomXml = false;
@@ -23,7 +23,7 @@ import type {
23
23
  RevisionDecorationModel,
24
24
  RevisionDecorationEntry,
25
25
  } from "../../ui/headless/revision-decoration-model.ts";
26
- import type { ScopeRailSegment } from "../workflow-rail-segments.ts";
26
+ import type { ScopeRailSegment } from "../workflow/rail/types.ts";
27
27
  import type {
28
28
  DecorationIndex,
29
29
  RenderAnchorIndex,
@@ -244,6 +244,26 @@ export interface RenderAnchorIndex {
244
244
  byScopeId(scopeId: string): readonly RenderFrameRect[];
245
245
  byCommentId(commentId: string): RenderFrameRect | null;
246
246
  byRevisionId(revisionId: string): RenderFrameRect | null;
247
+ /**
248
+ * Locate an object's exact bounding rect given its object id.
249
+ *
250
+ * Object ids discriminate **the object itself** from **the block that
251
+ * contains it** — for inline images embedded in a paragraph,
252
+ * `byObjectId(imageId)` returns the image's own bbox, not the
253
+ * enclosing paragraph's rect. For block-level drawings (floating
254
+ * anchors resolved to a page slot) the rect matches the drawing's
255
+ * slot.
256
+ *
257
+ * Refactor/05 Slice 7c substrate: the kernel does not yet carry
258
+ * per-object metadata on anchor frames, so this method returns
259
+ * `null` today for every id except those that also happen to be
260
+ * block ids (preserving the Slice-5 `object-handles.ts` fallback).
261
+ * When the layout facet grows an object-specific index (`byObjectId`
262
+ * on the layout graph, Slice 7d follow-up), this method starts
263
+ * returning real object rects automatically — consumers that gate
264
+ * on `result !== null` pick up the upgrade without an API break.
265
+ */
266
+ byObjectId(objectId: string): RenderFrameRect | null;
247
267
  }
248
268
 
249
269
  // ---------------------------------------------------------------------------
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { MAIN_STORY_TARGET } from "../../core/selection/mapping.ts";
17
17
  import { recordPerfSample } from "../../ui-tailwind/editor-surface/perf-probe.ts";
18
+ import { storyTargetKey } from "../story-targeting.ts";
18
19
  import type { EditorStoryTarget } from "../../api/public-types";
19
20
  import type {
20
21
  PublicBlockFragment,
@@ -25,7 +26,7 @@ import type {
25
26
  } from "../layout/public-facet.ts";
26
27
  import type { CommentDecorationModel } from "../../ui/headless/comment-decoration-model.ts";
27
28
  import type { RevisionDecorationModel } from "../../ui/headless/revision-decoration-model.ts";
28
- import type { ScopeRailSegment } from "../workflow-rail-segments.ts";
29
+ import type { ScopeRailSegment } from "../workflow/rail/types.ts";
29
30
  import {
30
31
  resolveDecorationIndex,
31
32
  type LockedRangeInput,
@@ -719,7 +720,14 @@ function buildAnchorIndex(
719
720
  pxPerTwip = 1,
720
721
  decorationIndex: DecorationIndex = EMPTY_DECORATION_INDEX,
721
722
  ): RenderAnchorIndex {
723
+ // Main-document runtime offsets flow through `byRuntimeOffset` (body
724
+ // region). Header / footer / footnote stories have offsets LOCAL to
725
+ // their story — they can't share the `byRuntimeOffset` keyspace with
726
+ // the body without collisions. Refactor/05 Slice 7d indexes non-body
727
+ // blocks via `byRuntimeOffsetByStory`, a `storyTargetKey → offset →
728
+ // rect` map consulted when the caller passes a `story` argument.
722
729
  const byRuntimeOffset = new Map<number, RenderFrameRect>();
730
+ const byRuntimeOffsetByStory = new Map<string, Map<number, RenderFrameRect>>();
723
731
  const byFragmentId = new Map<string, RenderFrameRect>();
724
732
  const byBlockId = new Map<string, RenderFrameRect>();
725
733
  const byPageIndex = new Map<number, RenderFrameRect>();
@@ -730,28 +738,61 @@ function buildAnchorIndex(
730
738
  const tableColumnEdges = new Map<string, RenderFrameRect>();
731
739
  const tableRowEdges = new Map<string, RenderFrameRect>();
732
740
 
741
+ const recordBlock = (
742
+ block: import("./render-frame-types.ts").RenderBlock,
743
+ offsetMap: Map<number, RenderFrameRect>,
744
+ ): void => {
745
+ byFragmentId.set(block.fragment.fragmentId, block.frame);
746
+ byBlockId.set(block.fragment.blockId, block.frame);
747
+ offsetMap.set(block.fragment.from, block.frame);
748
+ for (const line of block.lines) {
749
+ for (const anchor of line.anchors) {
750
+ offsetMap.set(anchor.runtimeOffset, anchor.frame);
751
+ }
752
+ }
753
+ if (block.kind === "table" && block.tablePlan) {
754
+ recordTableAnchors(
755
+ block.fragment.blockId,
756
+ block.frame,
757
+ block.tablePlan,
758
+ pxPerTwip,
759
+ tableCellRects,
760
+ tableColumnEdges,
761
+ tableRowEdges,
762
+ );
763
+ }
764
+ };
765
+
766
+ const recordStoryRegion = (
767
+ region: import("./render-frame-types.ts").RenderStoryRegion,
768
+ ): void => {
769
+ const storyKey = storyTargetKey(region.storyTarget);
770
+ let storyMap = byRuntimeOffsetByStory.get(storyKey);
771
+ if (!storyMap) {
772
+ storyMap = new Map<number, RenderFrameRect>();
773
+ byRuntimeOffsetByStory.set(storyKey, storyMap);
774
+ }
775
+ for (const block of region.blocks) {
776
+ recordBlock(block, storyMap);
777
+ }
778
+ };
779
+
733
780
  for (const page of pages) {
734
781
  byPageIndex.set(page.page.pageIndex, page.frame);
782
+ // Body region feeds the shared `byRuntimeOffset` (main-story offsets).
735
783
  for (const block of page.regions.body.blocks) {
736
- byFragmentId.set(block.fragment.fragmentId, block.frame);
737
- byBlockId.set(block.fragment.blockId, block.frame);
738
- byRuntimeOffset.set(block.fragment.from, block.frame);
739
- for (const line of block.lines) {
740
- for (const anchor of line.anchors) {
741
- byRuntimeOffset.set(anchor.runtimeOffset, anchor.frame);
742
- }
743
- }
744
- // P4d: derive per-table cell/edge rects from the attached plan.
745
- if (block.kind === "table" && block.tablePlan) {
746
- recordTableAnchors(
747
- block.fragment.blockId,
748
- block.frame,
749
- block.tablePlan,
750
- pxPerTwip,
751
- tableCellRects,
752
- tableColumnEdges,
753
- tableRowEdges,
754
- );
784
+ recordBlock(block, byRuntimeOffset);
785
+ }
786
+ // Refactor/05 Slice 7d: index non-body regions so header / footer /
787
+ // footnote chrome consumers can resolve `byFragmentId` /
788
+ // `byBlockId` / story-scoped `byRuntimeOffset` without falling
789
+ // back to body lookups. Each region's offsets are keyed on its
790
+ // own story (header offsets are header-local, etc.).
791
+ if (page.regions.header) recordStoryRegion(page.regions.header);
792
+ if (page.regions.footer) recordStoryRegion(page.regions.footer);
793
+ if (page.regions.footnotes) {
794
+ for (const footnote of page.regions.footnotes) {
795
+ recordStoryRegion(footnote);
755
796
  }
756
797
  }
757
798
  }
@@ -771,15 +812,22 @@ function buildAnchorIndex(
771
812
 
772
813
  const resolveByRuntimeOffset = (
773
814
  offset: number,
774
- _story?: EditorStoryTarget,
815
+ story?: EditorStoryTarget,
775
816
  ): RenderFrameRect | null => {
776
- void _story;
777
817
  const lookup = shiftForDeltas(offset);
778
- const exact = byRuntimeOffset.get(lookup);
818
+ // Refactor/05 Slice 7d: pick the per-story offset map when the
819
+ // caller names a non-main story. Delta-shift is currently
820
+ // main-only — non-body stories don't receive predicted-dispatch
821
+ // deltas today, so the shift is a no-op for them.
822
+ const targetMap =
823
+ story && story.kind !== "main"
824
+ ? byRuntimeOffsetByStory.get(storyTargetKey(story)) ?? byRuntimeOffset
825
+ : byRuntimeOffset;
826
+ const exact = targetMap.get(lookup);
779
827
  if (exact) return exact;
780
828
  let best: RenderFrameRect | null = null;
781
829
  let bestDistance = Number.POSITIVE_INFINITY;
782
- for (const [key, rect] of byRuntimeOffset) {
830
+ for (const [key, rect] of targetMap) {
783
831
  const distance = Math.abs(key - lookup);
784
832
  if (distance < bestDistance) {
785
833
  best = rect;
@@ -812,8 +860,16 @@ function buildAnchorIndex(
812
860
  // the anchor maps (see `shiftForDeltas` rationale above).
813
861
  const loShifted = shiftForDeltas(lo);
814
862
  const hiShifted = shiftForDeltas(hi);
863
+ // Refactor/05 Slice 7d: pick the per-story offset map when the
864
+ // selection lives in a non-main story (header / footer /
865
+ // footnote). Offsets are story-local so iterating the main map
866
+ // would never match.
867
+ const targetMap =
868
+ story && story.kind !== "main"
869
+ ? byRuntimeOffsetByStory.get(storyTargetKey(story)) ?? byRuntimeOffset
870
+ : byRuntimeOffset;
815
871
  let union: RenderFrameRect | null = null;
816
- for (const [key, rect] of byRuntimeOffset) {
872
+ for (const [key, rect] of targetMap) {
817
873
  if (key < loShifted || key >= hiShifted) continue;
818
874
  union = unionRects(union, rect);
819
875
  }
@@ -855,6 +911,19 @@ function buildAnchorIndex(
855
911
  );
856
912
  return match?.frame ?? null;
857
913
  },
914
+ // Refactor/05 Slice 7c substrate: object-specific index. Falls back
915
+ // to `byBlockId` because the render kernel does not yet carry
916
+ // per-object metadata on anchor frames — the layout facet would
917
+ // need to emit `byObjectId` entries on the page graph. When that
918
+ // follow-up ships, populate a dedicated `byObjectIdMap` here and
919
+ // consult it before the `byBlockId` fallback. The fallback is
920
+ // correct for block-level drawings (whose object id equals their
921
+ // block id); it is `heuristic` for inline images (which share
922
+ // their block id with the enclosing paragraph). `object-handles.ts`
923
+ // tags those handles `precision: "heuristic"` accordingly.
924
+ byObjectId(objectId) {
925
+ return byBlockId.get(objectId) ?? null;
926
+ },
858
927
  };
859
928
  }
860
929