@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,476 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.chrome` family (layer 10).
3
+ *
4
+ * Slice 4 wiring:
5
+ * - getPosture — live-with-adapter. Single composition site for
6
+ * ChromePosture. Reads the guard verdict from
7
+ * `handle.getInteractionGuardSnapshot()` and the
8
+ * mode/readOnly from `handle.getRenderSnapshot()`;
9
+ * overlays the host-provided slice (reviewMode,
10
+ * markupDisplay, debugMode, chromePreset) via
11
+ * `controller.getHostPosture()`. Host defaults
12
+ * applied at this layer — debugMode defaults to
13
+ * "off" per U6 + CLAUDE.md Protected Invariants.
14
+ * - getPinnedSurfaces — live-with-adapter. Delegates to
15
+ * `controller.getPinnedSurfaces()`; returns [] when
16
+ * unbound or the hook is absent. The existing
17
+ * `chrome-preset-model` lives in the presentation
18
+ * layer; the controller adapts it.
19
+ * - subscribe — live-with-adapter. Delegates to
20
+ * `controller.subscribeChrome`.
21
+ *
22
+ * Contract U5 — chrome posture is composed once per read at this single
23
+ * site. Components that call getPosture() do not re-derive from individual
24
+ * inputs. `refactor/10 Slice 6` migrates chrome components off their
25
+ * direct `getInteractionGuardSnapshot()` reads.
26
+ *
27
+ * Contract U6 — debug mode defaults "off". The default MUST survive every
28
+ * code path in this file. CLAUDE.md flags this has regressed multiple
29
+ * times; `test/api/v3/ui/chrome-posture-shape.test.ts` asserts it.
30
+ */
31
+
32
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
33
+ import type {
34
+ ChromePosture,
35
+ ChromeSurface,
36
+ ChromeDocumentMode,
37
+ UiListener,
38
+ UiUnsubscribe,
39
+ } from "./_types.ts";
40
+ import type {
41
+ ChromeComposition,
42
+ ChromeCompositionInput,
43
+ EditorRailTab,
44
+ } from "./chrome-composition.ts";
45
+ import { resolveChromeComposition } from "./chrome-composition.ts";
46
+ import type { UiApiContext } from "./_context.ts";
47
+ import { emitUxResponse } from "../_ux-response.ts";
48
+
49
+ export const getPostureMetadata: ApiV3FnMetadata = {
50
+ name: "ui.chrome.getPosture",
51
+ status: "live-with-adapter",
52
+ sourceLayer: "presentation",
53
+ liveEvidence: {
54
+ runnerTest: "test/api/v3/ui/chrome-posture-shape.test.ts",
55
+ commit: "refactor-10-slice-4",
56
+ },
57
+ uxIntent: { uiVisible: false },
58
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-read" },
59
+ stateClass: "C-local",
60
+ persistsTo: "none",
61
+ rwdReference:
62
+ "§UI API § ui.chrome.getPosture. Composes handle.getInteractionGuardSnapshot() + handle.getRenderSnapshot() with controller.getHostPosture() at a single site. debugMode defaults 'off' per U6.",
63
+ };
64
+
65
+ export const getCompositionMetadata: ApiV3FnMetadata = {
66
+ name: "ui.chrome.getComposition",
67
+ status: "live",
68
+ sourceLayer: "presentation",
69
+ liveEvidence: {
70
+ runnerTest: "test/api/v3/ui/chrome-composition-shape.test.ts",
71
+ commit: "refactor-10-slice-9",
72
+ },
73
+ uxIntent: { uiVisible: false },
74
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-read" },
75
+ stateClass: "C-local",
76
+ persistsTo: "none",
77
+ rwdReference:
78
+ "§UI API § ui.chrome.getComposition (U5.b). Pure function — wraps resolveChromeComposition from src/api/v3/ui/chrome-composition.ts. Input is host-supplied; output is the single ChromeComposition record every chrome surface reads. Caller memoizes.",
79
+ };
80
+
81
+ export const getPinnedSurfacesMetadata: ApiV3FnMetadata = {
82
+ name: "ui.chrome.getPinnedSurfaces",
83
+ status: "live-with-adapter",
84
+ sourceLayer: "presentation",
85
+ liveEvidence: {
86
+ runnerTest: "test/api/v3/ui/chrome-pinned-surfaces.test.ts",
87
+ commit: "refactor-10-slice-4",
88
+ },
89
+ uxIntent: { uiVisible: false },
90
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-read" },
91
+ stateClass: "C-local",
92
+ persistsTo: "none",
93
+ rwdReference:
94
+ "§UI API § ui.chrome.getPinnedSurfaces. Adapter delegates to controller.getPinnedSurfaces which wraps chrome-preset-model on the bind-side.",
95
+ };
96
+
97
+ export const subscribeMetadata: ApiV3FnMetadata = {
98
+ name: "ui.chrome.subscribe",
99
+ status: "live-with-adapter",
100
+ sourceLayer: "presentation",
101
+ liveEvidence: {
102
+ runnerTest: "test/api/v3/ui/chrome-posture-coherence.test.ts",
103
+ commit: "refactor-07-slice-2",
104
+ },
105
+ // Stream-form bidirectional channel (U5 + U7). Posture changes
106
+ // (guard-snapshot updates, host posture flips like debugMode toggle)
107
+ // fan out through this listener. Coalescing is microtask — posture
108
+ // composes at most once per render tick, no rAF batching needed.
109
+ uxIntent: {
110
+ uiVisible: true,
111
+ expectsUxResponse: "surface-refresh",
112
+ expectedDelta: "chrome subscriber attached; posture changes propagate through the listener",
113
+ },
114
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-subscribe" },
115
+ stateClass: "C-local",
116
+ persistsTo: "none",
117
+ bidirectional: true,
118
+ subscriptionShape: {
119
+ eventType: "ui.chrome.posture-changed",
120
+ payloadType: "ChromePosture",
121
+ coalescing: "microtask",
122
+ },
123
+ rwdReference: "§UI API § ui.chrome.subscribe. Adapter delegates to controller.subscribeChrome. Subscribe call emits one `ux.response.ui.chrome.subscribe` acknowledgement; per-change ChromePosture deliveries flow through the listener (microtask-coalesced).",
124
+ };
125
+
126
+ /**
127
+ * Normalize a `DocumentMode` (long form — "editing" / "suggesting" /
128
+ * "viewing" / "commenting") OR a `WorkflowScopeMode` (short form —
129
+ * "edit" / "suggest" / "view" / "comment") to the ChromePosture short
130
+ * form. Unknown values fall back to "edit" — the most permissive posture
131
+ * is the safest default.
132
+ */
133
+ function normalizeDocumentMode(raw: string | undefined | null): ChromeDocumentMode {
134
+ switch (raw) {
135
+ case "editing":
136
+ case "edit":
137
+ return "edit";
138
+ case "suggesting":
139
+ case "suggest":
140
+ return "suggest";
141
+ case "commenting":
142
+ case "comment":
143
+ return "comment";
144
+ case "viewing":
145
+ case "view":
146
+ return "view";
147
+ default:
148
+ return "edit";
149
+ }
150
+ }
151
+
152
+ function normalizeEffectiveMode(
153
+ mode: string | undefined | null,
154
+ ): ChromePosture["effectiveMode"] {
155
+ if (mode === "blocked") return "blocked";
156
+ return normalizeDocumentMode(mode);
157
+ }
158
+
159
+ // Rail-state metadata — class-C per-session local state, owned by L10
160
+ // per the three-way state model (§9.1).
161
+
162
+ export const getActiveRailTabMetadata: ApiV3FnMetadata = {
163
+ name: "ui.chrome.getActiveRailTab",
164
+ status: "live",
165
+ sourceLayer: "presentation",
166
+ liveEvidence: {
167
+ runnerTest: "test/api/v3/ui/chrome-rail-state.test.ts",
168
+ commit: "refactor-10-task-2-rail-state",
169
+ },
170
+ uxIntent: { uiVisible: false },
171
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-read" },
172
+ stateClass: "C-local",
173
+ persistsTo: "none",
174
+ rwdReference: "§UI API § ui.chrome.getActiveRailTab. Class-C rail state read — null when no tab is active. UI API owns the state; hosts persist across reloads via beforeunload save/restore if needed (no localStorage inside L10).",
175
+ };
176
+
177
+ export const setActiveRailTabMetadata: ApiV3FnMetadata = {
178
+ name: "ui.chrome.setActiveRailTab",
179
+ status: "live",
180
+ sourceLayer: "presentation",
181
+ liveEvidence: {
182
+ runnerTest: "test/api/v3/ui/chrome-rail-state.test.ts",
183
+ commit: "refactor-10-task-2-rail-state",
184
+ },
185
+ uxIntent: {
186
+ uiVisible: true,
187
+ expectsUxResponse: "surface-refresh",
188
+ expectedDelta: "rail active tab updated; subscribers receive the new value; next getComposition reflects the change",
189
+ },
190
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-chrome-set-rail-state" },
191
+ stateClass: "C-local",
192
+ persistsTo: "none",
193
+ rwdReference: "§UI API § ui.chrome.setActiveRailTab (null closes the rail).",
194
+ };
195
+
196
+ export const getPinnedRailTabsMetadata: ApiV3FnMetadata = {
197
+ name: "ui.chrome.getPinnedRailTabs",
198
+ status: "live",
199
+ sourceLayer: "presentation",
200
+ liveEvidence: {
201
+ runnerTest: "test/api/v3/ui/chrome-rail-state.test.ts",
202
+ commit: "refactor-10-task-2-rail-state",
203
+ },
204
+ uxIntent: { uiVisible: false },
205
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-read" },
206
+ stateClass: "C-local",
207
+ persistsTo: "none",
208
+ rwdReference: "§UI API § ui.chrome.getPinnedRailTabs.",
209
+ };
210
+
211
+ export const pinRailTabMetadata: ApiV3FnMetadata = {
212
+ name: "ui.chrome.pinRailTab",
213
+ status: "live",
214
+ sourceLayer: "presentation",
215
+ liveEvidence: {
216
+ runnerTest: "test/api/v3/ui/chrome-rail-state.test.ts",
217
+ commit: "refactor-10-task-2-rail-state",
218
+ },
219
+ uxIntent: {
220
+ uiVisible: true,
221
+ expectsUxResponse: "surface-refresh",
222
+ expectedDelta: "rail tab pinned; next getComposition reflects the pinned set",
223
+ },
224
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-chrome-set-rail-state" },
225
+ stateClass: "C-local",
226
+ persistsTo: "none",
227
+ rwdReference: "§UI API § ui.chrome.pinRailTab. Idempotent.",
228
+ };
229
+
230
+ export const unpinRailTabMetadata: ApiV3FnMetadata = {
231
+ name: "ui.chrome.unpinRailTab",
232
+ status: "live",
233
+ sourceLayer: "presentation",
234
+ liveEvidence: {
235
+ runnerTest: "test/api/v3/ui/chrome-rail-state.test.ts",
236
+ commit: "refactor-10-task-2-rail-state",
237
+ },
238
+ uxIntent: {
239
+ uiVisible: true,
240
+ expectsUxResponse: "surface-refresh",
241
+ expectedDelta: "rail tab unpinned; next getComposition reflects the pinned set",
242
+ },
243
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-chrome-set-rail-state" },
244
+ stateClass: "C-local",
245
+ persistsTo: "none",
246
+ rwdReference: "§UI API § ui.chrome.unpinRailTab. Idempotent.",
247
+ };
248
+
249
+ export const subscribeRailStateMetadata: ApiV3FnMetadata = {
250
+ name: "ui.chrome.subscribeRailState",
251
+ status: "live",
252
+ sourceLayer: "presentation",
253
+ liveEvidence: {
254
+ runnerTest: "test/api/v3/ui/chrome-rail-state.test.ts",
255
+ commit: "refactor-10-task-2-rail-state",
256
+ },
257
+ uxIntent: {
258
+ uiVisible: true,
259
+ expectsUxResponse: "surface-refresh",
260
+ expectedDelta: "rail-state subscriber attached; receives the new active tab on every set/pin/unpin that mutated state",
261
+ },
262
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-chrome-subscribe" },
263
+ stateClass: "C-local",
264
+ persistsTo: "none",
265
+ bidirectional: true,
266
+ subscriptionShape: {
267
+ eventType: "ui.chrome.rail_state_changed",
268
+ payloadType: "EditorRailTab | null",
269
+ coalescing: "microtask",
270
+ },
271
+ rwdReference: "§UI API § ui.chrome.subscribeRailState. Fires on any mutation to active-tab or pinned-set. Payload is the new active tab; consumers read the pinned set via getPinnedRailTabs.",
272
+ };
273
+
274
+ export function createChromeFamily(ctx: UiApiContext) {
275
+ // Closure-local class-C rail state. Per-createUiApi-instance — two
276
+ // instances over the same handle hold independent rail state (same
277
+ // isolation pattern as U9 overlay-visibility).
278
+ //
279
+ // `activeRailTabSet` distinguishes "user explicitly set activeRailTab"
280
+ // (including to `null`, meaning "close the rail") from "user hasn't
281
+ // expressed an opinion" (composer picks a mode-default). Without this
282
+ // flag, getComposition would force `null` for every caller that
283
+ // previously relied on the composer's mode-appropriate default.
284
+ let activeRailTab: EditorRailTab | null = null;
285
+ let activeRailTabSet = false;
286
+ const pinnedRailTabs = new Set<EditorRailTab>();
287
+ const railStateSubscribers = new Set<UiListener<EditorRailTab | null>>();
288
+
289
+ function notifyRailStateSubscribers(): void {
290
+ for (const listener of railStateSubscribers) {
291
+ try {
292
+ listener(activeRailTab);
293
+ } catch {
294
+ // Isolate listener errors.
295
+ }
296
+ }
297
+ }
298
+
299
+ return {
300
+ getPosture(): ChromePosture {
301
+ // Runtime-sourced slice — the guard + mode + readOnly. Per DS §8.3
302
+ // (runtime owns truth) we trust the RuntimeApiHandle contract —
303
+ // `getInteractionGuardSnapshot` and `getRenderSnapshot` are
304
+ // unconditional on the handle interface. A throw here is a runtime
305
+ // regression, not a "pre-mount" state, and masking it with a
306
+ // silent fallback would let the UI declare `effectiveMode: "edit"`
307
+ // while the substrate is broken. Pre-mount handles use the
308
+ // inert-facet pattern from `loading-runtime-bridge.ts`.
309
+ const guard = ctx.handle.getInteractionGuardSnapshot() as
310
+ | { effectiveMode?: string; blockedReasons?: ReadonlyArray<{ code?: string; reason?: string } | string> }
311
+ | undefined
312
+ | null;
313
+ const effectiveMode: ChromePosture["effectiveMode"] = guard
314
+ ? normalizeEffectiveMode(guard.effectiveMode)
315
+ : "edit";
316
+ const blockedReasons: readonly string[] =
317
+ guard && Array.isArray(guard.blockedReasons)
318
+ ? guard.blockedReasons.map((r) =>
319
+ typeof r === "string" ? r : (r?.code ?? r?.reason ?? "blocked"),
320
+ )
321
+ : [];
322
+
323
+ const render = ctx.handle.getRenderSnapshot() as
324
+ | { documentMode?: string; readOnly?: boolean }
325
+ | undefined
326
+ | null;
327
+ const documentMode: ChromeDocumentMode = render
328
+ ? normalizeDocumentMode(render.documentMode)
329
+ : "edit";
330
+ const readOnly = render?.readOnly === true;
331
+
332
+ // Host-provided slice — reviewMode / markupDisplay / debugMode /
333
+ // chromePreset. Defaults applied here; U6 — debugMode MUST default
334
+ // to "off" (CLAUDE.md Protected Invariants).
335
+ const host = ctx.binding?.controller.getHostPosture?.() ?? undefined;
336
+ const reviewMode = host?.reviewMode ?? "observer";
337
+ const markupDisplay = host?.markupDisplay ?? "final";
338
+ const debugMode = host?.debugMode ?? "off";
339
+ const chromePreset = host?.chromePreset;
340
+
341
+ return {
342
+ effectiveMode,
343
+ documentMode,
344
+ readOnly,
345
+ reviewMode,
346
+ markupDisplay,
347
+ debugMode,
348
+ chromePreset,
349
+ blockedReasons,
350
+ };
351
+ },
352
+ getComposition(input: ChromeCompositionInput): ChromeComposition {
353
+ // U5.b — compose with class-C rail state the UI API owns. The
354
+ // caller can override `activeRailTab` / `pinnedRailTabs` via input;
355
+ // when omitted AND the UI API's internal state has been set,
356
+ // internal state fills in. When the caller omits AND internal state
357
+ // is untouched (the initial-mount path), the composer's mode-default
358
+ // kicks in (e.g. rail open on "advanced" preset picks a default
359
+ // active tab per mode). This preserves back-compat for callers that
360
+ // never interacted with rail-state methods.
361
+ const merged: ChromeCompositionInput = {
362
+ ...input,
363
+ ...(input.activeRailTab !== undefined
364
+ ? {}
365
+ : activeRailTabSet
366
+ ? { activeRailTab }
367
+ : {}),
368
+ ...(input.pinnedRailTabs !== undefined
369
+ ? {}
370
+ : pinnedRailTabs.size > 0
371
+ ? { pinnedRailTabs }
372
+ : {}),
373
+ };
374
+ return resolveChromeComposition(merged);
375
+ },
376
+ getPinnedSurfaces(): readonly ChromeSurface[] {
377
+ const hook = ctx.binding?.controller.getPinnedSurfaces;
378
+ return hook ? hook() : [];
379
+ },
380
+ subscribe(listener: UiListener<ChromePosture>): UiUnsubscribe {
381
+ const controller = ctx.binding?.controller;
382
+ if (!controller) {
383
+ throw new Error(
384
+ "ui.chrome.subscribe: no controller bound — call ui.session.bind(controller) first",
385
+ );
386
+ }
387
+ if (!controller.subscribeChrome) {
388
+ throw new Error(
389
+ `ui.chrome.subscribe: controller of kind "${controller.kind}" did not provide a subscribeChrome hook`,
390
+ );
391
+ }
392
+ const unsubscribe = controller.subscribeChrome(listener);
393
+ emitUxResponse(ctx.handle, {
394
+ apiFn: subscribeMetadata.name,
395
+ intent: subscribeMetadata.uxIntent.expectedDelta ?? "",
396
+ mockOrLive: "live-with-adapter",
397
+ uiVisible: true,
398
+ expectedDelta: subscribeMetadata.uxIntent.expectedDelta,
399
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.chrome" } },
400
+ });
401
+ return unsubscribe;
402
+ },
403
+
404
+ // ----- Class-C rail state (Task 2) -----
405
+
406
+ getActiveRailTab(): EditorRailTab | null {
407
+ return activeRailTab;
408
+ },
409
+
410
+ setActiveRailTab(tab: EditorRailTab | null): void {
411
+ if (activeRailTabSet && activeRailTab === tab) return; // idempotent.
412
+ activeRailTab = tab;
413
+ activeRailTabSet = true;
414
+ emitUxResponse(ctx.handle, {
415
+ apiFn: setActiveRailTabMetadata.name,
416
+ intent: setActiveRailTabMetadata.uxIntent.expectedDelta ?? "",
417
+ mockOrLive: "live",
418
+ uiVisible: true,
419
+ expectedDelta: setActiveRailTabMetadata.uxIntent.expectedDelta,
420
+ actualDelta: { kind: "surface-refresh", payload: { activeRailTab: tab } },
421
+ });
422
+ notifyRailStateSubscribers();
423
+ },
424
+
425
+ getPinnedRailTabs(): ReadonlySet<EditorRailTab> {
426
+ // Return a defensive snapshot — mutation on the caller side must
427
+ // not leak into UI API state.
428
+ return new Set(pinnedRailTabs);
429
+ },
430
+
431
+ pinRailTab(tab: EditorRailTab): void {
432
+ if (pinnedRailTabs.has(tab)) return;
433
+ pinnedRailTabs.add(tab);
434
+ emitUxResponse(ctx.handle, {
435
+ apiFn: pinRailTabMetadata.name,
436
+ intent: pinRailTabMetadata.uxIntent.expectedDelta ?? "",
437
+ mockOrLive: "live",
438
+ uiVisible: true,
439
+ expectedDelta: pinRailTabMetadata.uxIntent.expectedDelta,
440
+ actualDelta: { kind: "surface-refresh", payload: { pinned: tab } },
441
+ });
442
+ notifyRailStateSubscribers();
443
+ },
444
+
445
+ unpinRailTab(tab: EditorRailTab): void {
446
+ if (!pinnedRailTabs.has(tab)) return;
447
+ pinnedRailTabs.delete(tab);
448
+ emitUxResponse(ctx.handle, {
449
+ apiFn: unpinRailTabMetadata.name,
450
+ intent: unpinRailTabMetadata.uxIntent.expectedDelta ?? "",
451
+ mockOrLive: "live",
452
+ uiVisible: true,
453
+ expectedDelta: unpinRailTabMetadata.uxIntent.expectedDelta,
454
+ actualDelta: { kind: "surface-refresh", payload: { unpinned: tab } },
455
+ });
456
+ notifyRailStateSubscribers();
457
+ },
458
+
459
+ subscribeRailState(
460
+ listener: UiListener<EditorRailTab | null>,
461
+ ): UiUnsubscribe {
462
+ railStateSubscribers.add(listener);
463
+ emitUxResponse(ctx.handle, {
464
+ apiFn: subscribeRailStateMetadata.name,
465
+ intent: subscribeRailStateMetadata.uxIntent.expectedDelta ?? "",
466
+ mockOrLive: "live",
467
+ uiVisible: true,
468
+ expectedDelta: subscribeRailStateMetadata.uxIntent.expectedDelta,
469
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.chrome.railState" } },
470
+ });
471
+ return () => {
472
+ railStateSubscribers.delete(listener);
473
+ };
474
+ },
475
+ };
476
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.debug` family (layer 10).
3
+ *
4
+ * Slice 5 wiring:
5
+ * - attach — live-with-adapter. Delegates to the bound controller's
6
+ * `attachDebug` hook, which on the bind-side wires
7
+ * `runtime.debug.bus` + `runtime.debug.getSnapshot` to the
8
+ * mounted debug surface. Returns a DebugAttachment whose
9
+ * `detach` invokes the controller-provided cleanup and
10
+ * clears the UI API's attachment record.
11
+ * - detach — live-with-adapter. Ends the current attachment (if any)
12
+ * by invoking its controller-provided cleanup. Idempotent
13
+ * — calling detach without an attachment is a no-op.
14
+ *
15
+ * Phase Q (runtime-embedded debug UX at `src/ui-tailwind/debug/**`) is the
16
+ * presentation consumer of this surface. Per refactor plan 10 risk register
17
+ * #3 and §9 finishing-development-branch discipline, Slice 5 ships the
18
+ * substrate; the Phase Q React components + `debugMode` public prop +
19
+ * `test/ui/debug-mode-visibility-invariant.test.ts` land in a follow-up.
20
+ * `src/ui-tailwind/debug/README.md` documents the reserved scope.
21
+ *
22
+ * Contract U6 — debug attachment is opt-in and scoped. Default state:
23
+ * no debug UX. The `debugMode` prop gate (to-be-added) defaults to
24
+ * `"off"`. CLAUDE.md Protected Invariants flag this has regressed
25
+ * multiple times.
26
+ */
27
+
28
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
29
+ import type { DebugSession, DebugAttachment } from "./_types.ts";
30
+ import type { UiApiContext } from "./_context.ts";
31
+
32
+ export const attachMetadata: ApiV3FnMetadata = {
33
+ name: "ui.debug.attach",
34
+ status: "live-with-adapter",
35
+ sourceLayer: "presentation",
36
+ liveEvidence: {
37
+ runnerTest: "test/api/v3/ui/debug-attach.test.ts",
38
+ commit: "refactor-10-slice-5",
39
+ },
40
+ uxIntent: { uiVisible: false },
41
+ agentMetadata: { readOrMutate: "compound", boundedScope: "session", auditCategory: "ui-debug-attach" },
42
+ stateClass: "C-local",
43
+ persistsTo: "none",
44
+ rwdReference:
45
+ "§UI API § ui.debug.attach. Adapter delegates to UiController.attachDebug which on the bind-side wires runtime.debug.bus + getSnapshot to the mounted debug surface. Throws when no controller is bound or the hook is absent.",
46
+ };
47
+
48
+ export const detachMetadata: ApiV3FnMetadata = {
49
+ name: "ui.debug.detach",
50
+ status: "live-with-adapter",
51
+ sourceLayer: "presentation",
52
+ liveEvidence: {
53
+ runnerTest: "test/api/v3/ui/debug-attach.test.ts",
54
+ commit: "refactor-10-slice-5",
55
+ },
56
+ uxIntent: { uiVisible: false },
57
+ agentMetadata: { readOrMutate: "compound", boundedScope: "session", auditCategory: "ui-debug-detach" },
58
+ stateClass: "C-local",
59
+ persistsTo: "none",
60
+ rwdReference:
61
+ "§UI API § ui.debug.detach. Idempotent — invokes the controller-provided cleanup from the active attachment (if any) and clears the record.",
62
+ };
63
+
64
+ interface DebugAttachmentState {
65
+ session: DebugSession;
66
+ cleanup: () => void;
67
+ }
68
+
69
+ export function createDebugFamily(ctx: UiApiContext) {
70
+ // Per-instance attachment state. One UI API holds at most one active
71
+ // debug attachment; re-attaching tears down the prior attachment first.
72
+ let current: DebugAttachmentState | null = null;
73
+
74
+ function detachInternal(): void {
75
+ if (current) {
76
+ try {
77
+ current.cleanup();
78
+ } finally {
79
+ current = null;
80
+ }
81
+ }
82
+ }
83
+
84
+ return {
85
+ attach(session: DebugSession): DebugAttachment {
86
+ const controller = ctx.binding?.controller;
87
+ if (!controller) {
88
+ throw new Error(
89
+ "ui.debug.attach: no controller bound — call ui.session.bind(controller) first",
90
+ );
91
+ }
92
+ if (!controller.attachDebug) {
93
+ throw new Error(
94
+ `ui.debug.attach: controller of kind "${controller.kind}" did not provide an attachDebug hook`,
95
+ );
96
+ }
97
+ // Tear down any prior attachment so re-attaching does not leak
98
+ // subscriptions or telemetry-bus listeners.
99
+ detachInternal();
100
+ const cleanup = controller.attachDebug(session);
101
+ current = { session, cleanup };
102
+ // NOTE: `DebugAttachment.detach` is aliased to `detachInternal`,
103
+ // which detaches **whatever is currently attached** — not
104
+ // specifically the session this attachment was created with. At
105
+ // most one attachment is live per UI API instance, so this is
106
+ // semantically correct under the "single attachment at a time"
107
+ // model. Callers that hold onto an older `attachment` ref and
108
+ // call its `.detach()` after a re-attach will tear down the
109
+ // NEWER attachment. Typical hosts create + detach in lockstep
110
+ // (attach on mount, detach on unmount), so this is fine; the
111
+ // debug service's RPC driver re-attaches on every session, which
112
+ // matches this model.
113
+ return {
114
+ session,
115
+ detach: detachInternal,
116
+ };
117
+ },
118
+ detach(): void {
119
+ // Idempotent — no-op when nothing is attached. See U6; the default
120
+ // state is "no debug UX", so repeated detach calls must stay safe.
121
+ detachInternal();
122
+ },
123
+ };
124
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @endStateApi v3 — UI API barrel (layer 10).
3
+ *
4
+ * Public exports for `src/api/v3/ui/**`. Consumers import from this barrel
5
+ * rather than individual family files.
6
+ */
7
+
8
+ export { createUiApi } from "./_create.ts";
9
+ export type {
10
+ ApiV3Ui,
11
+ ApiV3UiSession,
12
+ ApiV3UiSurface,
13
+ ApiV3UiViewport,
14
+ ApiV3UiOverlays,
15
+ ApiV3UiChrome,
16
+ ApiV3UiDebug,
17
+ ApiV3UiScope,
18
+ UiController,
19
+ UiControllerKind,
20
+ UiControllerFactory,
21
+ UiBinding,
22
+ UiListener,
23
+ UiUnsubscribe,
24
+ ViewportState,
25
+ ScrollTarget,
26
+ ScrollTargetBehavior,
27
+ SelectionRangeInput,
28
+ OverlayAnchorQuery,
29
+ OverlayKind,
30
+ OverlayVisibility,
31
+ WorkflowMarkupMode,
32
+ ChromePosture,
33
+ ChromeHostPosture,
34
+ ChromeSurface,
35
+ ChromeSurfaceKind,
36
+ ChromeDocumentMode,
37
+ ChromeMarkupDisplay,
38
+ ChromeReviewMode,
39
+ ChromeDebugMode,
40
+ DebugSession,
41
+ DebugAttachment,
42
+ GeometryRect,
43
+ } from "./_types.ts";
44
+
45
+ // Chrome composition types (U5.b) — relocated to layer 10 in Slice 8.
46
+ export { CHROME_RESPONSIVE_THRESHOLDS, resolveChromeComposition } from "./chrome-composition.ts";
47
+ export type {
48
+ ChromeComposition,
49
+ ChromeCompositionInput,
50
+ ChromeDensity,
51
+ ChromeResponsiveTier,
52
+ DiagnosticsSeverity,
53
+ DiagnosticsSignal,
54
+ EditorChromeMode,
55
+ EditorRailTab,
56
+ LocalSurfaceKind,
57
+ RailComposition,
58
+ WorkspaceRowComposition,
59
+ } from "./chrome-composition.ts";
60
+ export {
61
+ resolveChromePreset,
62
+ resolveChromePresetOptions,
63
+ resolveChromeVisibilityForPreset,
64
+ } from "./chrome-preset-model.ts";