@beyondwork/docx-react-component 1.0.67 → 1.0.70

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 -932
  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 -4797
  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
+ /**
2
+ * @endStateApi v3 — `ui.overlays.getVisibility / setLocalPreference /
3
+ * resetLocalPreference / subscribeVisibility` (U9, state-classes X3).
4
+ *
5
+ * The single composition site for overlay visibility. Merges:
6
+ * - class-A policy → `handle.getVisibilityPolicy(kind)` (L06 X1).
7
+ * - class-C preference → per-UI-API-instance Map in the factory closure.
8
+ *
9
+ * No renderer, chrome surface, or presentation component duplicates
10
+ * this logic — `ui.overlays.getVisibility` is the only path per
11
+ * architecture 10 §U9.
12
+ *
13
+ * Storage shape (closure-local, per `createUiApi(handle)` instance):
14
+ * - `localPrefs: Map<OverlayKind, boolean>` — user's explicit pref.
15
+ * - `subscribers: Map<OverlayKind, Set<UiListener<OverlayVisibility>>>` —
16
+ * listeners fired on pref changes.
17
+ *
18
+ * Composition pseudocode (architecture 10 §U9 + `00-overview.md §9.1`):
19
+ *
20
+ * policy = handle.getVisibilityPolicy(kind) // may be null
21
+ * pref = localPrefs.get(kind) // may be undefined
22
+ *
23
+ * # debug-panel special case: debugMode gate wins over everything.
24
+ * if kind === "debug-panel" and debugMode === "off":
25
+ * → { state: "forced-off", reason: "policy-enforcement" }
26
+ *
27
+ * # Policy enforcement is a hard override.
28
+ * if policy.enforcement === "always":
29
+ * → { state: "forced-on", reason: "policy-enforcement" }
30
+ * if policy.enforcement === "never":
31
+ * → { state: "forced-off", reason: "policy-enforcement" }
32
+ *
33
+ * # "authored-default" cedes to local preference.
34
+ * if pref === true: → { state: "visible", reason: "user-preference" }
35
+ * if pref === false: → { state: "hidden", reason: "user-preference" }
36
+ *
37
+ * # No preference → use policy default (or intrinsic default if no policy).
38
+ * defaultOn = policy.defaultOn ?? INTRINSIC_DEFAULTS[kind]
39
+ * → defaultOn ? visible : hidden, reason: "policy-default"
40
+ *
41
+ * Subscribe fan-out is local-preference-only in this slice. Policy
42
+ * changes and debugMode changes do not wake listeners yet — consumers
43
+ * that depend on either re-read visibility on their own cadence.
44
+ * Follow-up slice will subscribe to the L06 policy telemetry bus and
45
+ * fan out on `workflow.visibility_policy_changed`.
46
+ */
47
+
48
+ import type { RuntimeApiHandle } from "../_runtime-handle.ts";
49
+ import type { OverlayKind, OverlayVisibilityPolicy } from "../../public-types.ts";
50
+ import type {
51
+ OverlayVisibility,
52
+ UiListener,
53
+ UiUnsubscribe,
54
+ } from "./_types.ts";
55
+ import type { UiApiContext } from "./_context.ts";
56
+
57
+ /**
58
+ * Intrinsic defaults — used when no class-A policy has been authored for
59
+ * a kind. Mirrors the docs/wiki convention: scope/comment/tracked-changes
60
+ * on; suggestions + debug-panel off; presence on (collab default).
61
+ */
62
+ const INTRINSIC_DEFAULT_ON: Readonly<Record<OverlayKind, boolean>> = {
63
+ "scope-rail": true,
64
+ "comments": true,
65
+ "tracked-changes": true,
66
+ "suggestions": false,
67
+ "debug-panel": false,
68
+ "presence": true,
69
+ };
70
+
71
+ /**
72
+ * Per-instance visibility state. `createUiApi(handle)` constructs one of
73
+ * these; it closes over the overlays family.
74
+ */
75
+ export interface OverlaysVisibilityState {
76
+ readonly localPrefs: Map<OverlayKind, boolean>;
77
+ readonly subscribers: Map<OverlayKind, Set<UiListener<OverlayVisibility>>>;
78
+ }
79
+
80
+ export function createOverlaysVisibilityState(): OverlaysVisibilityState {
81
+ return {
82
+ localPrefs: new Map(),
83
+ subscribers: new Map(),
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Read the current effective debugMode from the bound controller's host
89
+ * posture slice. Mirrors the default handling in `chrome.ts::getPosture`
90
+ * — unbound / no hook / no debugMode field all resolve to `"off"` per
91
+ * U6 + CLAUDE.md Protected Invariants.
92
+ */
93
+ function readDebugMode(ctx: UiApiContext): "off" | "top-bar" | "full" {
94
+ return ctx.binding?.controller.getHostPosture?.()?.debugMode ?? "off";
95
+ }
96
+
97
+ function readPolicy(
98
+ handle: RuntimeApiHandle,
99
+ kind: OverlayKind,
100
+ ): OverlayVisibilityPolicy | null {
101
+ // `getVisibilityPolicy` is on `RuntimeApiHandle` (Layer 06 X1 +
102
+ // L07 roster update). Read directly — no cast. The function itself
103
+ // returns null on unknown/unset policy, so the handle covers the
104
+ // pre-paint case too.
105
+ return handle.getVisibilityPolicy(kind) ?? null;
106
+ }
107
+
108
+ /**
109
+ * Compose the OverlayVisibility record for a single kind. Pure except
110
+ * for the handle / ctx reads — no side effects, no allocation beyond
111
+ * the result object.
112
+ */
113
+ export function composeOverlayVisibility(
114
+ ctx: UiApiContext,
115
+ state: OverlaysVisibilityState,
116
+ kind: OverlayKind,
117
+ ): OverlayVisibility {
118
+ // debug-panel: when debugMode is "off" the panel is forced off
119
+ // regardless of policy or preference (CLAUDE.md Protected Invariant).
120
+ if (kind === "debug-panel" && readDebugMode(ctx) === "off") {
121
+ return { state: "forced-off", reason: "policy-enforcement" };
122
+ }
123
+
124
+ const policy = readPolicy(ctx.handle, kind);
125
+ const pref = state.localPrefs.get(kind);
126
+
127
+ // Hard policy overrides.
128
+ if (policy?.enforcement === "always") {
129
+ return { state: "forced-on", reason: "policy-enforcement" };
130
+ }
131
+ if (policy?.enforcement === "never") {
132
+ return { state: "forced-off", reason: "policy-enforcement" };
133
+ }
134
+
135
+ // authored-default / no policy: local preference wins if expressed.
136
+ if (pref === true) {
137
+ return { state: "visible", reason: "user-preference" };
138
+ }
139
+ if (pref === false) {
140
+ return { state: "hidden", reason: "user-preference" };
141
+ }
142
+
143
+ // No preference — fall back to policy default, else intrinsic default.
144
+ const defaultOn = policy?.defaultOn ?? INTRINSIC_DEFAULT_ON[kind];
145
+ return defaultOn
146
+ ? { state: "visible", reason: "policy-default" }
147
+ : { state: "hidden", reason: "policy-default" };
148
+ }
149
+
150
+ /**
151
+ * Notify all subscribers for a kind with the current composed visibility.
152
+ * Called after `setLocalPreference` / `resetLocalPreference`.
153
+ */
154
+ export function notifyVisibilitySubscribers(
155
+ ctx: UiApiContext,
156
+ state: OverlaysVisibilityState,
157
+ kind: OverlayKind,
158
+ ): void {
159
+ const listeners = state.subscribers.get(kind);
160
+ if (!listeners || listeners.size === 0) return;
161
+ const visibility = composeOverlayVisibility(ctx, state, kind);
162
+ for (const listener of listeners) {
163
+ try {
164
+ listener(visibility);
165
+ } catch {
166
+ // Isolate listener errors — one broken subscriber must not block
167
+ // the rest of the notification chain.
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,427 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.overlays` family (layer 10).
3
+ *
4
+ * Resolution order per OverlayAnchorQuery.kind:
5
+ *
6
+ * - Id-keyed kinds (block / scope / comment / revision / page):
7
+ * `handle.geometry.getAnchor(AnchorQuery)` directly; controller
8
+ * hook is the fallback when geometry returns null. Slice 11
9
+ * promoted these to `live` against geometry.
10
+ *
11
+ * - `kind: "selection"`: controller hook first (the shell-side
12
+ * `ShellOverlayAnchorBridge` populated by
13
+ * `useShellSelectionAnchorBridge` — carries tool-aware anchor
14
+ * semantics via `resolveSelectionAnchor`). `handle.geometry.
15
+ * getSelectionRects` is the headless / pre-mount fallback that
16
+ * returns the raw range rect without tool-context awareness.
17
+ * Flicker-remediation 2026-04-22 flipped this order (previously
18
+ * geometry-first), because raw-rects pre-empted the bridge's
19
+ * tool-aware anchor for structural tools (image, table-cell).
20
+ *
21
+ * - subscribe: live-with-adapter. Delegates to the bound
22
+ * controller's `subscribeOverlays` hook. Geometry invalidation
23
+ * streaming into the UI API direct from `handle.geometry` is a
24
+ * follow-up slice (needs a rAF-coalesced fan-out that doesn't
25
+ * exist on the geometry facet today).
26
+ *
27
+ * Return shape when geometry/handle is unavailable:
28
+ * - getAnchor returns null (never throws). Per U4, "if geometry is
29
+ * unavailable, return null — do not silently measure DOM."
30
+ * - getRects returns [].
31
+ * - subscribe throws — subscription is a stateful wiring decision; a
32
+ * silent no-op would hide broken event routing.
33
+ *
34
+ * Contract U4 — overlays derive anchors from geometry, not DOM. The
35
+ * shell bridge itself is geometry-backed (the resolver reads
36
+ * `geometryFacet.getRenderFrame().anchorIndex`); the UI API never
37
+ * measures DOM.
38
+ */
39
+
40
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
41
+ import type {
42
+ GeometryRect,
43
+ OverlayAnchorQuery,
44
+ OverlayVisibility,
45
+ UiListener,
46
+ UiUnsubscribe,
47
+ } from "./_types.ts";
48
+ import type { OverlayKind, SelectionSnapshot } from "../../public-types.ts";
49
+ import type { AnchorQuery } from "../../../runtime/geometry/geometry-types.ts";
50
+ import type { UiApiContext } from "./_context.ts";
51
+ import {
52
+ composeOverlayVisibility,
53
+ createOverlaysVisibilityState,
54
+ notifyVisibilitySubscribers,
55
+ } from "./overlays-visibility.ts";
56
+ import { emitUxResponse } from "../_ux-response.ts";
57
+
58
+ /**
59
+ * Translate the UI API's `OverlayAnchorQuery` (public, presentation-shaped) to
60
+ * the geometry facet's id-keyed `AnchorQuery`. Returns null for
61
+ * `kind: "selection"` — selection doesn't live in the id-keyed anchor
62
+ * index; it's resolved through a separate code path via
63
+ * `handle.geometry.getSelectionRects` (see `resolveSelectionRects` in
64
+ * this file).
65
+ */
66
+ function toAnchorQuery(query: OverlayAnchorQuery): AnchorQuery | null {
67
+ switch (query.kind) {
68
+ case "block":
69
+ return { kind: "block-id", value: query.value };
70
+ case "scope":
71
+ return { kind: "scope-id", value: query.value };
72
+ case "comment":
73
+ return { kind: "comment-id", value: query.value };
74
+ case "revision":
75
+ return { kind: "revision-id", value: query.value };
76
+ case "page":
77
+ return { kind: "page-index", value: query.value };
78
+ case "selection":
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Resolve `OverlayAnchorQuery.kind === "selection"` via
85
+ * `handle.geometry.getSelectionRects({from, to, story})`. The current
86
+ * selection range is read from `handle.getRenderSnapshot().selection`
87
+ * (collapsed selections return []; callers that want caret geometry
88
+ * use `handle.geometry.getCaret(offset)` directly).
89
+ *
90
+ * Returns `[]` when geometry / render snapshot are unavailable (pre-paint
91
+ * / headless handles that don't expose the method). Never reaches DOM
92
+ * per U4.
93
+ */
94
+ function resolveSelectionRects(ctx: UiApiContext): readonly GeometryRect[] {
95
+ const render = ctx.handle.getRenderSnapshot?.() as
96
+ | { selection?: SelectionSnapshot }
97
+ | null
98
+ | undefined;
99
+ const selection = render?.selection;
100
+ if (!selection || selection.isCollapsed) return [];
101
+ const getSelectionRects = ctx.handle.geometry?.getSelectionRects;
102
+ if (typeof getSelectionRects !== "function") return [];
103
+ const from = Math.min(selection.anchor, selection.head);
104
+ const to = Math.max(selection.anchor, selection.head);
105
+ return getSelectionRects({
106
+ from,
107
+ to,
108
+ story: selection.storyTarget,
109
+ });
110
+ }
111
+
112
+ export const getAnchorMetadata: ApiV3FnMetadata = {
113
+ name: "ui.overlays.getAnchor",
114
+ status: "live",
115
+ sourceLayer: "presentation",
116
+ liveEvidence: {
117
+ runnerTest: "test/api/v3/ui/overlays-anchor.test.ts",
118
+ commit: "refactor-10-slice-11",
119
+ },
120
+ uxIntent: { uiVisible: false },
121
+ agentMetadata: { readOrMutate: "read", boundedScope: "document", auditCategory: "ui-overlays-read" },
122
+ stateClass: "C-local",
123
+ persistsTo: "none",
124
+ rwdReference: "§UI API § ui.overlays.getAnchor. For id-keyed kinds (block/scope/comment/revision/page): reads handle.geometry.getAnchor(query) directly; controller hook is the fallback when geometry returns null. For kind:'selection': controller hook (shell bridge — tool-aware) is the primary path; handle.geometry.getSelectionRects is the fallback for headless / pre-mount.",
125
+ };
126
+
127
+ export const getRectsMetadata: ApiV3FnMetadata = {
128
+ name: "ui.overlays.getRects",
129
+ status: "live",
130
+ sourceLayer: "presentation",
131
+ liveEvidence: {
132
+ runnerTest: "test/api/v3/ui/overlays-anchor.test.ts",
133
+ commit: "refactor-10-slice-11",
134
+ },
135
+ uxIntent: { uiVisible: false },
136
+ agentMetadata: { readOrMutate: "read", boundedScope: "scope", auditCategory: "ui-overlays-read" },
137
+ stateClass: "C-local",
138
+ persistsTo: "none",
139
+ rwdReference: "§UI API § ui.overlays.getRects. For id-keyed kinds: reads handle.geometry.getAnchorRects(query) directly; controller hook is the fallback when geometry returns []. For kind:'selection': controller hook (shell bridge — tool-aware) is the primary path; handle.geometry.getSelectionRects is the fallback.",
140
+ };
141
+
142
+ export const subscribeMetadata: ApiV3FnMetadata = {
143
+ name: "ui.overlays.subscribe",
144
+ status: "live-with-adapter",
145
+ sourceLayer: "presentation",
146
+ liveEvidence: {
147
+ runnerTest: "test/api/v3/ui/overlays-anchor.test.ts",
148
+ commit: "refactor-07-slice-2",
149
+ },
150
+ // Stream-form bidirectional channel (U4 + U7). Subscribe call is
151
+ // uiVisible — attaching the listener re-parents which overlays are
152
+ // re-resolved on geometry invalidation. Per-invalidation OverlayAnchorQuery
153
+ // deliveries travel through the listener.
154
+ uxIntent: {
155
+ uiVisible: true,
156
+ expectsUxResponse: "surface-refresh",
157
+ expectedDelta: "overlay subscriber attached; geometry invalidations that overlap attached queries propagate through the listener",
158
+ },
159
+ agentMetadata: { readOrMutate: "read", boundedScope: "document", auditCategory: "ui-overlays-subscribe" },
160
+ stateClass: "C-local",
161
+ persistsTo: "none",
162
+ bidirectional: true,
163
+ subscriptionShape: {
164
+ eventType: "ui.overlays.invalidated",
165
+ payloadType: "OverlayAnchorQuery",
166
+ coalescing: "raf",
167
+ },
168
+ rwdReference: "§UI API § ui.overlays.subscribe. Adapter delegates to UiController.subscribeOverlays; throws when the active binding has no hook. Subscribe call emits one `ux.response.ui.overlays.subscribe` acknowledgement; per-invalidation OverlayAnchorQuery deliveries flow through the listener.",
169
+ };
170
+
171
+ // ----- U9 overlay-visibility metadata (state-classes X3) -----
172
+
173
+ export const getVisibilityMetadata: ApiV3FnMetadata = {
174
+ name: "ui.overlays.getVisibility",
175
+ status: "live",
176
+ sourceLayer: "presentation",
177
+ liveEvidence: {
178
+ runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
179
+ commit: "refactor-10-slice-x3",
180
+ },
181
+ uxIntent: { uiVisible: false },
182
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-read" },
183
+ stateClass: "C-local",
184
+ persistsTo: "none",
185
+ rwdReference: "§UI API § ui.overlays.getVisibility (U9). Single composition site merging class-A policy (handle.getVisibilityPolicy) with class-C local preference. debug-panel is additionally gated on ChromePosture.debugMode per CLAUDE.md Protected Invariant.",
186
+ };
187
+
188
+ export const setLocalPreferenceMetadata: ApiV3FnMetadata = {
189
+ name: "ui.overlays.setLocalPreference",
190
+ status: "live",
191
+ sourceLayer: "presentation",
192
+ liveEvidence: {
193
+ runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
194
+ commit: "refactor-10-slice-x3",
195
+ },
196
+ uxIntent: {
197
+ uiVisible: true,
198
+ expectsUxResponse: "surface-refresh",
199
+ expectedDelta: "overlay local preference set; subscribers for the kind receive the new composed visibility",
200
+ },
201
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-overlays-set-preference" },
202
+ stateClass: "C-local",
203
+ persistsTo: "none",
204
+ rwdReference: "§UI API § ui.overlays.setLocalPreference (U9). Class-C write; no broadcast, no canonical persistence. Hosts that want cross-reload survival save the preferences on beforeunload and restore on mount.",
205
+ };
206
+
207
+ export const resetLocalPreferenceMetadata: ApiV3FnMetadata = {
208
+ name: "ui.overlays.resetLocalPreference",
209
+ status: "live",
210
+ sourceLayer: "presentation",
211
+ liveEvidence: {
212
+ runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
213
+ commit: "refactor-10-slice-x3",
214
+ },
215
+ uxIntent: {
216
+ uiVisible: true,
217
+ expectsUxResponse: "surface-refresh",
218
+ expectedDelta: "overlay local preference cleared; subsequent reads fall back to policy default",
219
+ },
220
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-overlays-reset-preference" },
221
+ stateClass: "C-local",
222
+ persistsTo: "none",
223
+ rwdReference: "§UI API § ui.overlays.resetLocalPreference (U9). Class-C write — clears the per-session preference for a specific OverlayKind.",
224
+ };
225
+
226
+ export const subscribeVisibilityMetadata: ApiV3FnMetadata = {
227
+ name: "ui.overlays.subscribeVisibility",
228
+ status: "live",
229
+ sourceLayer: "presentation",
230
+ liveEvidence: {
231
+ runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
232
+ commit: "refactor-10-slice-x3",
233
+ },
234
+ uxIntent: {
235
+ uiVisible: true,
236
+ expectsUxResponse: "surface-refresh",
237
+ expectedDelta: "visibility subscriber attached; per-kind OverlayVisibility deliveries propagate on local-preference change",
238
+ },
239
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-subscribe-visibility" },
240
+ stateClass: "C-local",
241
+ persistsTo: "none",
242
+ bidirectional: true,
243
+ subscriptionShape: {
244
+ eventType: "ui.overlays.visibility_changed",
245
+ payloadType: "OverlayVisibility",
246
+ coalescing: "microtask",
247
+ },
248
+ rwdReference: "§UI API § ui.overlays.subscribeVisibility (U9). Fires on BOTH local-preference mutation AND class-A policy-change (closed 2026-04-22 — chained to L06's handle.subscribeVisibilityPolicy). debugMode-change fan-out remains a Phase Q follow-up.",
249
+ };
250
+
251
+ export function createOverlaysFamily(ctx: UiApiContext) {
252
+ // Closure-local visibility state — per UI API instance. Cross-instance
253
+ // isolation is structural (two createUiApi(handle) calls hold two
254
+ // states). Storage is in-memory; hosts persist across reloads via
255
+ // beforeunload/restore per architecture 10 §U9.
256
+ const visibilityState = createOverlaysVisibilityState();
257
+
258
+ // W10 policy-change fan-out — when L06 mutates the class-A policy
259
+ // (via runtime.workflow.setVisibilityPolicy / clearVisibilityPolicy),
260
+ // re-notify every kind that has a live subscriber. This closes the
261
+ // follow-up flagged in `subscribeVisibilityMetadata.rwdReference`:
262
+ // previously we only fired on local-preference changes, leaving
263
+ // authoring-tool policy mutations invisible until a doc reload.
264
+ //
265
+ // The subscription is global (not per-kind) because L06's hook fires
266
+ // for ANY policy change — we iterate our local subscriber map on fire
267
+ // and compose a fresh OverlayVisibility per kind. Cost: O(subscribed
268
+ // kinds) × O(composeOverlayVisibility), bounded by 6 kinds.
269
+ // `subscribeVisibilityPolicy` is on `RuntimeApiHandle` (L06 X1 + L07
270
+ // roster update). Optional-chain defensively: legacy / test-stub
271
+ // handles that predate X1 are allowed to omit it — the graceful-
272
+ // degradation contract is tested at `overlays-visibility.test.ts`
273
+ // "X1⇄X3 — handles without subscribeVisibilityPolicy degrade
274
+ // gracefully (no throw)".
275
+ //
276
+ // TODO(ui.dispose): we intentionally do not retain the unsubscribe
277
+ // here — the family instance lives for the lifetime of the host's UI
278
+ // API, and disposal of that API also collapses the handle. If host
279
+ // teardown ever gains a ui.dispose() hook, the unsubscribe ref MUST
280
+ // be retained and invoked there to avoid a leak on the L06 policy-
281
+ // broadcast channel.
282
+ ctx.handle.subscribeVisibilityPolicy?.(() => {
283
+ for (const kind of visibilityState.subscribers.keys()) {
284
+ notifyVisibilitySubscribers(ctx, visibilityState, kind);
285
+ }
286
+ });
287
+
288
+ return {
289
+ getAnchor(query: OverlayAnchorQuery): GeometryRect | null {
290
+ // `kind: "selection"` — bridge-first, geometry fallback.
291
+ //
292
+ // Flicker-remediation 2026-04-22: Slice 11's original geometry-
293
+ // first order pre-empted the shell-side bridge. The bridge
294
+ // (populated by `useShellSelectionAnchorBridge` via
295
+ // `resolveSelectionAnchor`) carries tool-aware semantics —
296
+ // structural-context tools resolve to the containing block or
297
+ // table-cell rect, not the raw selection range. Raw
298
+ // `handle.geometry.getSelectionRects` returns only the range
299
+ // rects; using them for a tool-active selection placed the
300
+ // floating surface against the wrong anchor. Order swapped so
301
+ // the shell's semantically-correct resolver wins when mounted;
302
+ // geometry remains the fallback for headless / pre-mount cases
303
+ // where no controller is bound.
304
+ if (query.kind === "selection") {
305
+ const hook = ctx.binding?.controller.getOverlayAnchor;
306
+ if (hook) {
307
+ const fromBridge = hook(query);
308
+ if (fromBridge) return fromBridge;
309
+ }
310
+ const rects = resolveSelectionRects(ctx);
311
+ return rects.length > 0 ? (rects[0] ?? null) : null;
312
+ }
313
+ const anchorQuery = toAnchorQuery(query);
314
+ if (anchorQuery) {
315
+ const fromGeometry = ctx.handle.geometry?.getAnchor?.(anchorQuery) ?? null;
316
+ if (fromGeometry) return fromGeometry;
317
+ }
318
+ const hook = ctx.binding?.controller.getOverlayAnchor;
319
+ // U4: geometry unavailable AND no controller hook → null.
320
+ // MUST NOT fall back to DOM measurement.
321
+ return hook ? hook(query) : null;
322
+ },
323
+ getRects(query: OverlayAnchorQuery): readonly GeometryRect[] {
324
+ // Bridge-first, geometry fallback — same rationale as getAnchor.
325
+ if (query.kind === "selection") {
326
+ const hook = ctx.binding?.controller.getOverlayRects;
327
+ if (hook) {
328
+ const fromBridge = hook(query);
329
+ if (fromBridge.length > 0) return fromBridge;
330
+ }
331
+ return resolveSelectionRects(ctx);
332
+ }
333
+ const anchorQuery = toAnchorQuery(query);
334
+ if (anchorQuery) {
335
+ const fromGeometry = ctx.handle.geometry?.getAnchorRects?.(anchorQuery) ?? [];
336
+ if (fromGeometry.length > 0) return fromGeometry;
337
+ }
338
+ const hook = ctx.binding?.controller.getOverlayRects;
339
+ return hook ? hook(query) : [];
340
+ },
341
+ subscribe(listener: UiListener<OverlayAnchorQuery>): UiUnsubscribe {
342
+ const controller = ctx.binding?.controller;
343
+ if (!controller) {
344
+ throw new Error(
345
+ "ui.overlays.subscribe: no controller bound — call ui.session.bind(controller) first",
346
+ );
347
+ }
348
+ if (!controller.subscribeOverlays) {
349
+ throw new Error(
350
+ `ui.overlays.subscribe: controller of kind "${controller.kind}" did not provide a subscribeOverlays hook`,
351
+ );
352
+ }
353
+ const unsubscribe = controller.subscribeOverlays(listener);
354
+ emitUxResponse(ctx.handle, {
355
+ apiFn: subscribeMetadata.name,
356
+ intent: subscribeMetadata.uxIntent.expectedDelta ?? "",
357
+ mockOrLive: "live-with-adapter",
358
+ uiVisible: true,
359
+ expectedDelta: subscribeMetadata.uxIntent.expectedDelta,
360
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.overlays" } },
361
+ });
362
+ return unsubscribe;
363
+ },
364
+
365
+ // ----- U9 overlay-visibility (state-classes X3) -----
366
+
367
+ getVisibility(kind: OverlayKind): OverlayVisibility {
368
+ return composeOverlayVisibility(ctx, visibilityState, kind);
369
+ },
370
+
371
+ setLocalPreference(kind: OverlayKind, visible: boolean): void {
372
+ const prev = visibilityState.localPrefs.get(kind);
373
+ if (prev === visible) return; // idempotent — no spurious wakeups.
374
+ visibilityState.localPrefs.set(kind, visible);
375
+ emitUxResponse(ctx.handle, {
376
+ apiFn: setLocalPreferenceMetadata.name,
377
+ intent: setLocalPreferenceMetadata.uxIntent.expectedDelta ?? "",
378
+ mockOrLive: "live",
379
+ uiVisible: true,
380
+ expectedDelta: setLocalPreferenceMetadata.uxIntent.expectedDelta,
381
+ actualDelta: { kind: "surface-refresh", payload: { overlay: kind, visible } },
382
+ });
383
+ notifyVisibilitySubscribers(ctx, visibilityState, kind);
384
+ },
385
+
386
+ resetLocalPreference(kind: OverlayKind): void {
387
+ const had = visibilityState.localPrefs.has(kind);
388
+ if (!had) return; // idempotent.
389
+ visibilityState.localPrefs.delete(kind);
390
+ emitUxResponse(ctx.handle, {
391
+ apiFn: resetLocalPreferenceMetadata.name,
392
+ intent: resetLocalPreferenceMetadata.uxIntent.expectedDelta ?? "",
393
+ mockOrLive: "live",
394
+ uiVisible: true,
395
+ expectedDelta: resetLocalPreferenceMetadata.uxIntent.expectedDelta,
396
+ actualDelta: { kind: "surface-refresh", payload: { overlay: kind, reset: true } },
397
+ });
398
+ notifyVisibilitySubscribers(ctx, visibilityState, kind);
399
+ },
400
+
401
+ subscribeVisibility(
402
+ kind: OverlayKind,
403
+ listener: UiListener<OverlayVisibility>,
404
+ ): UiUnsubscribe {
405
+ let listeners = visibilityState.subscribers.get(kind);
406
+ if (!listeners) {
407
+ listeners = new Set();
408
+ visibilityState.subscribers.set(kind, listeners);
409
+ }
410
+ listeners.add(listener);
411
+ emitUxResponse(ctx.handle, {
412
+ apiFn: subscribeVisibilityMetadata.name,
413
+ intent: subscribeVisibilityMetadata.uxIntent.expectedDelta ?? "",
414
+ mockOrLive: "live",
415
+ uiVisible: true,
416
+ expectedDelta: subscribeVisibilityMetadata.uxIntent.expectedDelta,
417
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.overlays.visibility", overlay: kind } },
418
+ });
419
+ return () => {
420
+ const current = visibilityState.subscribers.get(kind);
421
+ if (!current) return;
422
+ current.delete(listener);
423
+ if (current.size === 0) visibilityState.subscribers.delete(kind);
424
+ };
425
+ },
426
+ };
427
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.scope` family (layer 10).
3
+ *
4
+ * Opens the mounted-path scope-read seam requested by coord-08 §1 +
5
+ * coord-11 §5/§50 ("L08 §2 blocked on L10 ui.scope.* seam"). Unblocked
6
+ * by L07's `compileScopeBundleById` handle primitive (refactor/07
7
+ * commit `3ffac7fe`, 2026-04-23).
8
+ *
9
+ * This slice ships the MVP primitive:
10
+ *
11
+ * ui.scope.getBundle(scopeId: string): ScopeBundle | null
12
+ *
13
+ * Thin wrapper over `handle.compileScopeBundleById(scopeId, nowUtc)`.
14
+ * `nowUtc` is generated internally via `new Date().toISOString()` so UI
15
+ * consumers don't have to thread a clock through their code. The
16
+ * underlying AI API (`ai.getScopeBundle`) keeps its explicit-clock
17
+ * contract for agent-replay determinism; the UI seam trades that for
18
+ * ergonomic one-call scope reads appropriate for interactive UX.
19
+ *
20
+ * Enumeration — `ui.scope.list()` returning `SemanticScope[]` — is a
21
+ * deferred follow-up. Coord-11 §5 scope-card migration needs
22
+ * one-at-a-time bundle reads (the card mounts for a selected scope);
23
+ * broad enumeration stays on L11 using existing `WorkflowScope`-typed
24
+ * snapshots from the rail until a batch primitive exists.
25
+ *
26
+ * Contract compliance:
27
+ * - U1: reads through `RuntimeApiHandle` + returned ScopeBundle is
28
+ * pure canonical-document projection. No DOM / PM.
29
+ * - stateClass: A-canonical (canonical document truth). Unlike other
30
+ * L10 families which are C-local, this one surfaces runtime-owned
31
+ * canonical state — an intentional exception documented here.
32
+ * Persisted to canonical per the underlying scope authoring.
33
+ */
34
+
35
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
36
+ import type { ScopeBundle } from "../../public-types.ts";
37
+ import type { UiApiContext } from "./_context.ts";
38
+
39
+ export const getBundleMetadata: ApiV3FnMetadata = {
40
+ name: "ui.scope.getBundle",
41
+ status: "live-with-adapter",
42
+ sourceLayer: "presentation",
43
+ liveEvidence: {
44
+ runnerTest: "test/api/v3/ui/scope-get-bundle.test.ts",
45
+ commit: "refactor-10-slice-ui-scope-mvp",
46
+ },
47
+ uxIntent: { uiVisible: false },
48
+ agentMetadata: {
49
+ readOrMutate: "read",
50
+ boundedScope: "scope",
51
+ auditCategory: "ui-scope-read",
52
+ },
53
+ // UI-surface convenience wrapper over a canonical read. Classification
54
+ // mirrors the underlying `ai.getScopeBundle` — reading canonical
55
+ // scope state, not class-C local UI preference.
56
+ stateClass: "A-canonical",
57
+ persistsTo: "canonical",
58
+ rwdReference:
59
+ "§UI API § ui.scope.getBundle. Wraps handle.compileScopeBundleById(scopeId, nowUtc). nowUtc generated internally from new Date().toISOString() for ergonomic UI use; callers that need determinism (agent replay, tests) use ai.getScopeBundle with an explicit clock.",
60
+ };
61
+
62
+ export function createScopeFamily(ctx: UiApiContext) {
63
+ return {
64
+ getBundle(scopeId: string): ScopeBundle | null {
65
+ const compile = ctx.handle.compileScopeBundleById;
66
+ if (typeof compile !== "function") return null;
67
+ const nowUtc = new Date().toISOString();
68
+ return compile.call(ctx.handle, scopeId, nowUtc);
69
+ },
70
+ };
71
+ }