@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,170 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ import type {
4
+ EditorAnchorProjection,
5
+ ScopeIssueAction,
6
+ WorkflowScopeMode,
7
+ } from "../../api/public-types.ts";
8
+ import {
9
+ cycleScopeIndex,
10
+ shouldHandleScopeNavKey,
11
+ } from "../chrome-overlay/scope-keyboard-cycle";
12
+
13
+ export interface UseScopeCardStateOptions {
14
+ layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
15
+ /**
16
+ * Layer-06 workflow facet — canonical source of scope card models.
17
+ * Required by the keyboard-nav loop + Ask-agent handler after Slice 4
18
+ * rail-seam inversion removed those methods from `layoutFacet`.
19
+ */
20
+ workflowFacet?: import("../../runtime/workflow/rail/types.ts").WorkflowFacet;
21
+ onScopeModeChangeRequested?: (payload: {
22
+ scopeId: string;
23
+ mode: WorkflowScopeMode;
24
+ }) => void;
25
+ onScopeIssueActionRequested?: (payload: {
26
+ scopeId: string;
27
+ issueId: string;
28
+ action: ScopeIssueAction;
29
+ }) => void;
30
+ onScopeAcceptSuggestionGroup?: (payload: {
31
+ scopeId: string;
32
+ groupId: string;
33
+ }) => void;
34
+ onScopeRejectSuggestionGroup?: (payload: {
35
+ scopeId: string;
36
+ groupId: string;
37
+ }) => void;
38
+ onScopeAskAgent?: (payload: {
39
+ scopeId: string;
40
+ anchor?: EditorAnchorProjection;
41
+ }) => void;
42
+ }
43
+
44
+ export interface ScopeCardState {
45
+ activeScopeId: string | null;
46
+ handleScopeStripeClick: (segment: { scopeId: string }) => void;
47
+ handleScopeCardClose: () => void;
48
+ handleScopeCardModeChange: (scopeId: string, mode: WorkflowScopeMode) => void;
49
+ handleScopeCardIssueAction: (
50
+ scopeId: string,
51
+ issueId: string,
52
+ action: ScopeIssueAction,
53
+ ) => void;
54
+ handleScopeCardAcceptSuggestionGroup: (scopeId: string, groupId: string) => void;
55
+ handleScopeCardRejectSuggestionGroup: (scopeId: string, groupId: string) => void;
56
+ handleScopeCardAskAgent: (scopeId: string) => void;
57
+ }
58
+
59
+ /**
60
+ * Scope-card open/close state + keyboard navigation + all scope-card
61
+ * callback handlers. Extracted from `tw-review-workspace.tsx`.
62
+ *
63
+ * The card closes on click-outside, Escape, or a repeat click on its
64
+ * stripe. J / K cycle the active scope in document order; Enter opens
65
+ * the first scope when none is active (P3d). Pure helpers
66
+ * `shouldHandleScopeNavKey` + `cycleScopeIndex` back the keyboard
67
+ * logic so it remains unit-testable without a workspace mount.
68
+ */
69
+ export function useScopeCardState(options: UseScopeCardStateOptions): ScopeCardState {
70
+ const {
71
+ layoutFacet,
72
+ workflowFacet,
73
+ onScopeModeChangeRequested,
74
+ onScopeIssueActionRequested,
75
+ onScopeAcceptSuggestionGroup,
76
+ onScopeRejectSuggestionGroup,
77
+ onScopeAskAgent,
78
+ } = options;
79
+ void layoutFacet;
80
+
81
+ const [activeScopeId, setActiveScopeId] = useState<string | null>(null);
82
+
83
+ const handleScopeStripeClick = useCallback(
84
+ (segment: { scopeId: string }) => {
85
+ setActiveScopeId((current) =>
86
+ current === segment.scopeId ? null : segment.scopeId,
87
+ );
88
+ },
89
+ [],
90
+ );
91
+
92
+ const handleScopeCardClose = useCallback(() => {
93
+ setActiveScopeId(null);
94
+ }, []);
95
+
96
+ useEffect(() => {
97
+ if (!workflowFacet) {
98
+ return undefined;
99
+ }
100
+ const onKey = (event: KeyboardEvent) => {
101
+ if (!shouldHandleScopeNavKey(event)) return;
102
+ const models = workflowFacet.getAllScopeCardModels();
103
+ if (models.length === 0) return;
104
+ const ids = models.map((model) => model.scopeId);
105
+ const key = event.key.toLowerCase();
106
+ if (key === "enter") {
107
+ if (!activeScopeId) {
108
+ setActiveScopeId(ids[0] ?? null);
109
+ event.preventDefault();
110
+ }
111
+ return;
112
+ }
113
+ const direction: 1 | -1 = key === "j" ? 1 : -1;
114
+ const next = cycleScopeIndex(activeScopeId, ids, direction);
115
+ setActiveScopeId(next);
116
+ event.preventDefault();
117
+ };
118
+ window.addEventListener("keydown", onKey);
119
+ return () => window.removeEventListener("keydown", onKey);
120
+ }, [workflowFacet, activeScopeId]);
121
+
122
+ const handleScopeCardModeChange = useCallback(
123
+ (scopeId: string, mode: WorkflowScopeMode) => {
124
+ onScopeModeChangeRequested?.({ scopeId, mode });
125
+ },
126
+ [onScopeModeChangeRequested],
127
+ );
128
+
129
+ const handleScopeCardIssueAction = useCallback(
130
+ (scopeId: string, issueId: string, action: ScopeIssueAction) => {
131
+ onScopeIssueActionRequested?.({ scopeId, issueId, action });
132
+ },
133
+ [onScopeIssueActionRequested],
134
+ );
135
+
136
+ const handleScopeCardAcceptSuggestionGroup = useCallback(
137
+ (scopeId: string, groupId: string) => {
138
+ onScopeAcceptSuggestionGroup?.({ scopeId, groupId });
139
+ },
140
+ [onScopeAcceptSuggestionGroup],
141
+ );
142
+
143
+ const handleScopeCardRejectSuggestionGroup = useCallback(
144
+ (scopeId: string, groupId: string) => {
145
+ onScopeRejectSuggestionGroup?.({ scopeId, groupId });
146
+ },
147
+ [onScopeRejectSuggestionGroup],
148
+ );
149
+
150
+ const handleScopeCardAskAgent = useCallback(
151
+ (scopeId: string) => {
152
+ const cardModel = workflowFacet
153
+ ?.getAllScopeCardModels()
154
+ .find((m) => m.scopeId === scopeId);
155
+ onScopeAskAgent?.({ scopeId, anchor: cardModel?.anchor });
156
+ },
157
+ [onScopeAskAgent, workflowFacet],
158
+ );
159
+
160
+ return {
161
+ activeScopeId,
162
+ handleScopeStripeClick,
163
+ handleScopeCardClose,
164
+ handleScopeCardModeChange,
165
+ handleScopeCardIssueAction,
166
+ handleScopeCardAcceptSuggestionGroup,
167
+ handleScopeCardRejectSuggestionGroup,
168
+ handleScopeCardAskAgent,
169
+ };
170
+ }
@@ -0,0 +1,28 @@
1
+ import { useEffect, useRef, useState, type RefObject } from "react";
2
+
3
+ export interface ScrollRootCapture {
4
+ scrollRootRef: RefObject<HTMLDivElement | null>;
5
+ pageStackScrollRoot: HTMLElement | null;
6
+ }
7
+
8
+ /**
9
+ * P8.11 — capture the scroll-root DOM element on mount so the chrome
10
+ * overlay's `TwPageStackChromeLayer` can measure per-page rects and
11
+ * observe DOM mutations. `scrollRootRef` is attached to the existing
12
+ * `[data-wre-scroll-root]` container; rely on a mount effect rather
13
+ * than a ref callback so render-time state stays cheap. The
14
+ * comparison guard in the effect keeps `setPageStackScrollRoot` from
15
+ * firing every commit.
16
+ */
17
+ export function useScrollRootCapture(): ScrollRootCapture {
18
+ const scrollRootRef = useRef<HTMLDivElement | null>(null);
19
+ const [pageStackScrollRoot, setPageStackScrollRoot] = useState<HTMLElement | null>(null);
20
+
21
+ useEffect(() => {
22
+ if (scrollRootRef.current !== pageStackScrollRoot) {
23
+ setPageStackScrollRoot(scrollRootRef.current);
24
+ }
25
+ });
26
+
27
+ return { scrollRootRef, pageStackScrollRoot };
28
+ }
@@ -0,0 +1,113 @@
1
+ import { useMemo, type RefObject } from "react";
2
+
3
+ import type { EditorViewStateSnapshot } from "../../api/public-types";
4
+ import type {
5
+ ActiveSelectionToolModel,
6
+ SelectionToolAnchor,
7
+ } from "../../ui/headless/selection-tool-types";
8
+ import { resolveSelectionToolPlacement } from "../chrome/tw-selection-tool-placement";
9
+ import { useUiApi } from "../ui-api-context";
10
+
11
+ import { resolveSelectionToolbarPlacement } from "./selection-toolbar-placement.ts";
12
+
13
+ export interface UseSelectionToolbarPlacementOptions {
14
+ gatedSelectionTool: ActiveSelectionToolModel | null;
15
+ selection: EditorViewStateSnapshot["selection"];
16
+ selectionToolAnchor: SelectionToolAnchor | null | undefined;
17
+ zoomScale: number;
18
+ renderFrameRevision: number;
19
+ selectionToolbarRootRef: RefObject<HTMLDivElement | null>;
20
+ }
21
+
22
+ /**
23
+ * Compute placement for the floating selection toolbar.
24
+ *
25
+ * Resolution order (flicker-remediation 2026-04-22):
26
+ *
27
+ * 1. Legacy `resolveSelectionToolbarPlacement` driven by the
28
+ * host-supplied `selectionToolAnchor` prop + the toolbar root's
29
+ * `getBoundingClientRect()`. This path correctly handles
30
+ * viewport-to-container translation + zoom scaling. It was the
31
+ * original pre-DS-C1 path, is demonstrably correct, and is
32
+ * populated every render by the shell — so it does not race with
33
+ * bridge publication order.
34
+ *
35
+ * 2. `ui.overlays.getAnchor({ kind: "selection" })` fallback when
36
+ * the host hasn't populated `selectionToolAnchor`. The UI API
37
+ * returns a frame-local rect; projecting it into container-local
38
+ * coordinates is a documented TODO (the bridge rect's
39
+ * `space: "frame"` is tracked but not yet translated in this
40
+ * placement path). Until that projection lands, this branch is
41
+ * best-effort for consumers that don't supply the legacy prop —
42
+ * e.g. future headless / Playwright drivers.
43
+ *
44
+ * DS-C1 (designsystem.md §8.8.1 "Selection toolbar" row) still routes
45
+ * anchor READS through the UI API in principle; the shell bridge
46
+ * (`useShellSelectionAnchorBridge`) continues publishing tool-aware
47
+ * rects so `ui.overlays.getAnchor({ kind: "selection" })` returns a
48
+ * meaningful rect for the debug service, Playwright, and the UI-API
49
+ * consumer tests. The placement hook's priority change is strictly a
50
+ * flicker-remediation concession: same source of truth, correct
51
+ * coordinate space.
52
+ *
53
+ * The memo re-runs when the geometry facet bumps `renderFrameRevision`
54
+ * so placement tracks new rects without a separate subscription here.
55
+ */
56
+ export function useSelectionToolbarPlacement(
57
+ options: UseSelectionToolbarPlacementOptions,
58
+ ): ReturnType<typeof resolveSelectionToolbarPlacement> {
59
+ const {
60
+ gatedSelectionTool,
61
+ selection,
62
+ selectionToolAnchor,
63
+ zoomScale,
64
+ renderFrameRevision,
65
+ selectionToolbarRootRef,
66
+ } = options;
67
+ const ui = useUiApi();
68
+
69
+ return useMemo(() => {
70
+ // Primary: legacy DOM-anchor path. Correct coord space; no bridge
71
+ // timing dependency.
72
+ const legacy = resolveSelectionToolbarPlacement(
73
+ selectionToolAnchor,
74
+ selectionToolbarRootRef.current,
75
+ zoomScale,
76
+ );
77
+ if (legacy) return legacy;
78
+
79
+ // Fallback: UI API anchor for consumers that don't populate
80
+ // `selectionToolAnchor`. Coord-space projection TODO — the bridge
81
+ // rect is frame-local, `resolveSelectionToolPlacement` assumes
82
+ // container-local. Best-effort until projection lands.
83
+ if (ui && gatedSelectionTool) {
84
+ const anchorRect = ui.overlays.getAnchor({ kind: "selection" });
85
+ if (anchorRect && selectionToolbarRootRef.current) {
86
+ const containerRect =
87
+ selectionToolbarRootRef.current.getBoundingClientRect();
88
+ const result = resolveSelectionToolPlacement({
89
+ anchor: {
90
+ leftPx: anchorRect.leftPx,
91
+ topPx: anchorRect.topPx,
92
+ widthPx: anchorRect.widthPx,
93
+ heightPx: anchorRect.heightPx,
94
+ },
95
+ container: {
96
+ widthPx: containerRect.width,
97
+ heightPx: containerRect.height,
98
+ },
99
+ });
100
+ if (result) return result;
101
+ }
102
+ }
103
+ return null;
104
+ // eslint-disable-next-line react-hooks/exhaustive-deps
105
+ }, [
106
+ ui,
107
+ selectionToolAnchor,
108
+ gatedSelectionTool,
109
+ selection,
110
+ zoomScale,
111
+ renderFrameRevision,
112
+ ]);
113
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Shell-side writer for the overlay-anchor bridge (DS-C1).
3
+ *
4
+ * Publishes a `getSelectionAnchor` closure on every render so that
5
+ * `ui.overlays.getAnchor({ kind: "selection" })` returns the current
6
+ * selection rect without re-creating the UI API or the controller.
7
+ *
8
+ * Pairs with `src/shell/overlay-anchor-bridge.ts` +
9
+ * `src/ui/ui-controller-factory.ts` + `OverlayAnchorBridgeProvider`.
10
+ * When no bridge provider is mounted (headless / tests), this hook is
11
+ * a no-op.
12
+ *
13
+ * The resolver is written during render (not from an effect) so
14
+ * readers that call `ui.overlays.getAnchor({ kind: "selection" })`
15
+ * inside the same render pass — specifically
16
+ * `useSelectionToolbarPlacement` — see the current-render inputs, not
17
+ * the previous render's. The bridge itself is a mutable ref-box; the
18
+ * write is idempotent and free of React-scheduling hazards.
19
+ *
20
+ * A `useEffect` cleanup runs on unmount to clear the bridge so the
21
+ * controller returns `null` (U4) after the tree tears down. The
22
+ * cleanup only fires on unmount (empty deps), never on re-render,
23
+ * because we rewrite the resolver on every render and React would
24
+ * otherwise clobber a live resolver mid-render.
25
+ */
26
+
27
+ import { useEffect, useRef } from "react";
28
+
29
+ import type { GeometryRect } from "../../api/v3/ui/_types.ts";
30
+ import type {
31
+ EditorViewStateSnapshot,
32
+ GeometryFacet,
33
+ } from "../../api/public-types";
34
+ import type { ActiveSelectionToolModel } from "../../ui/headless/selection-tool-types";
35
+ import type { ShellOverlayAnchorBridge } from "../../shell/overlay-anchor-bridge.ts";
36
+ import { resolveSelectionAnchor } from "../chrome/tw-selection-anchor-resolver";
37
+ import { useOverlayAnchorBridge } from "../overlay-anchor-bridge-context.tsx";
38
+
39
+ export interface UseShellSelectionAnchorBridgeOptions {
40
+ /** Runtime geometry facet — kernel source for render-frame rects. */
41
+ geometryFacet?: GeometryFacet;
42
+ /** Current selection snapshot from the runtime view-state. */
43
+ selection: EditorViewStateSnapshot["selection"];
44
+ /** Current active (gated) selection tool. */
45
+ gatedSelectionTool: ActiveSelectionToolModel | null;
46
+ /**
47
+ * Bumps when the geometry facet emits a new render frame; the
48
+ * publication runs during render anyway, so this field is carried
49
+ * for API symmetry with the consumer hook (placement) whose memo
50
+ * tracks it.
51
+ */
52
+ renderFrameRevision: number;
53
+ }
54
+
55
+ /**
56
+ * Publishes `bridge.getSelectionAnchor` synchronously during render.
57
+ * Consumers read through `ui.overlays.getAnchor({ kind: "selection" })`
58
+ * in the same render pass and see the current-render inputs.
59
+ */
60
+ export function useShellSelectionAnchorBridge(
61
+ options: UseShellSelectionAnchorBridgeOptions,
62
+ ): void {
63
+ const bridge = useOverlayAnchorBridge();
64
+ const { geometryFacet, selection, gatedSelectionTool } = options;
65
+
66
+ // Synchronous write during render. Mutating a ref-box here is safe:
67
+ // the bridge is not React state, the write is idempotent, and the
68
+ // bridge identity is stable across renders (the controller closed
69
+ // over the same object at API construction).
70
+ if (bridge) {
71
+ bridge.getSelectionAnchor = () => {
72
+ if (!geometryFacet) return null;
73
+ const anchor = resolveSelectionAnchor({
74
+ geometryFacet,
75
+ selection,
76
+ tool: gatedSelectionTool,
77
+ });
78
+ if (!anchor) return null;
79
+ // `resolveSelectionAnchor` returns kernel-frame-local px. Tag as
80
+ // `frame` space so overlay consumers project through the same
81
+ // transform L05 uses for id-keyed anchors.
82
+ return {
83
+ leftPx: anchor.leftPx,
84
+ topPx: anchor.topPx,
85
+ widthPx: anchor.widthPx,
86
+ heightPx: anchor.heightPx,
87
+ space: "frame",
88
+ } satisfies GeometryRect;
89
+ };
90
+ }
91
+
92
+ // Clear on true unmount only. We track the bridge identity through
93
+ // a ref so the cleanup closure refers to the bridge that was live
94
+ // at cleanup time — not the bridge captured on the first render.
95
+ // This also guards the React StrictMode double-mount path: the
96
+ // post-mount cleanup previously nulled the resolver the render
97
+ // body had just written (StrictMode fires cleanup after the first
98
+ // mount, then re-mounts; without the ref guard the second mount
99
+ // would not re-run the render body and the resolver would stay
100
+ // null until the next render). The render-body assignment above
101
+ // runs every commit, so the ref write here never shadows a newer
102
+ // resolver unless `bridge` itself has changed identity.
103
+ const liveBridgeRef = useRef(bridge);
104
+ liveBridgeRef.current = bridge;
105
+ useEffect(() => {
106
+ return () => {
107
+ const current = liveBridgeRef.current;
108
+ if (current) {
109
+ current.getSelectionAnchor = null;
110
+ }
111
+ };
112
+ // eslint-disable-next-line react-hooks/exhaustive-deps
113
+ }, []);
114
+ }
115
+
116
+ /**
117
+ * Re-exported for consumers that want to peek at the bridge directly
118
+ * (tests, evidence runners). Not used by the placement hook.
119
+ */
120
+ export type { ShellOverlayAnchorBridge };
@@ -0,0 +1,55 @@
1
+ import { useMemo } from "react";
2
+
3
+ import type {
4
+ PublicMeasurementFidelity,
5
+ RuntimeRenderSnapshot,
6
+ } from "../../api/public-types.ts";
7
+
8
+ export interface StatusBarPageFacts {
9
+ displayPageNumber: number | null;
10
+ pageCount: number | null;
11
+ measurementFidelity: PublicMeasurementFidelity | undefined;
12
+ }
13
+
14
+ export interface UseStatusBarPageFactsOptions {
15
+ layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
16
+ selectionPosition: number;
17
+ activeStory: RuntimeRenderSnapshot["activeStory"];
18
+ renderFrameRevision: number;
19
+ }
20
+
21
+ /**
22
+ * P5b — status-bar facts derived from the layout facet so the
23
+ * Page-N-of-M display + measurement-fidelity badge ("E" / "C" / "C+F")
24
+ * refresh on every layout-affecting facet event. `renderFrameRevision`
25
+ * (bumped by `useLayoutFacetRenderSignal`) drives the memo; no separate
26
+ * subscription here.
27
+ */
28
+ export function useStatusBarPageFacts(
29
+ options: UseStatusBarPageFactsOptions,
30
+ ): StatusBarPageFacts {
31
+ const { layoutFacet, selectionPosition, activeStory, renderFrameRevision } = options;
32
+
33
+ return useMemo(() => {
34
+ const facet = layoutFacet;
35
+ if (!facet) {
36
+ return {
37
+ displayPageNumber: null,
38
+ pageCount: null,
39
+ measurementFidelity: undefined,
40
+ };
41
+ }
42
+ const pageRef = facet.getPageForOffset(selectionPosition, activeStory);
43
+ const displayPageNumber =
44
+ pageRef !== null && typeof pageRef.pageIndex === "number"
45
+ ? facet.getDisplayPageNumber(pageRef.pageIndex) ?? pageRef.pageIndex + 1
46
+ : null;
47
+ const pageCount = facet.getPageCount();
48
+ return {
49
+ displayPageNumber,
50
+ pageCount,
51
+ measurementFidelity: facet.getMeasurementFidelity(),
52
+ };
53
+ // eslint-disable-next-line react-hooks/exhaustive-deps
54
+ }, [layoutFacet, selectionPosition, activeStory, renderFrameRevision]);
55
+ }
@@ -0,0 +1,43 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import { readViewportHeight, readViewportWidth } from "./page-shell-metrics.ts";
4
+
5
+ export interface ViewportDimensions {
6
+ viewportWidth: number | undefined;
7
+ viewportHeight: number | undefined;
8
+ }
9
+
10
+ /**
11
+ * Live window-dimension hook with SSR-safe initial read + `resize` listener.
12
+ * Extracted from `tw-review-workspace.tsx` so the viewport state is
13
+ * addressable on its own. The hook does not read DOM during a mutation;
14
+ * `readViewportWidth` / `readViewportHeight` return `undefined` under
15
+ * SSR, matching the prior inline behavior.
16
+ */
17
+ export function useViewportDimensions(): ViewportDimensions {
18
+ const [viewportWidth, setViewportWidth] = useState<number | undefined>(
19
+ () => readViewportWidth(),
20
+ );
21
+ const [viewportHeight, setViewportHeight] = useState<number | undefined>(
22
+ () => readViewportHeight(),
23
+ );
24
+
25
+ useEffect(() => {
26
+ if (typeof window === "undefined") {
27
+ return;
28
+ }
29
+
30
+ const updateViewport = () => {
31
+ setViewportWidth(readViewportWidth());
32
+ setViewportHeight(readViewportHeight());
33
+ };
34
+
35
+ updateViewport();
36
+ window.addEventListener("resize", updateViewport);
37
+ return () => {
38
+ window.removeEventListener("resize", updateViewport);
39
+ };
40
+ }, []);
41
+
42
+ return { viewportWidth, viewportHeight };
43
+ }
@@ -0,0 +1,25 @@
1
+ import { useRef } from "react";
2
+
3
+ import {
4
+ createLocalSurfaceArbiter,
5
+ type LocalSurfaceArbiter,
6
+ } from "../chrome/local-surface-arbiter";
7
+
8
+ /**
9
+ * Phase F.2 — per-workspace local-surface arbiter. Every floating
10
+ * surface inside the workspace (selection toolbar, scope card,
11
+ * suggestion card, context menu, comment preview) requests through this
12
+ * shared instance.
13
+ *
14
+ * Stable identity via `useRef` — the arbiter is an event-emitter with
15
+ * its own internal state, so recreating it per render would drop active
16
+ * requests. Consumers read it through the `LocalSurfaceArbiterContext`
17
+ * provider mounted in the workspace return tree.
18
+ */
19
+ export function useWorkspaceArbiter(): LocalSurfaceArbiter {
20
+ const ref = useRef<LocalSurfaceArbiter | null>(null);
21
+ if (ref.current === null) {
22
+ ref.current = createLocalSurfaceArbiter();
23
+ }
24
+ return ref.current;
25
+ }
@@ -0,0 +1,86 @@
1
+ import { useMemo } from "react";
2
+
3
+ import {
4
+ resolveChromeComposition,
5
+ type ChromeCompositionInput,
6
+ } from "../../api/v3/ui";
7
+ import { useUiApi } from "../ui-api-context";
8
+
9
+ export type UseWorkspaceCompositionOptions = Pick<
10
+ ChromeCompositionInput,
11
+ | "chromePreset"
12
+ | "chromeOptions"
13
+ | "reviewMode"
14
+ | "role"
15
+ | "markupDisplay"
16
+ | "diagnosticsSignal"
17
+ >;
18
+
19
+ /**
20
+ * Memoized hook that returns the chrome composition for the workspace.
21
+ *
22
+ * When a `UiApiProvider` wraps the tree, this hook calls
23
+ * `ui.chrome.getComposition(input)` — the architectural path per
24
+ * architecture 10 §U5.b and designsystem §8.3 (the UI API owns
25
+ * composition decisions). When no provider is present, the hook falls
26
+ * back to `resolveChromeComposition` (the same function the UI API
27
+ * wraps) — so headless paths, the React harness, and tests behave
28
+ * identically without needing to mount a provider.
29
+ *
30
+ * The underlying function lives in layer 10 (`src/api/v3/ui/chrome-
31
+ * composition.ts`); the L11 re-export shim at
32
+ * `src/ui-tailwind/chrome/chrome-composition-model.ts` exists for other
33
+ * consumers mid-migration. This hook is the first mounted consumer to
34
+ * source from L10 directly (refactor/10 Slice 10).
35
+ *
36
+ * Memoization (perf invariant #4): `TwReviewWorkspace` re-renders on
37
+ * every PM transaction (`view.updateState()` wholesale replace in the
38
+ * inverted-truth architecture). `resolveChromeComposition` allocates
39
+ * new Sets + arrays on each call; `useMemo` keeps downstream
40
+ * `React.memo` consumers stable across commits.
41
+ *
42
+ * **Caller contract**: every option value passed into this hook MUST be
43
+ * referentially stable across renders that don't intend to trigger a
44
+ * re-composition — the memo's dep list compares by identity. In
45
+ * particular, `diagnosticsSignal` (built each render by
46
+ * `useDiagnosticsSignal`) and `chromeOptions` (host prop) must already
47
+ * be memoized by their source; passing a freshly-allocated object here
48
+ * thrashes the memo and cascades through every `React.memo` consumer
49
+ * downstream. Review M6 (2026-04-22) flagged this as a silent perf
50
+ * regression risk.
51
+ *
52
+ * `readOnly` is intentionally omitted from the option set: the
53
+ * composition seam is independent of read-only posture today; Phase E
54
+ * threads it through the interactionGuardSnapshot wiring once the rail
55
+ * owns the full posture composition.
56
+ */
57
+ export function useWorkspaceComposition(
58
+ options: UseWorkspaceCompositionOptions,
59
+ ): ReturnType<typeof resolveChromeComposition> {
60
+ const ui = useUiApi();
61
+ const {
62
+ chromePreset,
63
+ chromeOptions,
64
+ reviewMode,
65
+ role,
66
+ markupDisplay,
67
+ diagnosticsSignal,
68
+ } = options;
69
+
70
+ return useMemo(
71
+ () => {
72
+ const input: ChromeCompositionInput = {
73
+ chromePreset,
74
+ chromeOptions,
75
+ reviewMode,
76
+ role,
77
+ markupDisplay,
78
+ diagnosticsSignal,
79
+ };
80
+ return ui
81
+ ? ui.chrome.getComposition(input)
82
+ : resolveChromeComposition(input);
83
+ },
84
+ [ui, chromePreset, chromeOptions, reviewMode, role, markupDisplay, diagnosticsSignal],
85
+ );
86
+ }