@beyondwork/docx-react-component 1.0.66 → 1.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/README.md +75 -931
  2. package/package.json +26 -27
  3. package/src/api/anchor-conversion.ts +43 -0
  4. package/src/api/editor-state-types.ts +2 -1
  5. package/src/api/public-types.ts +504 -101
  6. package/src/api/session-state.ts +4 -0
  7. package/src/api/v3/README.md +91 -0
  8. package/src/api/v3/_create.ts +146 -0
  9. package/src/api/v3/_layer-metadata.ts +362 -0
  10. package/src/api/v3/_mocks.ts +84 -0
  11. package/src/api/v3/_runtime-handle.ts +162 -0
  12. package/src/api/v3/_ux-response.ts +73 -0
  13. package/src/api/v3/ai/_metadata-audit.ts +225 -0
  14. package/src/api/v3/ai/attach.ts +235 -0
  15. package/src/api/v3/ai/bundle.ts +132 -0
  16. package/src/api/v3/ai/explain.ts +144 -0
  17. package/src/api/v3/ai/export.ts +54 -0
  18. package/src/api/v3/ai/inspect.ts +118 -0
  19. package/src/api/v3/ai/policy.ts +77 -0
  20. package/src/api/v3/ai/replacement.ts +341 -0
  21. package/src/api/v3/ai/resolve.ts +133 -0
  22. package/src/api/v3/index.ts +79 -0
  23. package/src/api/v3/runtime/chart.ts +310 -0
  24. package/src/api/v3/runtime/clipboard.ts +81 -0
  25. package/src/api/v3/runtime/collab.ts +331 -0
  26. package/src/api/v3/runtime/content.ts +236 -0
  27. package/src/api/v3/runtime/document.ts +282 -0
  28. package/src/api/v3/runtime/formatting.ts +186 -0
  29. package/src/api/v3/runtime/geometry.ts +349 -0
  30. package/src/api/v3/runtime/layout.ts +108 -0
  31. package/src/api/v3/runtime/review.ts +129 -0
  32. package/src/api/v3/runtime/search.ts +74 -0
  33. package/src/api/v3/runtime/table.ts +63 -0
  34. package/src/api/v3/runtime/workflow.ts +434 -0
  35. package/src/api/v3/ui/_context.ts +86 -0
  36. package/src/api/v3/ui/_create.ts +65 -0
  37. package/src/api/v3/ui/_types.ts +520 -0
  38. package/src/api/v3/ui/chrome-composition.ts +342 -0
  39. package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
  40. package/src/api/v3/ui/chrome.ts +476 -0
  41. package/src/api/v3/ui/debug.ts +124 -0
  42. package/src/api/v3/ui/index.ts +64 -0
  43. package/src/api/v3/ui/overlays-visibility.ts +170 -0
  44. package/src/api/v3/ui/overlays.ts +427 -0
  45. package/src/api/v3/ui/scope.ts +71 -0
  46. package/src/api/v3/ui/session.ts +100 -0
  47. package/src/api/v3/ui/surface.ts +170 -0
  48. package/src/api/v3/ui/viewport.ts +303 -0
  49. package/src/core/commands/index.ts +28 -6
  50. package/src/core/commands/list-commands.ts +3 -2
  51. package/src/core/commands/section-layout-commands.ts +9 -8
  52. package/src/core/schema/text-schema.ts +16 -0
  53. package/src/core/selection/mapping.ts +33 -72
  54. package/src/core/state/editor-state.ts +96 -189
  55. package/src/index.ts +23 -4
  56. package/src/io/chart-preview-resolver.ts +1 -1
  57. package/src/io/docx-session.ts +36 -4795
  58. package/src/io/export/build-app-properties-xml.ts +1 -1
  59. package/src/io/export/serialize-comments.ts +1 -1
  60. package/src/io/export/serialize-headers-footers.ts +6 -1
  61. package/src/io/export/serialize-main-document.ts +45 -0
  62. package/src/io/export/serialize-run-formatting.ts +17 -2
  63. package/src/io/export/twip.ts +1 -1
  64. package/src/io/normalize/normalize-text.ts +27 -20
  65. package/src/io/ooxml/chart/parse-series.ts +1 -1
  66. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  67. package/src/io/ooxml/chart/types.ts +1 -1
  68. package/src/io/ooxml/classify-embedding.ts +83 -33
  69. package/src/io/ooxml/parse-fill.ts +1 -1
  70. package/src/io/ooxml/parse-main-document.ts +71 -1
  71. package/src/io/ooxml/parse-object.ts +14 -10
  72. package/src/io/ooxml/parse-run-formatting.ts +47 -1
  73. package/src/io/ooxml/property-grab-bag.ts +2 -2
  74. package/src/io/ooxml/units.ts +11 -0
  75. package/src/io/ooxml/workflow-payload.ts +282 -7
  76. package/src/model/anchor.ts +85 -0
  77. package/src/model/canonical-document.ts +351 -15
  78. package/src/model/chart-types.ts +1 -1
  79. package/src/model/layout/index.ts +83 -0
  80. package/src/model/layout/page-graph-types.ts +181 -0
  81. package/src/model/layout/page-layout-snapshot.ts +105 -0
  82. package/src/model/layout/resolved-layout-types.ts +47 -0
  83. package/src/model/layout/runtime-page-graph-types.ts +102 -0
  84. package/src/model/paragraph-scope-ids.ts +72 -0
  85. package/src/model/review/comment-types.ts +112 -0
  86. package/src/model/review/index.ts +2 -0
  87. package/src/model/review/revision-types.ts +215 -0
  88. package/src/model/snapshot.ts +32 -0
  89. package/src/review/store/comment-store.ts +21 -47
  90. package/src/review/store/revision-types.ts +40 -198
  91. package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
  92. package/src/runtime/collab/runtime-collab-sync.ts +13 -3
  93. package/src/runtime/collab-session.ts +1 -1
  94. package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
  95. package/src/runtime/debug/event-ring-buffer.ts +64 -0
  96. package/src/runtime/debug/probability-sampler.ts +18 -0
  97. package/src/runtime/debug/runtime-debug-facet.ts +67 -0
  98. package/src/runtime/debug/stage-tokens.ts +31 -0
  99. package/src/runtime/debug/telemetry-bus.ts +271 -0
  100. package/src/runtime/debug/types.ts +275 -0
  101. package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
  102. package/src/runtime/document-layout.ts +8 -6
  103. package/src/runtime/document-runtime.ts +843 -1141
  104. package/src/runtime/document-search.ts +1 -1
  105. package/src/runtime/edit-ops/index.ts +1 -1
  106. package/src/runtime/external-send-runtime.ts +1 -1
  107. package/src/runtime/formatting/document-lookup.ts +235 -0
  108. package/src/runtime/formatting/field/registry.ts +41 -0
  109. package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
  110. package/src/runtime/formatting/font-resolution.ts +83 -0
  111. package/src/runtime/formatting/formatting-context.ts +903 -0
  112. package/src/runtime/formatting/formatting-types.ts +157 -0
  113. package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
  114. package/src/runtime/formatting/index.ts +125 -0
  115. package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
  116. package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
  117. package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
  118. package/src/runtime/formatting/projector.ts +75 -0
  119. package/src/runtime/formatting/resolve-effective.ts +407 -0
  120. package/src/runtime/formatting/revision-display.ts +105 -0
  121. package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
  122. package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
  123. package/src/runtime/formatting/telemetry-bridge.ts +106 -0
  124. package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
  125. package/src/runtime/geometry/caret-geometry.ts +164 -0
  126. package/src/runtime/geometry/geometry-facet.ts +364 -0
  127. package/src/runtime/geometry/geometry-types.ts +256 -0
  128. package/src/runtime/geometry/hit-test.ts +125 -0
  129. package/src/runtime/geometry/index.ts +71 -0
  130. package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
  131. package/src/runtime/geometry/invalidation.ts +35 -0
  132. package/src/runtime/geometry/object-handles.ts +77 -0
  133. package/src/runtime/geometry/overlay-rects.ts +85 -0
  134. package/src/runtime/geometry/project-anchors.ts +100 -0
  135. package/src/runtime/geometry/project-fragments.ts +216 -0
  136. package/src/runtime/geometry/projector.ts +129 -0
  137. package/src/runtime/geometry/replacement-envelope.ts +130 -0
  138. package/src/runtime/geometry/viewport.ts +218 -0
  139. package/src/runtime/layout/compat-input-ledger.ts +211 -0
  140. package/src/runtime/layout/index.ts +6 -1
  141. package/src/runtime/layout/inert-layout-facet.ts +12 -7
  142. package/src/runtime/layout/layout-engine-instance.ts +189 -11
  143. package/src/runtime/layout/layout-engine-version.ts +450 -1
  144. package/src/runtime/layout/layout-facet-types.ts +60 -0
  145. package/src/runtime/layout/layout-measurement-provider.ts +13 -0
  146. package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
  147. package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
  148. package/src/runtime/layout/page-graph.ts +62 -209
  149. package/src/runtime/layout/page-story-resolver.ts +7 -12
  150. package/src/runtime/layout/paginated-layout-engine.ts +186 -11
  151. package/src/runtime/layout/project-block-fragments.ts +11 -0
  152. package/src/runtime/layout/projector.ts +90 -0
  153. package/src/runtime/layout/public-facet.ts +187 -442
  154. package/src/runtime/layout/resolved-formatting-state.ts +158 -26
  155. package/src/runtime/layout/table-render-plan.ts +1 -1
  156. package/src/runtime/prerender/cache-envelope.ts +6 -1
  157. package/src/runtime/prerender/prerender-document.ts +18 -23
  158. package/src/runtime/render/decoration-resolver.ts +1 -1
  159. package/src/runtime/render/render-frame-types.ts +20 -0
  160. package/src/runtime/render/render-kernel.ts +94 -25
  161. package/src/runtime/scopes/_formatting-seam.ts +262 -0
  162. package/src/runtime/scopes/_scope-dependencies.ts +49 -0
  163. package/src/runtime/scopes/action-validation.ts +356 -0
  164. package/src/runtime/scopes/attach-explanation.ts +102 -0
  165. package/src/runtime/scopes/audit-bundle.ts +71 -0
  166. package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
  167. package/src/runtime/scopes/compile-scope.ts +262 -0
  168. package/src/runtime/scopes/compiler-service.ts +431 -0
  169. package/src/runtime/scopes/create-issue.ts +107 -0
  170. package/src/runtime/scopes/enumerate-scopes.ts +543 -0
  171. package/src/runtime/scopes/evidence.ts +233 -0
  172. package/src/runtime/scopes/index.ts +150 -0
  173. package/src/runtime/scopes/position-map.ts +214 -0
  174. package/src/runtime/scopes/preservation-boundary.ts +91 -0
  175. package/src/runtime/scopes/projector.ts +49 -0
  176. package/src/runtime/scopes/replaceability.ts +87 -0
  177. package/src/runtime/scopes/replacement/apply.ts +228 -0
  178. package/src/runtime/scopes/replacement/compile.ts +59 -0
  179. package/src/runtime/scopes/replacement/propose.ts +42 -0
  180. package/src/runtime/scopes/resolve-reference.ts +347 -0
  181. package/src/runtime/scopes/review-bundle.ts +141 -0
  182. package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
  183. package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
  184. package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
  185. package/src/runtime/scopes/scope-kinds/field.ts +65 -0
  186. package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
  187. package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
  188. package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
  189. package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
  190. package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
  191. package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
  192. package/src/runtime/scopes/scope-kinds/table.ts +55 -0
  193. package/src/runtime/scopes/scope-range.ts +208 -0
  194. package/src/runtime/scopes/semantic-scope-types.ts +454 -0
  195. package/src/runtime/scopes/workflow-overlap.ts +92 -0
  196. package/src/runtime/selection/index.ts +1 -1
  197. package/src/runtime/structure-ops/fragment-insert.ts +1 -1
  198. package/src/runtime/structure-ops/index.ts +1 -1
  199. package/src/runtime/surface-projection.ts +232 -262
  200. package/src/runtime/units.ts +4 -2
  201. package/src/runtime/workflow/coordinator.ts +1348 -0
  202. package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
  203. package/src/runtime/workflow/index.ts +25 -0
  204. package/src/runtime/workflow/markup-mode-policy.ts +98 -0
  205. package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
  206. package/src/runtime/workflow/metadata-persistence.ts +306 -0
  207. package/src/runtime/workflow/metadata-writer.ts +123 -0
  208. package/src/runtime/workflow/overlay-store.ts +690 -0
  209. package/src/runtime/workflow/projector.ts +127 -0
  210. package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
  211. package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
  212. package/src/runtime/workflow/rail/types.ts +198 -0
  213. package/src/runtime/workflow/scope-rail-composer.ts +39 -0
  214. package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
  215. package/src/runtime/workflow/scope-writer.ts +188 -0
  216. package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
  217. package/src/runtime/workflow/visibility-policy.ts +129 -0
  218. package/src/session/_sync-legacy.ts +66 -0
  219. package/src/session/export/embedded-reconstitute.ts +104 -0
  220. package/src/session/export/export-diagnostics.ts +85 -0
  221. package/src/session/export/export-validation.ts +110 -0
  222. package/src/session/export/index.ts +34 -0
  223. package/src/session/export/preservation-reattach.ts +30 -0
  224. package/src/session/export/serialize-dispatch.ts +165 -0
  225. package/src/session/export/stateful-export-pipeline.ts +432 -0
  226. package/src/session/export/stateful-export.ts +684 -0
  227. package/src/session/import/canonical-assembly.ts +227 -0
  228. package/src/session/import/diagnostics-session.ts +54 -0
  229. package/src/session/import/embedded-discovery.ts +225 -0
  230. package/src/session/import/embedded-offload.ts +337 -0
  231. package/src/session/import/import-diagnostics.ts +69 -0
  232. package/src/session/import/loader-types.ts +313 -0
  233. package/src/session/import/loader.ts +1834 -0
  234. package/src/session/import/normalize.ts +195 -0
  235. package/src/session/import/package-parts.ts +217 -0
  236. package/src/session/import/package-read.ts +195 -0
  237. package/src/session/import/parse-orchestration.ts +105 -0
  238. package/src/session/import/part-constants.ts +70 -0
  239. package/src/session/import/part-discovery.ts +94 -0
  240. package/src/session/import/preservation-index.ts +46 -0
  241. package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
  242. package/src/session/import/review-import.ts +508 -0
  243. package/src/session/import/styles-consolidation.ts +281 -0
  244. package/src/session/import/workflow-scope-import.ts +256 -0
  245. package/src/session/index.ts +37 -0
  246. package/src/session/session-state.ts +69 -0
  247. package/src/session/session.ts +532 -0
  248. package/src/session/shared/protection.ts +228 -0
  249. package/src/session/shared/session-utils.ts +82 -0
  250. package/src/session/types.ts +499 -0
  251. package/src/shell/chart-snapshots.ts +96 -0
  252. package/src/shell/media-previews.ts +85 -0
  253. package/src/shell/overlay-anchor-bridge.ts +53 -0
  254. package/src/shell/paste-adapter.ts +23 -0
  255. package/src/shell/ref-commands.ts +1697 -0
  256. package/src/shell/ref-utilities.ts +48 -0
  257. package/src/shell/search.ts +51 -0
  258. package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
  259. package/src/shell/ui-subscriber-channels.ts +81 -0
  260. package/src/shell/use-collab-sync.ts +116 -0
  261. package/src/ui/WordReviewEditor.tsx +496 -2051
  262. package/src/ui/editor-shell-view.tsx +30 -1
  263. package/src/ui/editor-surface-controller.tsx +49 -1
  264. package/src/ui/headless/revision-decoration-model.ts +83 -0
  265. package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
  266. package/src/ui/headless/scoped-chrome-policy.ts +2 -2
  267. package/src/ui/headless/selection-tool-context.ts +1 -1
  268. package/src/ui/headless/selection-tool-resolver.ts +1 -1
  269. package/src/ui/runtime-shortcut-dispatch.ts +46 -1
  270. package/src/ui/ui-controller-factory.ts +221 -0
  271. package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
  272. package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
  273. package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
  274. package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
  275. package/src/ui-tailwind/chart/render/area.tsx +3 -3
  276. package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
  277. package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
  278. package/src/ui-tailwind/chart/render/combo.tsx +2 -2
  279. package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
  280. package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
  281. package/src/ui-tailwind/chart/render/line.tsx +3 -3
  282. package/src/ui-tailwind/chart/render/pie.tsx +6 -6
  283. package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
  284. package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
  285. package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
  286. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
  287. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
  288. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
  289. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
  290. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
  291. package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
  292. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
  293. package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
  294. package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
  295. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
  296. package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
  297. package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
  298. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
  299. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
  300. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
  301. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
  302. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
  303. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
  304. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
  305. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
  306. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
  307. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
  308. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
  309. package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
  310. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
  311. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
  312. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
  313. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
  314. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
  315. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
  316. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
  317. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
  318. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
  319. package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
  320. package/src/ui-tailwind/debug/README.md +57 -0
  321. package/src/ui-tailwind/debug/index.ts +3 -0
  322. package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
  323. package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
  324. package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
  325. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
  326. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
  327. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
  328. package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
  329. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
  330. package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
  331. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
  332. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
  333. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
  334. package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
  335. package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
  336. package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
  337. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
  338. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
  339. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
  340. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
  341. package/src/ui-tailwind/index.ts +0 -5
  342. package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
  343. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
  344. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
  345. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
  346. package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
  347. package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
  348. package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
  349. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
  350. package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
  351. package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
  352. package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
  353. package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
  354. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
  355. package/src/ui-tailwind/review-workspace/types.ts +408 -0
  356. package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
  357. package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
  358. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
  359. package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
  360. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
  361. package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
  362. package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
  363. package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
  364. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
  365. package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
  366. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
  367. package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
  368. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
  369. package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
  370. package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
  371. package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
  372. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
  373. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  374. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
  375. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
  376. package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
  377. package/src/ui-tailwind/ui-api-context.tsx +43 -0
  378. package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
  379. package/src/validation/compatibility-engine.ts +6 -6
  380. package/src/runtime/styles-cascade.ts +0 -33
  381. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
  382. /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
  383. /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
  384. /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
