@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,100 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.session` family (layer 10).
3
+ *
4
+ * Slice 2 — live. Pure state management inside the UI API instance: bind
5
+ * stores the controller on the shared context, getController reads it,
6
+ * release clears it. No runtime coupling at this layer; the controller is
7
+ * the seam.
8
+ *
9
+ * Contract U2 — UI API is bound to an opaque controller, not React
10
+ * internals. The same shape works for the React editor, a Playwright test,
11
+ * and a headless debug session.
12
+ */
13
+
14
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
15
+ import type { UiController, UiBinding } from "./_types.ts";
16
+ import type { UiApiContext, BindingState } from "./_context.ts";
17
+
18
+ export const bindMetadata: ApiV3FnMetadata = {
19
+ name: "ui.session.bind",
20
+ status: "live",
21
+ sourceLayer: "presentation",
22
+ liveEvidence: {
23
+ runnerTest: "test/api/v3/ui/session-bind.test.ts",
24
+ commit: "refactor-10-slice-2",
25
+ },
26
+ uxIntent: { uiVisible: false },
27
+ agentMetadata: { readOrMutate: "compound", boundedScope: "session", auditCategory: "ui-session-bind" },
28
+ stateClass: "C-local",
29
+ persistsTo: "none",
30
+ rwdReference: "§UI API § ui.session.bind",
31
+ };
32
+
33
+ export const getControllerMetadata: ApiV3FnMetadata = {
34
+ name: "ui.session.getController",
35
+ status: "live",
36
+ sourceLayer: "presentation",
37
+ liveEvidence: {
38
+ runnerTest: "test/api/v3/ui/session-bind.test.ts",
39
+ commit: "refactor-10-slice-2",
40
+ },
41
+ uxIntent: { uiVisible: false },
42
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-session-read" },
43
+ stateClass: "C-local",
44
+ persistsTo: "none",
45
+ rwdReference: "§UI API § ui.session.getController",
46
+ };
47
+
48
+ export const releaseMetadata: ApiV3FnMetadata = {
49
+ name: "ui.session.release",
50
+ status: "live",
51
+ sourceLayer: "presentation",
52
+ liveEvidence: {
53
+ runnerTest: "test/api/v3/ui/session-bind.test.ts",
54
+ commit: "refactor-10-slice-2",
55
+ },
56
+ uxIntent: { uiVisible: false },
57
+ agentMetadata: { readOrMutate: "compound", boundedScope: "session", auditCategory: "ui-session-release" },
58
+ stateClass: "C-local",
59
+ persistsTo: "none",
60
+ rwdReference: "§UI API § ui.session.release",
61
+ };
62
+
63
+ export function createSessionFamily(ctx: UiApiContext) {
64
+ function makeBinding(state: BindingState): UiBinding {
65
+ return {
66
+ controller: state.controller,
67
+ release(): void {
68
+ if (state.released) return;
69
+ state.released = true;
70
+ // Only clear the root binding if it still points to this state —
71
+ // calling release() on a stale binding must NOT unbind a newer one.
72
+ if (ctx.binding === state) {
73
+ ctx.binding = null;
74
+ }
75
+ },
76
+ };
77
+ }
78
+ return {
79
+ bind(controller: UiController): UiBinding {
80
+ // Replace any prior binding. The prior binding's own release() is a
81
+ // no-op after this (its released flag will be set the next time it
82
+ // is invoked — and it can no longer affect ctx.binding).
83
+ if (ctx.binding) {
84
+ ctx.binding.released = true;
85
+ }
86
+ const state: BindingState = { controller, released: false };
87
+ ctx.binding = state;
88
+ return makeBinding(state);
89
+ },
90
+ getController(): UiController | null {
91
+ return ctx.binding?.controller ?? null;
92
+ },
93
+ release(): void {
94
+ if (ctx.binding) {
95
+ ctx.binding.released = true;
96
+ ctx.binding = null;
97
+ }
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.surface` family (layer 10).
3
+ *
4
+ * Slice 2 wiring:
5
+ * - getSelection — live. Reads `handle.getRenderSnapshot().selection`;
6
+ * returns null when the runtime has no snapshot.
7
+ * - setSelection — live-with-adapter. Delegates to the bound
8
+ * controller's `dispatchSelection` hook. Bidirectional
9
+ * channel U3.
10
+ * - getViewport — partial. Delegates to the controller's
11
+ * `getViewport` hook if provided; otherwise returns a
12
+ * mock default. Slice 3 replaces with a
13
+ * `GeometryFacet.getViewport()` read.
14
+ * - scrollTo — live-with-adapter. Delegates to the bound
15
+ * controller's `dispatchScroll`. Slice 3 adds
16
+ * geometry-backed resolution for block/scope/comment/
17
+ * revision targets before dispatch.
18
+ *
19
+ * Contract U3 — selection + viewport are the only bidirectional channels.
20
+ */
21
+
22
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
23
+ import type { SelectionSnapshot } from "../../public-types.ts";
24
+ import type {
25
+ SelectionRangeInput,
26
+ ScrollTarget,
27
+ ViewportState,
28
+ } from "./_types.ts";
29
+ import type { UiApiContext } from "./_context.ts";
30
+ import { readComposedViewport } from "./_context.ts";
31
+ import { emitUxResponse } from "../_ux-response.ts";
32
+
33
+ export const getSelectionMetadata: ApiV3FnMetadata = {
34
+ name: "ui.surface.getSelection",
35
+ status: "live",
36
+ sourceLayer: "presentation",
37
+ liveEvidence: {
38
+ runnerTest: "test/api/v3/ui/selection-bidirectional.test.ts",
39
+ commit: "refactor-10-slice-2",
40
+ },
41
+ uxIntent: { uiVisible: false },
42
+ agentMetadata: { readOrMutate: "read", boundedScope: "selection", auditCategory: "ui-surface-read" },
43
+ stateClass: "C-local",
44
+ persistsTo: "none",
45
+ rwdReference: "§UI API § ui.surface.getSelection",
46
+ };
47
+
48
+ export const setSelectionMetadata: ApiV3FnMetadata = {
49
+ name: "ui.surface.setSelection",
50
+ status: "live-with-adapter",
51
+ sourceLayer: "presentation",
52
+ liveEvidence: {
53
+ runnerTest: "test/api/v3/ui/selection-bidirectional.test.ts",
54
+ commit: "refactor-10-slice-2",
55
+ },
56
+ uxIntent: { uiVisible: true, expectsUxResponse: "selection-change", expectedDelta: "selection moves to requested range" },
57
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "selection", auditCategory: "ui-surface-set-selection" },
58
+ stateClass: "C-local",
59
+ persistsTo: "none",
60
+ // Dispatch-form bidirectional channel (U3): request flows UI → runtime
61
+ // via controller.dispatchSelection; the runtime's follow-up
62
+ // `selection.changed` emission is observed by the viewport / surface
63
+ // event stream, not by this call. No subscriptionShape — single-shot.
64
+ bidirectional: true,
65
+ rwdReference: "§UI API § ui.surface.setSelection. Adapter delegates to UiController.dispatchSelection; throws when the active binding has no dispatch hook.",
66
+ };
67
+
68
+ export const getViewportMetadata: ApiV3FnMetadata = {
69
+ name: "ui.surface.getViewport",
70
+ status: "partial",
71
+ sourceLayer: "presentation",
72
+ liveEvidence: {
73
+ runnerTest: "test/api/v3/ui/selection-bidirectional.test.ts",
74
+ commit: "refactor-10-slice-2",
75
+ },
76
+ mockShape: {
77
+ deterministic: true,
78
+ seededFrom: "fixed",
79
+ shapeDescription: "Default zeroed ViewportState (width/height/scroll=0, zoom/dpr=1) when no controller.getViewport hook is provided. Slice 3 promotes to live via GeometryFacet.getViewport().",
80
+ carriesMockFlag: true,
81
+ },
82
+ uxIntent: { uiVisible: false },
83
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-surface-read" },
84
+ stateClass: "C-local",
85
+ persistsTo: "none",
86
+ rwdReference: "§UI API § ui.surface.getViewport",
87
+ };
88
+
89
+ export const scrollToMetadata: ApiV3FnMetadata = {
90
+ name: "ui.surface.scrollTo",
91
+ status: "live-with-adapter",
92
+ sourceLayer: "presentation",
93
+ liveEvidence: {
94
+ runnerTest: "test/api/v3/ui/scroll-to.test.ts",
95
+ commit: "refactor-10-slice-2",
96
+ },
97
+ uxIntent: { uiVisible: true, expectsUxResponse: "surface-refresh", expectedDelta: "viewport scrolls to target" },
98
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-surface-scroll" },
99
+ stateClass: "C-local",
100
+ persistsTo: "none",
101
+ // Dispatch-form bidirectional channel (U3): request flows UI → runtime
102
+ // via controller.dispatchScroll; viewport settles via rAF + the
103
+ // `ux.response.ui.surface.scrollTo` event announces the request.
104
+ bidirectional: true,
105
+ rwdReference: "§UI API § ui.surface.scrollTo. Adapter delegates to UiController.dispatchScroll. Slice 3 adds geometry-backed target resolution for block/scope/comment/revision kinds prior to dispatch.",
106
+ };
107
+
108
+ export function createSurfaceFamily(ctx: UiApiContext) {
109
+ return {
110
+ getSelection(): SelectionSnapshot | null {
111
+ // Per DS §8.3 (runtime owns truth), trust the handle contract.
112
+ // A thrown `getRenderSnapshot` is a runtime regression, not a
113
+ // pre-mount state — silent null would let the UI claim "no
114
+ // selection" while substrate is broken. Pre-mount handles use
115
+ // the inert-facet pattern (`loading-runtime-bridge.ts`).
116
+ const snapshot = ctx.handle.getRenderSnapshot();
117
+ return snapshot?.selection ?? null;
118
+ },
119
+ setSelection(range: SelectionRangeInput): void {
120
+ const controller = ctx.binding?.controller;
121
+ if (!controller) {
122
+ throw new Error(
123
+ "ui.surface.setSelection: no controller bound — call ui.session.bind(controller) first",
124
+ );
125
+ }
126
+ if (!controller.dispatchSelection) {
127
+ throw new Error(
128
+ `ui.surface.setSelection: controller of kind "${controller.kind}" did not provide a dispatchSelection hook`,
129
+ );
130
+ }
131
+ controller.dispatchSelection(range);
132
+ emitUxResponse(ctx.handle, {
133
+ apiFn: setSelectionMetadata.name,
134
+ intent: setSelectionMetadata.uxIntent.expectedDelta ?? "",
135
+ mockOrLive: "live-with-adapter",
136
+ uiVisible: true,
137
+ expectedDelta: setSelectionMetadata.uxIntent.expectedDelta,
138
+ actualDelta: { kind: "selection-change", payload: { anchor: range.anchor, head: range.head } },
139
+ });
140
+ },
141
+ getViewport(): ViewportState {
142
+ // Shared composition with `ui.viewport.get` (Slice 11) — reads
143
+ // scroll/dpr/zoom from handle.geometry, width/height from the
144
+ // bound controller.
145
+ return readComposedViewport(ctx);
146
+ },
147
+ async scrollTo(target: ScrollTarget): Promise<void> {
148
+ const controller = ctx.binding?.controller;
149
+ if (!controller) {
150
+ throw new Error(
151
+ "ui.surface.scrollTo: no controller bound — call ui.session.bind(controller) first",
152
+ );
153
+ }
154
+ if (!controller.dispatchScroll) {
155
+ throw new Error(
156
+ `ui.surface.scrollTo: controller of kind "${controller.kind}" did not provide a dispatchScroll hook`,
157
+ );
158
+ }
159
+ await controller.dispatchScroll(target);
160
+ emitUxResponse(ctx.handle, {
161
+ apiFn: scrollToMetadata.name,
162
+ intent: scrollToMetadata.uxIntent.expectedDelta ?? "",
163
+ mockOrLive: "live-with-adapter",
164
+ uiVisible: true,
165
+ expectedDelta: scrollToMetadata.uxIntent.expectedDelta,
166
+ actualDelta: { kind: "surface-refresh", payload: { kind: target.kind, value: target.kind === "scope" || target.kind === "comment" || target.kind === "revision" || target.kind === "block" ? target.value : undefined } },
167
+ });
168
+ },
169
+ };
170
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * @endStateApi v3 — `ui.viewport` family (layer 10).
3
+ *
4
+ * Slice 11 wiring (2026-04-22 — refactor/10 adversarial closure):
5
+ * - get — live. Reads scroll / dpr / zoom directly from
6
+ * `handle.geometry.getViewport()` (available since
7
+ * refactor/05 adversarial closure). Width + height
8
+ * still come from the controller hook when present,
9
+ * because those are DOM-measured container dims and
10
+ * not in the geometry primitive. When neither geometry
11
+ * nor controller is available, returns a mock-flagged
12
+ * zeroed default.
13
+ * - subscribe — live-with-adapter. Delegates to the bound controller's
14
+ * `subscribeViewport` hook. Bind-side is responsible for
15
+ * rAF coalescing (U7). Throws when no controller is
16
+ * bound or the controller lacks the hook — consumers
17
+ * learn about misconfiguration rather than silently
18
+ * missing events.
19
+ *
20
+ * Contract U7 — subscribers receive runtime-time events plus coalesced
21
+ * rAF ticks for viewport. Never on raw PM transaction emission.
22
+ */
23
+
24
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
25
+ import type {
26
+ ViewportState,
27
+ UiListener,
28
+ UiUnsubscribe,
29
+ WorkflowMarkupMode,
30
+ } from "./_types.ts";
31
+ import type { UiApiContext } from "./_context.ts";
32
+ import { readComposedViewport } from "./_context.ts";
33
+ import { emitUxResponse } from "../_ux-response.ts";
34
+
35
+ export const getMetadata: ApiV3FnMetadata = {
36
+ name: "ui.viewport.get",
37
+ status: "live-with-adapter",
38
+ sourceLayer: "presentation",
39
+ liveEvidence: {
40
+ runnerTest: "test/api/v3/ui/viewport-subscribe.test.ts",
41
+ commit: "refactor-10-slice-11",
42
+ },
43
+ mockShape: {
44
+ deterministic: true,
45
+ seededFrom: "fixed",
46
+ shapeDescription: "Default zeroed ViewportState (width/height=0, scroll=0, zoom/dpr=1) when neither handle.geometry nor a controller.getViewport hook is available (headless pre-paint). Slice 11 sources scroll/dpr/zoom from handle.geometry.getViewport() and width/height from the controller hook when present.",
47
+ carriesMockFlag: true,
48
+ },
49
+ uxIntent: { uiVisible: false },
50
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-read" },
51
+ stateClass: "C-local",
52
+ persistsTo: "none",
53
+ rwdReference: "§UI API § ui.viewport.get. Reads scroll/dpr/zoom through handle.geometry.getViewport() directly (refactor/05 geometry facet). width/height come from controller.getViewport() when bound, since container-dim measurement is DOM-local and not in the geometry primitive.",
54
+ };
55
+
56
+ export const subscribeMetadata: ApiV3FnMetadata = {
57
+ name: "ui.viewport.subscribe",
58
+ status: "live-with-adapter",
59
+ sourceLayer: "presentation",
60
+ liveEvidence: {
61
+ runnerTest: "test/api/v3/ui/viewport-subscribe.test.ts",
62
+ commit: "refactor-07-slice-2",
63
+ },
64
+ // Stream-form bidirectional channel (U3 + U7). `uiVisible: true`
65
+ // because the subscribe call IS observable UX — registering a
66
+ // listener affects what the mounted surface paints on the next
67
+ // viewport change. The single `surface-refresh` ack fires on the
68
+ // establishing call; the rAF-coalesced stream is announced via
69
+ // `subscriptionShape`.
70
+ uxIntent: {
71
+ uiVisible: true,
72
+ expectsUxResponse: "surface-refresh",
73
+ expectedDelta: "viewport subscriber attached; future scroll/zoom/dpr changes propagate through the listener",
74
+ },
75
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-subscribe" },
76
+ stateClass: "C-local",
77
+ persistsTo: "none",
78
+ bidirectional: true,
79
+ subscriptionShape: {
80
+ eventType: "ui.viewport.changed",
81
+ payloadType: "ViewportState",
82
+ coalescing: "raf",
83
+ },
84
+ rwdReference: "§UI API § ui.viewport.subscribe. Adapter delegates to UiController.subscribeViewport; throws when the active binding has no hook. Subscribe call emits one `ux.response.ui.viewport.subscribe` acknowledgement; per-tick ViewportState deliveries flow through the listener (rAF-coalesced, U7).",
85
+ };
86
+
87
+ // ----- X5 markup-mode metadata (state-classes cross-cutting Slice X5) -----
88
+
89
+ /**
90
+ * Intrinsic default when no policy has been authored AND no local
91
+ * preference is set. "all" mirrors the shipped shell default: show
92
+ * insertions + deletions + suggestions so a user who hasn't touched
93
+ * the control sees changes.
94
+ */
95
+ const INTRINSIC_DEFAULT_MARKUP_MODE: WorkflowMarkupMode = "all";
96
+
97
+ export const getEffectiveMarkupModeMetadata: ApiV3FnMetadata = {
98
+ name: "ui.viewport.getEffectiveMarkupMode",
99
+ status: "live",
100
+ sourceLayer: "presentation",
101
+ liveEvidence: {
102
+ runnerTest: "test/api/v3/ui/markup-mode-composition.test.ts",
103
+ commit: "refactor-10-slice-x5",
104
+ },
105
+ uxIntent: { uiVisible: false },
106
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-read" },
107
+ stateClass: "C-local",
108
+ persistsTo: "none",
109
+ rwdReference: "§UI API § ui.viewport.getEffectiveMarkupMode (X5). Composes class-A policy (handle.getMarkupModePolicy) with class-C local preference. enforcement 'always' hard-overrides the preference; 'authored-default' cedes to local preference.",
110
+ };
111
+
112
+ export const setLocalMarkupModeMetadata: ApiV3FnMetadata = {
113
+ name: "ui.viewport.setLocalMarkupMode",
114
+ status: "live",
115
+ sourceLayer: "presentation",
116
+ liveEvidence: {
117
+ runnerTest: "test/api/v3/ui/markup-mode-composition.test.ts",
118
+ commit: "refactor-10-slice-x5",
119
+ },
120
+ uxIntent: {
121
+ uiVisible: true,
122
+ expectsUxResponse: "surface-refresh",
123
+ expectedDelta: "markup-mode local preference set; subscribers receive the new composed mode",
124
+ },
125
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-viewport-set-markup-mode" },
126
+ stateClass: "C-local",
127
+ persistsTo: "none",
128
+ rwdReference: "§UI API § ui.viewport.setLocalMarkupMode (X5). Class-C write.",
129
+ };
130
+
131
+ export const resetLocalMarkupModeMetadata: ApiV3FnMetadata = {
132
+ name: "ui.viewport.resetLocalMarkupMode",
133
+ status: "live",
134
+ sourceLayer: "presentation",
135
+ liveEvidence: {
136
+ runnerTest: "test/api/v3/ui/markup-mode-composition.test.ts",
137
+ commit: "refactor-10-slice-x5",
138
+ },
139
+ uxIntent: {
140
+ uiVisible: true,
141
+ expectsUxResponse: "surface-refresh",
142
+ expectedDelta: "markup-mode local preference cleared; subsequent reads fall back to policy",
143
+ },
144
+ agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-viewport-reset-markup-mode" },
145
+ stateClass: "C-local",
146
+ persistsTo: "none",
147
+ rwdReference: "§UI API § ui.viewport.resetLocalMarkupMode (X5).",
148
+ };
149
+
150
+ export const subscribeEffectiveMarkupModeMetadata: ApiV3FnMetadata = {
151
+ name: "ui.viewport.subscribeEffectiveMarkupMode",
152
+ status: "live",
153
+ sourceLayer: "presentation",
154
+ liveEvidence: {
155
+ runnerTest: "test/api/v3/ui/markup-mode-composition.test.ts",
156
+ commit: "refactor-10-slice-x5",
157
+ },
158
+ uxIntent: {
159
+ uiVisible: true,
160
+ expectsUxResponse: "surface-refresh",
161
+ expectedDelta: "markup-mode subscriber attached; future local-pref changes + L06 policy changes propagate through the listener",
162
+ },
163
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-subscribe" },
164
+ stateClass: "C-local",
165
+ persistsTo: "none",
166
+ bidirectional: true,
167
+ subscriptionShape: {
168
+ eventType: "ui.viewport.markup_mode_changed",
169
+ payloadType: "WorkflowMarkupMode",
170
+ coalescing: "microtask",
171
+ },
172
+ rwdReference: "§UI API § ui.viewport.subscribeEffectiveMarkupMode (X5). Fires on local-pref mutations AND on L06 class-A policy changes (chained through handle.subscribeMarkupModePolicy).",
173
+ };
174
+
175
+ export function createViewportFamily(ctx: UiApiContext) {
176
+ // Closure-local class-C markup-mode state (X5). Per-createUiApi-
177
+ // instance — two instances over the same handle hold independent
178
+ // preferences (same isolation pattern as U9 + rail-state).
179
+ let localMarkupMode: WorkflowMarkupMode | null = null;
180
+ const markupModeSubscribers = new Set<UiListener<WorkflowMarkupMode>>();
181
+
182
+ function readEffectiveMarkupMode(): WorkflowMarkupMode {
183
+ const policy = ctx.handle.getMarkupModePolicy?.() ?? null;
184
+ // enforcement "always" hard-overrides local preference.
185
+ if (policy?.enforcement === "always") {
186
+ return policy.mode;
187
+ }
188
+ // authored-default / no policy: local preference wins if expressed.
189
+ if (localMarkupMode !== null) {
190
+ return localMarkupMode;
191
+ }
192
+ // No preference → policy.mode (authored default) or intrinsic "all".
193
+ return policy?.mode ?? INTRINSIC_DEFAULT_MARKUP_MODE;
194
+ }
195
+
196
+ function notifyMarkupModeSubscribers(): void {
197
+ if (markupModeSubscribers.size === 0) return;
198
+ const mode = readEffectiveMarkupMode();
199
+ for (const listener of markupModeSubscribers) {
200
+ try {
201
+ listener(mode);
202
+ } catch {
203
+ // Isolate listener errors.
204
+ }
205
+ }
206
+ }
207
+
208
+ // Chain L06 policy changes into our subscribers so consumers get
209
+ // notified on class-A authoring (same pattern as U9 X1⇄X3).
210
+ // TODO(ui.dispose): retain the unsubscribe if a family-level dispose
211
+ // hook lands. The family instance lives for the lifetime of the
212
+ // handle today.
213
+ ctx.handle.subscribeMarkupModePolicy?.(() => {
214
+ notifyMarkupModeSubscribers();
215
+ });
216
+
217
+ return {
218
+ // Slice 11 — composes scroll/dpr/zoom from `handle.geometry` with
219
+ // width/height from the bound controller (see
220
+ // `readComposedViewport` in `_context.ts` for the shared
221
+ // implementation).
222
+ get(): ViewportState {
223
+ return readComposedViewport(ctx);
224
+ },
225
+ subscribe(listener: UiListener<ViewportState>): UiUnsubscribe {
226
+ const controller = ctx.binding?.controller;
227
+ if (!controller) {
228
+ throw new Error(
229
+ "ui.viewport.subscribe: no controller bound — call ui.session.bind(controller) first",
230
+ );
231
+ }
232
+ if (!controller.subscribeViewport) {
233
+ throw new Error(
234
+ `ui.viewport.subscribe: controller of kind "${controller.kind}" did not provide a subscribeViewport hook`,
235
+ );
236
+ }
237
+ const unsubscribe = controller.subscribeViewport(listener);
238
+ // Stream-form bidirectional ack: one UxResponse per subscribe call
239
+ // announcing "channel established". Per-tick ViewportState updates
240
+ // flow through the listener, not this event stream.
241
+ emitUxResponse(ctx.handle, {
242
+ apiFn: subscribeMetadata.name,
243
+ intent: subscribeMetadata.uxIntent.expectedDelta ?? "",
244
+ mockOrLive: "live-with-adapter",
245
+ uiVisible: true,
246
+ expectedDelta: subscribeMetadata.uxIntent.expectedDelta,
247
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.viewport" } },
248
+ });
249
+ return unsubscribe;
250
+ },
251
+
252
+ // ----- X5 markup-mode (state-classes cross-cutting Slice X5) -----
253
+
254
+ getEffectiveMarkupMode(): WorkflowMarkupMode {
255
+ return readEffectiveMarkupMode();
256
+ },
257
+
258
+ setLocalMarkupMode(mode: WorkflowMarkupMode): void {
259
+ if (localMarkupMode === mode) return; // idempotent.
260
+ localMarkupMode = mode;
261
+ emitUxResponse(ctx.handle, {
262
+ apiFn: setLocalMarkupModeMetadata.name,
263
+ intent: setLocalMarkupModeMetadata.uxIntent.expectedDelta ?? "",
264
+ mockOrLive: "live",
265
+ uiVisible: true,
266
+ expectedDelta: setLocalMarkupModeMetadata.uxIntent.expectedDelta,
267
+ actualDelta: { kind: "surface-refresh", payload: { markupMode: mode } },
268
+ });
269
+ notifyMarkupModeSubscribers();
270
+ },
271
+
272
+ resetLocalMarkupMode(): void {
273
+ if (localMarkupMode === null) return; // idempotent.
274
+ localMarkupMode = null;
275
+ emitUxResponse(ctx.handle, {
276
+ apiFn: resetLocalMarkupModeMetadata.name,
277
+ intent: resetLocalMarkupModeMetadata.uxIntent.expectedDelta ?? "",
278
+ mockOrLive: "live",
279
+ uiVisible: true,
280
+ expectedDelta: resetLocalMarkupModeMetadata.uxIntent.expectedDelta,
281
+ actualDelta: { kind: "surface-refresh", payload: { markupMode: "reset" } },
282
+ });
283
+ notifyMarkupModeSubscribers();
284
+ },
285
+
286
+ subscribeEffectiveMarkupMode(
287
+ listener: UiListener<WorkflowMarkupMode>,
288
+ ): UiUnsubscribe {
289
+ markupModeSubscribers.add(listener);
290
+ emitUxResponse(ctx.handle, {
291
+ apiFn: subscribeEffectiveMarkupModeMetadata.name,
292
+ intent: subscribeEffectiveMarkupModeMetadata.uxIntent.expectedDelta ?? "",
293
+ mockOrLive: "live",
294
+ uiVisible: true,
295
+ expectedDelta: subscribeEffectiveMarkupModeMetadata.uxIntent.expectedDelta,
296
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.viewport.markupMode" } },
297
+ });
298
+ return () => {
299
+ markupModeSubscribers.delete(listener);
300
+ };
301
+ },
302
+ };
303
+ }
@@ -21,6 +21,27 @@ import {
21
21
  type MappingStep,
22
22
  type TransactionMapping,
23
23
  } from "../selection/mapping.ts";
24
+ import {
25
+ toInternalAnchorProjection,
26
+ toPublicAnchorProjection,
27
+ } from "../selection/anchor-conversion.ts";
28
+ import type { EditorAnchorProjection as PublicEditorAnchorProjection } from "../../api/public-types.ts";
29
+
30
+ /**
31
+ * Apply a transaction mapping to a record-attached (public flat) anchor.
32
+ * Record-attached anchors (on `EditorWarning` / `CompatibilityFeatureEntry`)
33
+ * carry the flat public shape; `mapAnchor` operates on the internal nested
34
+ * shape. Convert flat → nested → map → flat so the mapping composition
35
+ * logic stays in `mapping.ts` while records stay flat.
36
+ *
37
+ * See `docs/plans/cross-layer-coord-02.md §8`.
38
+ */
39
+ function mapRecordAnchor(
40
+ anchor: PublicEditorAnchorProjection,
41
+ mapping: TransactionMapping,
42
+ ): PublicEditorAnchorProjection {
43
+ return toPublicAnchorProjection(mapAnchor(toInternalAnchorProjection(anchor), mapping));
44
+ }
24
45
  import {
25
46
  createAcceptRevisionCommand,
26
47
  createRejectRevisionCommand,
@@ -37,6 +58,7 @@ import {
37
58
  } from "./text-commands.ts";
38
59
  import type {
39
60
  BlockNode,
61
+ MutableCanonicalDocument,
40
62
  RevisionRecord as CanonicalRevisionRecord,
41
63
  } from "../../model/canonical-document.ts";
42
64
  import { remapCommentThreads } from "../../review/store/comment-remapping.ts";
@@ -95,7 +117,7 @@ import { insertPageBreak, insertTable } from "./text-commands.ts";
95
117
  // is a **diamond** dependency (A → B, A → C, B → C), NOT a module-level
96
118
  // cycle — `text-commands.ts` does not re-enter `core/commands/index.ts`
97
119
  // or `runtime/edit-ops/`. Verified 2026-04-20 by static DFS; see
98
- // docs/plans/architecture-lane.md §F3. Keep it this way — don't add
120
+ // CLAUDE.md (lane status table) §F3. Keep it this way — don't add
99
121
  // imports from `runtime/` that route through this file, as that could
100
122
  // introduce a real cycle.
101
123
  import { editLayer } from "../../runtime/edit-ops/index.ts";
@@ -527,7 +549,7 @@ export function executeEditorCommand(
527
549
  entry.affectedAnchor
528
550
  ? {
529
551
  ...entry,
530
- affectedAnchor: mapAnchor(entry.affectedAnchor, mapping),
552
+ affectedAnchor: mapRecordAnchor(entry.affectedAnchor, mapping),
531
553
  }
532
554
  : entry,
533
555
  ),
@@ -574,7 +596,7 @@ export function executeEditorCommand(
574
596
  entry.affectedAnchor
575
597
  ? {
576
598
  ...entry,
577
- affectedAnchor: mapAnchor(entry.affectedAnchor, mapping),
599
+ affectedAnchor: mapRecordAnchor(entry.affectedAnchor, mapping),
578
600
  }
579
601
  : entry,
580
602
  ),
@@ -1363,7 +1385,7 @@ export function applyDocumentPatch(
1363
1385
  contentChildren?: ContentChildrenPatch,
1364
1386
  ): CanonicalDocumentEnvelope {
1365
1387
  const nextContent = resolvePatchedContent(base, patch, contentChildren);
1366
- const merged: CanonicalDocumentEnvelope = { ...base, ...patch };
1388
+ const merged: MutableCanonicalDocument = { ...base, ...patch };
1367
1389
  if (nextContent) {
1368
1390
  merged.content = nextContent;
1369
1391
  }
@@ -1445,7 +1467,7 @@ function buildDocumentReplaceTransaction(
1445
1467
  entry.affectedAnchor
1446
1468
  ? {
1447
1469
  ...entry,
1448
- affectedAnchor: mapAnchor(entry.affectedAnchor, mapping),
1470
+ affectedAnchor: mapRecordAnchor(entry.affectedAnchor, mapping),
1449
1471
  }
1450
1472
  : entry,
1451
1473
  ),
@@ -2094,7 +2116,7 @@ function mapWarningsThroughMapping(
2094
2116
  warning.affectedAnchor
2095
2117
  ? {
2096
2118
  ...warning,
2097
- affectedAnchor: mapAnchor(warning.affectedAnchor, mapping),
2119
+ affectedAnchor: mapRecordAnchor(warning.affectedAnchor, mapping),
2098
2120
  }
2099
2121
  : warning,
2100
2122
  );
@@ -2,6 +2,7 @@ import type {
2
2
  AbstractNumberingDefinition,
3
3
  BlockNode,
4
4
  CanonicalDocument as CanonicalDocumentEnvelope,
5
+ MutableCanonicalDocument,
5
6
  NumberingCatalog,
6
7
  NumberingInstance,
7
8
  ParagraphNode,
@@ -498,14 +499,14 @@ function ensureNumberingCatalog(value: NumberingCatalog): NumberingCatalog {
498
499
  function cloneEnvelope(
499
500
  document: CanonicalDocumentEnvelope,
500
501
  timestamp: string,
501
- ): CanonicalDocumentEnvelope {
502
+ ): MutableCanonicalDocument {
502
503
  return {
503
504
  ...structuredClone(document),
504
505
  updatedAt: timestamp,
505
506
  };
506
507
  }
507
508
 
508
- function captureEditableParagraphs(document: CanonicalDocumentEnvelope): ParagraphNode[] {
509
+ function captureEditableParagraphs(document: MutableCanonicalDocument): ParagraphNode[] {
509
510
  if (isDocumentRoot(document.content)) {
510
511
  const paragraphs: ParagraphNode[] = [];
511
512
  collectEditableParagraphs(document.content.children as BlockNode[], paragraphs);