@@ -0,0 +1,151 @@
1
+ import { useMemo } from "react";
2
+
3
+ import type {
4
+ DocumentNavigationSnapshot,
5
+ RuntimeRenderSnapshot,
6
+ WordReviewEditorChromeVisibility,
7
+ ZoomLevel,
8
+ } from "../../api/public-types.ts";
9
+ import type { ActiveSelectionToolModel } from "../../ui/headless/selection-tool-types";
10
+
11
+ import {
12
+ buildPageChromeModel,
13
+ type PageChromeModel,
14
+ } from "./page-chrome.ts";
15
+ import {
16
+ buildPageShellMetrics,
17
+ resolveZoomMultiplier,
18
+ type PageShellMetrics,
19
+ } from "./page-shell-metrics.ts";
20
+ import {
21
+ resolveActiveParagraphLayout,
22
+ type ActiveParagraphLayout,
23
+ } from "./paragraph-layout.ts";
24
+
25
+ export interface UseDerivedViewStateOptions {
26
+ snapshot: RuntimeRenderSnapshot;
27
+ documentNavigation: DocumentNavigationSnapshot | undefined;
28
+ activeSelectionTool: ActiveSelectionToolModel | null | undefined;
29
+ chromeVisibility: Pick<WordReviewEditorChromeVisibility, "contextToolbars" | "pageChrome">;
30
+ selectionPosition: number;
31
+ shouldResolveActiveParagraphLayout: boolean;
32
+ zoomLevel: ZoomLevel;
33
+ numericZoomScale: number;
34
+ viewportWidth: number | undefined;
35
+ viewportHeight: number | undefined;
36
+ isPageWorkspace: boolean;
37
+ }
38
+
39
+ export interface DerivedViewState {
40
+ activeParagraphLayout: ActiveParagraphLayout | null;
41
+ pageChromeModel: PageChromeModel;
42
+ gatedSelectionTool: ActiveSelectionToolModel | null;
43
+ pageShellMetrics: PageShellMetrics;
44
+ zoomScale: number;
45
+ pageZoomBucket: "low" | "base" | "high" | undefined;
46
+ }
47
+
48
+ /**
49
+ * Bundle of memoized derived view-state that TwReviewWorkspace feeds
50
+ * into its chrome + overlay layers:
51
+ *
52
+ * - `activeParagraphLayout` — page-chrome ruler input; resolved only
53
+ * when the ruler is visible (layout tools open + page chrome
54
+ * present + page workspace mode).
55
+ * - `pageChromeModel` — line numbering / page border / grid style.
56
+ * - `gatedSelectionTool` — chromeVisibility-scoped view of the host's
57
+ * selection tool model.
58
+ * - `pageShellMetrics` — real-twip frame + content-inset styles.
59
+ * - `zoomScale` — numeric → passthrough; symbolic ("pageWidth" /
60
+ * "onePage") → resolved against the active section's real paper
61
+ * dimensions via `resolveZoomMultiplier` (P2.c).
62
+ * - `pageZoomBucket` — chromeless classification used by
63
+ * `TwPageRuler` + zoom controls.
64
+ */
65
+ export function useDerivedViewState(
66
+ options: UseDerivedViewStateOptions,
67
+ ): DerivedViewState {
68
+ const {
69
+ snapshot,
70
+ documentNavigation,
71
+ activeSelectionTool,
72
+ chromeVisibility,
73
+ selectionPosition,
74
+ shouldResolveActiveParagraphLayout,
75
+ zoomLevel,
76
+ numericZoomScale,
77
+ viewportWidth,
78
+ viewportHeight,
79
+ isPageWorkspace,
80
+ } = options;
81
+
82
+ const activeParagraphLayout = useMemo(
83
+ () =>
84
+ shouldResolveActiveParagraphLayout
85
+ ? resolveActiveParagraphLayout(snapshot.surface, selectionPosition)
86
+ : null,
87
+ [selectionPosition, shouldResolveActiveParagraphLayout, snapshot.surface],
88
+ );
89
+
90
+ const pageChromeModel = useMemo(
91
+ () =>
92
+ buildPageChromeModel(
93
+ snapshot.surface,
94
+ snapshot.pageLayout,
95
+ documentNavigation,
96
+ snapshot.activeStory,
97
+ ),
98
+ [documentNavigation, snapshot.activeStory, snapshot.pageLayout, snapshot.surface],
99
+ );
100
+
101
+ const gatedSelectionTool = useMemo(() => {
102
+ if (!activeSelectionTool) return null;
103
+ if (
104
+ activeSelectionTool.kind === "structure-context" &&
105
+ !chromeVisibility.contextToolbars
106
+ ) {
107
+ return null;
108
+ }
109
+ return activeSelectionTool;
110
+ }, [activeSelectionTool, chromeVisibility.contextToolbars]);
111
+
112
+ const pageShellMetrics = useMemo(
113
+ () => buildPageShellMetrics(snapshot.pageLayout),
114
+ [snapshot.pageLayout],
115
+ );
116
+
117
+ const zoomScale = useMemo(() => {
118
+ if (typeof zoomLevel === "number") return numericZoomScale;
119
+ return resolveZoomMultiplier(
120
+ zoomLevel,
121
+ pageShellMetrics.frameWidthPx ?? 0,
122
+ pageShellMetrics.frameHeightPx ?? 0,
123
+ viewportWidth,
124
+ viewportHeight,
125
+ );
126
+ }, [
127
+ zoomLevel,
128
+ numericZoomScale,
129
+ pageShellMetrics.frameWidthPx,
130
+ pageShellMetrics.frameHeightPx,
131
+ viewportWidth,
132
+ viewportHeight,
133
+ ]);
134
+
135
+ const pageZoomBucket: "low" | "base" | "high" | undefined = !isPageWorkspace
136
+ ? undefined
137
+ : zoomScale < 1
138
+ ? "low"
139
+ : zoomScale > 1
140
+ ? "high"
141
+ : "base";
142
+
143
+ return {
144
+ activeParagraphLayout,
145
+ pageChromeModel,
146
+ gatedSelectionTool,
147
+ pageShellMetrics,
148
+ zoomScale,
149
+ pageZoomBucket,
150
+ };
151
+ }
@@ -0,0 +1,70 @@
1
+ import { useMemo } from "react";
2
+
3
+ import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
4
+ import type { SessionCapabilities } from "../../api/public-types";
5
+
6
+ export interface DiagnosticsSignal {
7
+ severity: "none" | "info" | "warning" | "blocked";
8
+ count: number;
9
+ }
10
+
11
+ export interface UseDiagnosticsSignalOptions {
12
+ snapshot: RuntimeRenderSnapshot;
13
+ caps: SessionCapabilities | undefined;
14
+ preserveOnlyCount: number;
15
+ blockedReasonsCount: number;
16
+ }
17
+
18
+ /**
19
+ * Phase E.1 — diagnostics signal feeds `resolveChromeComposition` so the
20
+ * `health` rail tab can enter `visibleTabs`. Severity ladder mirrors
21
+ * `TwAlertBanner`'s precedence (§6.17): blocked export → workflow blocked
22
+ * → preserve-only / warnings → info.
23
+ *
24
+ * Perf — I1 fix: the workspace re-renders on every PM transaction
25
+ * (inverted-truth architecture — `view.updateState()` replaces PM state
26
+ * wholesale per commit, per `CLAUDE.md §Performance Invariants #4`).
27
+ * The derivations below run `Array.prototype.filter` over
28
+ * `snapshot.compatibility.featureEntries` and `snapshot.warnings`, so
29
+ * they MUST memoize on structural inputs. One memo for everything the
30
+ * signal needs keeps the hot path tight.
31
+ */
32
+ export function useDiagnosticsSignal(
33
+ options: UseDiagnosticsSignalOptions,
34
+ ): DiagnosticsSignal {
35
+ const { snapshot, caps, preserveOnlyCount, blockedReasonsCount } = options;
36
+
37
+ return useMemo(() => {
38
+ const featureEntries = snapshot.compatibility.featureEntries;
39
+ const unsupportedFatalCount =
40
+ caps?.unsupportedFatalCount ??
41
+ featureEntries.filter((e) => e.featureClass === "unsupported-fatal").length;
42
+ let infoWarningCount = 0;
43
+ for (const w of snapshot.warnings) {
44
+ if (w.severity === "info") infoWarningCount += 1;
45
+ }
46
+ const blockingWarningCount = snapshot.warnings.length - infoWarningCount;
47
+ const count =
48
+ caps?.healthIssueCount ??
49
+ preserveOnlyCount + unsupportedFatalCount + snapshot.warnings.length;
50
+ const severity: "none" | "info" | "warning" | "blocked" =
51
+ snapshot.compatibility.blockExport || unsupportedFatalCount > 0
52
+ ? "blocked"
53
+ : preserveOnlyCount > 0 ||
54
+ blockingWarningCount > 0 ||
55
+ blockedReasonsCount > 0
56
+ ? "warning"
57
+ : infoWarningCount > 0
58
+ ? "info"
59
+ : "none";
60
+ return { severity, count };
61
+ }, [
62
+ snapshot.compatibility.featureEntries,
63
+ snapshot.compatibility.blockExport,
64
+ snapshot.warnings,
65
+ caps?.unsupportedFatalCount,
66
+ caps?.healthIssueCount,
67
+ preserveOnlyCount,
68
+ blockedReasonsCount,
69
+ ]);
70
+ }
@@ -0,0 +1,40 @@
1
+ import { useMemo } from "react";
2
+
3
+ import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
4
+
5
+ export interface GrabbedSegmentOffsets {
6
+ from: number;
7
+ to: number;
8
+ }
9
+
10
+ /**
11
+ * N6 — resolve grabbed-object segment offsets from the surface so the
12
+ * selection overlay can query the anchor index without a full surface walk.
13
+ */
14
+ export function useGrabbedSegmentOffsets(
15
+ snapshot: RuntimeRenderSnapshot,
16
+ ): GrabbedSegmentOffsets | null {
17
+ return useMemo(() => {
18
+ const objectId = snapshot.grabbedObjectId ?? null;
19
+ if (!objectId || !snapshot.surface) return null;
20
+ for (const block of snapshot.surface.blocks) {
21
+ if (!("segments" in block)) continue;
22
+ for (const seg of (block as { segments?: unknown[] }).segments ?? []) {
23
+ const s = seg as {
24
+ kind?: string;
25
+ mediaId?: string;
26
+ from?: number;
27
+ to?: number;
28
+ };
29
+ if (
30
+ (s.kind === "image" || s.kind === "shape") &&
31
+ s.mediaId === objectId &&
32
+ s.from != null
33
+ ) {
34
+ return { from: s.from, to: s.to ?? s.from + 1 };
35
+ }
36
+ }
37
+ }
38
+ return null;
39
+ }, [snapshot.grabbedObjectId, snapshot.surface]);
40
+ }
@@ -0,0 +1,55 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ /**
4
+ * P3.b + P5b + P14.b — subscribe to layout facet events that should
5
+ * trigger a chrome re-projection (zoom, render frame ready, layout
6
+ * recomputed / committed, page count changes, measurement backend
7
+ * ready, etc.) and expose a monotonic revision counter. Consumers
8
+ * include the counter in dependency arrays of useMemo-backed placement
9
+ * calculations so they re-run when the layout engine emits new rects.
10
+ *
11
+ * Bumps are coalesced through `queueMicrotask` so multiple events in
12
+ * the same synchronous tick fold into one re-render.
13
+ */
14
+ export function useLayoutFacetRenderSignal(
15
+ layoutFacet: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet | undefined,
16
+ ): number {
17
+ const [renderFrameRevision, setRenderFrameRevision] = useState(0);
18
+
19
+ useEffect(() => {
20
+ if (!layoutFacet) return;
21
+ let pendingBump = false;
22
+ let cancelled = false;
23
+ const scheduleBump = () => {
24
+ if (pendingBump || cancelled) return;
25
+ pendingBump = true;
26
+ queueMicrotask(() => {
27
+ pendingBump = false;
28
+ if (cancelled) return;
29
+ setRenderFrameRevision((n) => n + 1);
30
+ });
31
+ };
32
+ const unsub = layoutFacet.subscribe((event) => {
33
+ switch (event.kind) {
34
+ case "zoom_changed":
35
+ case "render_frame_ready":
36
+ case "layout_recomputed":
37
+ case "incremental_relayout":
38
+ case "page_count_changed":
39
+ case "page_field_dirtied":
40
+ case "measurement_backend_ready":
41
+ case "layout_committed":
42
+ scheduleBump();
43
+ break;
44
+ default:
45
+ break;
46
+ }
47
+ });
48
+ return () => {
49
+ cancelled = true;
50
+ unsub();
51
+ };
52
+ }, [layoutFacet]);
53
+
54
+ return renderFrameRevision;
55
+ }
@@ -0,0 +1,130 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+
3
+ import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
4
+ import {
5
+ useVisibleBlockRange,
6
+ useVisiblePageIndexRange,
7
+ } from "../page-stack/use-visible-block-range.ts";
8
+
9
+ export interface UsePageMarkersOptions {
10
+ pageStackScrollRoot: HTMLElement | null;
11
+ snapshot: RuntimeRenderSnapshot;
12
+ layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
13
+ }
14
+
15
+ export interface PageMarkersResult {
16
+ pageMarkers: readonly HTMLElement[];
17
+ visibleBlockRange: ReturnType<typeof useVisibleBlockRange>;
18
+ visiblePageIndexRange: ReturnType<typeof useVisiblePageIndexRange>;
19
+ }
20
+
21
+ /**
22
+ * L7 Phase 2 Task 2.2.4a — viewport-scroll wiring.
23
+ *
24
+ * Collects DOM elements with `[data-page-frame]` from the PM surface so
25
+ * the IntersectionObserver inside `useVisibleBlockRange` /
26
+ * `useVisiblePageIndexRange` can determine which pages are currently
27
+ * visible. A MutationObserver refreshes the set when the PM surface
28
+ * re-renders (new document load, page count change, etc.).
29
+ *
30
+ * The hook also derives `selectionBlockIndex` internally so the visible
31
+ * range always includes the selection, and pushes the resolved visible
32
+ * block range into the layout facet (layout's viewport-culling
33
+ * machinery).
34
+ *
35
+ * Returns the synthetic page-0 marker prepended when the real DOM lacks
36
+ * one — the boundary widgets between page N and N+1 carry
37
+ * `data-page-frame="N+1"`, so page 0 has no widget before it.
38
+ */
39
+ export function usePageMarkers(options: UsePageMarkersOptions): PageMarkersResult {
40
+ const { pageStackScrollRoot, snapshot, layoutFacet } = options;
41
+
42
+ const [pageMarkers, setPageMarkers] = useState<readonly HTMLElement[]>([]);
43
+
44
+ useEffect(() => {
45
+ const root = pageStackScrollRoot;
46
+ if (!root) {
47
+ setPageMarkers([]);
48
+ return undefined;
49
+ }
50
+ const refresh = () => {
51
+ const found = Array.from(root.querySelectorAll<HTMLElement>("[data-page-frame]"));
52
+ const hasPage0 = found.some(
53
+ (el) => el.getAttribute("data-page-frame") === "0",
54
+ );
55
+ if (!hasPage0 && found.length > 0) {
56
+ const page1Marker = found.find(
57
+ (el) => el.getAttribute("data-page-frame") === "1",
58
+ );
59
+ const page1First = page1Marker
60
+ ? Number(page1Marker.getAttribute("data-page-first-block-index") ?? "")
61
+ : NaN;
62
+ const page0Last = Number.isFinite(page1First) && page1First > 0
63
+ ? page1First - 1
64
+ : -1;
65
+ const ownerDoc = found[0]!.ownerDocument;
66
+ const synth = ownerDoc.createElement("span");
67
+ synth.setAttribute("data-page-frame", "0");
68
+ synth.setAttribute("data-page-first-block-index", "0");
69
+ synth.setAttribute("data-page-last-block-index", String(Math.max(0, page0Last)));
70
+ setPageMarkers([synth, ...found]);
71
+ } else {
72
+ setPageMarkers(found);
73
+ }
74
+ };
75
+ refresh();
76
+ const view = root.ownerDocument?.defaultView;
77
+ if (!view?.MutationObserver) return undefined;
78
+ const mo = new view.MutationObserver(refresh);
79
+ mo.observe(root, { childList: true, subtree: true });
80
+ return () => mo.disconnect();
81
+ // Re-run when the scroll root changes OR when a new snapshot lands
82
+ // (which may have a different page count and new widgets in the PM DOM).
83
+ // NOTE: snapshot.surface is intentionally excluded — its reference changes on
84
+ // every requestViewportRefresh(), which would add two extra render passes per
85
+ // scroll event. The page-0 fallback uses -1 when page1First is unknown,
86
+ // which is correct (no page-0 blocks → synthetic marker contributes nothing).
87
+ }, [pageStackScrollRoot, snapshot.revisionToken]);
88
+
89
+ const selectionBlockIndex = useMemo(() => {
90
+ const sel = snapshot.selection;
91
+ const blocks = snapshot.surface?.blocks;
92
+ if (!sel || !blocks) return null;
93
+ for (let i = 0; i < blocks.length; i++) {
94
+ const block = blocks[i]!;
95
+ const blockFrom = block.from;
96
+ const blockTo = block.to;
97
+ if (sel.head >= blockFrom && sel.head <= blockTo) return i;
98
+ }
99
+ return null;
100
+ }, [snapshot.selection, snapshot.surface]);
101
+
102
+ const visibleBlockRange = useVisibleBlockRange({
103
+ pageMarkers,
104
+ overscanPages: 2,
105
+ selectionBlockIndex,
106
+ totalBlockCount: snapshot.surface?.blocks.length ?? 0,
107
+ });
108
+
109
+ // L7 Phase 2.8 — viewport cull for `TwPageStackChromeLayer`. Returns
110
+ // `null` while the IntersectionObserver hasn't reported yet; the chrome
111
+ // layer treats null as "render every page" so first paint is
112
+ // unaffected. Once the observer fires, the chrome layer only mounts
113
+ // bands for pages inside `[start, end)` plus overscan, eliminating the
114
+ // measured 412 ms Layout + 229 ms Pre-paint cost on 138-pp extra-large.
115
+ const visiblePageIndexRange = useVisiblePageIndexRange({
116
+ pageMarkers,
117
+ overscanPages: 2,
118
+ });
119
+
120
+ // Push the visible range into the layout facet (which delegates to the
121
+ // runtime's viewport-culling machinery). Depend on `[start, end]` values
122
+ // (not the range object) so identity-preserving updates are a no-op.
123
+ useEffect(() => {
124
+ if (!layoutFacet) return;
125
+ layoutFacet.setVisibleBlockRange(visibleBlockRange);
126
+ layoutFacet.requestViewportRefresh();
127
+ }, [layoutFacet, visibleBlockRange.start, visibleBlockRange.end]);
128
+
129
+ return { pageMarkers, visibleBlockRange, visiblePageIndexRange };
130
+ }
@@ -0,0 +1,60 @@
1
+ import { useEffect, useRef, useState, type RefObject } from "react";
2
+
3
+ export interface PmSurfaceCapture {
4
+ bodySlotRef: RefObject<HTMLDivElement | null>;
5
+ pmSurfaceElement: HTMLElement | null;
6
+ }
7
+
8
+ /**
9
+ * P8.11 — capture the PM surface DOM element. The ProseMirror surface
10
+ * mounts inside `bodySlotRef` on its own schedule (the PM constructor
11
+ * runs inside `TwProseMirrorSurface`). A MutationObserver scoped to the
12
+ * body slot's `childList` picks up the PM root on first commit; once
13
+ * captured, the chrome layer owns reparent state (including portal-slot
14
+ * promotion), so the hook skips further updates unless PM is actually
15
+ * disconnected from the document (e.g. session / document swap tearing
16
+ * PM down).
17
+ */
18
+ export function usePmSurfaceCapture(): PmSurfaceCapture {
19
+ const bodySlotRef = useRef<HTMLDivElement | null>(null);
20
+ const [pmSurfaceElement, setPmSurfaceElement] = useState<HTMLElement | null>(null);
21
+
22
+ useEffect(() => {
23
+ const slot = bodySlotRef.current;
24
+ if (!slot) return undefined;
25
+
26
+ // If we already hold a live reference, the chrome layer may have
27
+ // portaled PM into a per-page band — PM has left `bodySlotRef` but
28
+ // is still connected to the document. Keep the reference until the
29
+ // node is fully disconnected.
30
+ if (pmSurfaceElement && pmSurfaceElement.isConnected) {
31
+ return undefined;
32
+ }
33
+
34
+ const readPm = (): HTMLElement | null =>
35
+ slot.querySelector<HTMLElement>(".ProseMirror");
36
+
37
+ const current = readPm();
38
+ if (current !== pmSurfaceElement) {
39
+ setPmSurfaceElement(current);
40
+ }
41
+ const runtime = slot.ownerDocument?.defaultView as
42
+ | (Window & { MutationObserver?: typeof MutationObserver })
43
+ | null;
44
+ if (!runtime?.MutationObserver) return undefined;
45
+ const observer = new runtime.MutationObserver(() => {
46
+ const next = readPm();
47
+ if (next !== null && next !== pmSurfaceElement) {
48
+ setPmSurfaceElement(next);
49
+ }
50
+ });
51
+ // `childList: true, subtree: false` — only care when children of the
52
+ // body slot change (e.g. PM is added for the first time). Subtree
53
+ // mutations (PM's own edits) fire on every keystroke and are not
54
+ // our concern.
55
+ observer.observe(slot, { childList: true, subtree: false });
56
+ return () => observer.disconnect();
57
+ }, [pmSurfaceElement]);
58
+
59
+ return { bodySlotRef, pmSurfaceElement };
60
+ }
@@ -0,0 +1,63 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+
4
+ import {
5
+ getInitialReviewRailOpen,
6
+ isNarrowChromeViewport,
7
+ } from "../chrome/responsive-chrome";
8
+ import { readViewportWidth } from "./page-shell-metrics.ts";
9
+
10
+ export interface UseReviewRailStateOptions {
11
+ reviewRailAvailable: boolean;
12
+ viewportWidth: number | undefined;
13
+ }
14
+
15
+ export interface ReviewRailState {
16
+ reviewRailOpen: boolean;
17
+ setReviewRailOpen: Dispatch<SetStateAction<boolean>>;
18
+ }
19
+
20
+ /**
21
+ * Review-rail open/close state + the responsive transition effect.
22
+ *
23
+ * When the responsive signature flips (narrow↔wide, or
24
+ * `reviewRailAvailable` changes), the rail resets to its default open
25
+ * state per `getInitialReviewRailOpen`. A ref guards the effect so it
26
+ * only fires on actual transitions, not every viewport resize.
27
+ *
28
+ * Drawer-mode Escape close is NOT handled here — it lives in the
29
+ * parent because `showDrawerReviewRail` flows from the chrome-policy
30
+ * hook which consumes `reviewRailOpen` from this hook (a cycle).
31
+ * Keep those effects in the caller.
32
+ */
33
+ export function useReviewRailState(
34
+ options: UseReviewRailStateOptions,
35
+ ): ReviewRailState {
36
+ const { reviewRailAvailable, viewportWidth } = options;
37
+
38
+ const [reviewRailOpen, setReviewRailOpen] = useState(() =>
39
+ getInitialReviewRailOpen({
40
+ viewportWidth: readViewportWidth(),
41
+ reviewRailAvailable,
42
+ }),
43
+ );
44
+
45
+ const responsiveSignatureRef = useRef<string | null>(null);
46
+
47
+ useEffect(() => {
48
+ const responsiveSignature = `${reviewRailAvailable ? "1" : "0"}:${isNarrowChromeViewport(viewportWidth) ? "n" : "d"}`;
49
+ if (responsiveSignatureRef.current === responsiveSignature) {
50
+ return;
51
+ }
52
+
53
+ responsiveSignatureRef.current = responsiveSignature;
54
+ setReviewRailOpen(
55
+ getInitialReviewRailOpen({
56
+ viewportWidth,
57
+ reviewRailAvailable,
58
+ }),
59
+ );
60
+ }, [reviewRailAvailable, viewportWidth]);
61
+
62
+ return { reviewRailOpen, setReviewRailOpen };
63
+ }