@beyondwork/docx-react-component 1.0.66 → 1.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/README.md +75 -931
  2. package/package.json +26 -27
  3. package/src/api/anchor-conversion.ts +43 -0
  4. package/src/api/editor-state-types.ts +2 -1
  5. package/src/api/public-types.ts +504 -101
  6. package/src/api/session-state.ts +4 -0
  7. package/src/api/v3/README.md +91 -0
  8. package/src/api/v3/_create.ts +146 -0
  9. package/src/api/v3/_layer-metadata.ts +362 -0
  10. package/src/api/v3/_mocks.ts +84 -0
  11. package/src/api/v3/_runtime-handle.ts +162 -0
  12. package/src/api/v3/_ux-response.ts +73 -0
  13. package/src/api/v3/ai/_metadata-audit.ts +225 -0
  14. package/src/api/v3/ai/attach.ts +235 -0
  15. package/src/api/v3/ai/bundle.ts +132 -0
  16. package/src/api/v3/ai/explain.ts +144 -0
  17. package/src/api/v3/ai/export.ts +54 -0
  18. package/src/api/v3/ai/inspect.ts +118 -0
  19. package/src/api/v3/ai/policy.ts +77 -0
  20. package/src/api/v3/ai/replacement.ts +341 -0
  21. package/src/api/v3/ai/resolve.ts +133 -0
  22. package/src/api/v3/index.ts +79 -0
  23. package/src/api/v3/runtime/chart.ts +310 -0
  24. package/src/api/v3/runtime/clipboard.ts +81 -0
  25. package/src/api/v3/runtime/collab.ts +331 -0
  26. package/src/api/v3/runtime/content.ts +236 -0
  27. package/src/api/v3/runtime/document.ts +282 -0
  28. package/src/api/v3/runtime/formatting.ts +186 -0
  29. package/src/api/v3/runtime/geometry.ts +349 -0
  30. package/src/api/v3/runtime/layout.ts +108 -0
  31. package/src/api/v3/runtime/review.ts +129 -0
  32. package/src/api/v3/runtime/search.ts +74 -0
  33. package/src/api/v3/runtime/table.ts +63 -0
  34. package/src/api/v3/runtime/workflow.ts +434 -0
  35. package/src/api/v3/ui/_context.ts +86 -0
  36. package/src/api/v3/ui/_create.ts +65 -0
  37. package/src/api/v3/ui/_types.ts +520 -0
  38. package/src/api/v3/ui/chrome-composition.ts +342 -0
  39. package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
  40. package/src/api/v3/ui/chrome.ts +476 -0
  41. package/src/api/v3/ui/debug.ts +124 -0
  42. package/src/api/v3/ui/index.ts +64 -0
  43. package/src/api/v3/ui/overlays-visibility.ts +170 -0
  44. package/src/api/v3/ui/overlays.ts +427 -0
  45. package/src/api/v3/ui/scope.ts +71 -0
  46. package/src/api/v3/ui/session.ts +100 -0
  47. package/src/api/v3/ui/surface.ts +170 -0
  48. package/src/api/v3/ui/viewport.ts +303 -0
  49. package/src/core/commands/index.ts +28 -6
  50. package/src/core/commands/list-commands.ts +3 -2
  51. package/src/core/commands/section-layout-commands.ts +9 -8
  52. package/src/core/schema/text-schema.ts +16 -0
  53. package/src/core/selection/mapping.ts +33 -72
  54. package/src/core/state/editor-state.ts +96 -189
  55. package/src/index.ts +23 -4
  56. package/src/io/chart-preview-resolver.ts +1 -1
  57. package/src/io/docx-session.ts +36 -4795
  58. package/src/io/export/build-app-properties-xml.ts +1 -1
  59. package/src/io/export/serialize-comments.ts +1 -1
  60. package/src/io/export/serialize-headers-footers.ts +6 -1
  61. package/src/io/export/serialize-main-document.ts +45 -0
  62. package/src/io/export/serialize-run-formatting.ts +17 -2
  63. package/src/io/export/twip.ts +1 -1
  64. package/src/io/normalize/normalize-text.ts +27 -20
  65. package/src/io/ooxml/chart/parse-series.ts +1 -1
  66. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  67. package/src/io/ooxml/chart/types.ts +1 -1
  68. package/src/io/ooxml/classify-embedding.ts +83 -33
  69. package/src/io/ooxml/parse-fill.ts +1 -1
  70. package/src/io/ooxml/parse-main-document.ts +71 -1
  71. package/src/io/ooxml/parse-object.ts +14 -10
  72. package/src/io/ooxml/parse-run-formatting.ts +47 -1
  73. package/src/io/ooxml/property-grab-bag.ts +2 -2
  74. package/src/io/ooxml/units.ts +11 -0
  75. package/src/io/ooxml/workflow-payload.ts +282 -7
  76. package/src/model/anchor.ts +85 -0
  77. package/src/model/canonical-document.ts +351 -15
  78. package/src/model/chart-types.ts +1 -1
  79. package/src/model/layout/index.ts +83 -0
  80. package/src/model/layout/page-graph-types.ts +181 -0
  81. package/src/model/layout/page-layout-snapshot.ts +105 -0
  82. package/src/model/layout/resolved-layout-types.ts +47 -0
  83. package/src/model/layout/runtime-page-graph-types.ts +102 -0
  84. package/src/model/paragraph-scope-ids.ts +72 -0
  85. package/src/model/review/comment-types.ts +112 -0
  86. package/src/model/review/index.ts +2 -0
  87. package/src/model/review/revision-types.ts +215 -0
  88. package/src/model/snapshot.ts +32 -0
  89. package/src/review/store/comment-store.ts +21 -47
  90. package/src/review/store/revision-types.ts +40 -198
  91. package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
  92. package/src/runtime/collab/runtime-collab-sync.ts +13 -3
  93. package/src/runtime/collab-session.ts +1 -1
  94. package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
  95. package/src/runtime/debug/event-ring-buffer.ts +64 -0
  96. package/src/runtime/debug/probability-sampler.ts +18 -0
  97. package/src/runtime/debug/runtime-debug-facet.ts +67 -0
  98. package/src/runtime/debug/stage-tokens.ts +31 -0
  99. package/src/runtime/debug/telemetry-bus.ts +271 -0
  100. package/src/runtime/debug/types.ts +275 -0
  101. package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
  102. package/src/runtime/document-layout.ts +8 -6
  103. package/src/runtime/document-runtime.ts +843 -1141
  104. package/src/runtime/document-search.ts +1 -1
  105. package/src/runtime/edit-ops/index.ts +1 -1
  106. package/src/runtime/external-send-runtime.ts +1 -1
  107. package/src/runtime/formatting/document-lookup.ts +235 -0
  108. package/src/runtime/formatting/field/registry.ts +41 -0
  109. package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
  110. package/src/runtime/formatting/font-resolution.ts +83 -0
  111. package/src/runtime/formatting/formatting-context.ts +903 -0
  112. package/src/runtime/formatting/formatting-types.ts +157 -0
  113. package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
  114. package/src/runtime/formatting/index.ts +125 -0
  115. package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
  116. package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
  117. package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
  118. package/src/runtime/formatting/projector.ts +75 -0
  119. package/src/runtime/formatting/resolve-effective.ts +407 -0
  120. package/src/runtime/formatting/revision-display.ts +105 -0
  121. package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
  122. package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
  123. package/src/runtime/formatting/telemetry-bridge.ts +106 -0
  124. package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
  125. package/src/runtime/geometry/caret-geometry.ts +164 -0
  126. package/src/runtime/geometry/geometry-facet.ts +364 -0
  127. package/src/runtime/geometry/geometry-types.ts +256 -0
  128. package/src/runtime/geometry/hit-test.ts +125 -0
  129. package/src/runtime/geometry/index.ts +71 -0
  130. package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
  131. package/src/runtime/geometry/invalidation.ts +35 -0
  132. package/src/runtime/geometry/object-handles.ts +77 -0
  133. package/src/runtime/geometry/overlay-rects.ts +85 -0
  134. package/src/runtime/geometry/project-anchors.ts +100 -0
  135. package/src/runtime/geometry/project-fragments.ts +216 -0
  136. package/src/runtime/geometry/projector.ts +129 -0
  137. package/src/runtime/geometry/replacement-envelope.ts +130 -0
  138. package/src/runtime/geometry/viewport.ts +218 -0
  139. package/src/runtime/layout/compat-input-ledger.ts +211 -0
  140. package/src/runtime/layout/index.ts +6 -1
  141. package/src/runtime/layout/inert-layout-facet.ts +12 -7
  142. package/src/runtime/layout/layout-engine-instance.ts +189 -11
  143. package/src/runtime/layout/layout-engine-version.ts +450 -1
  144. package/src/runtime/layout/layout-facet-types.ts +60 -0
  145. package/src/runtime/layout/layout-measurement-provider.ts +13 -0
  146. package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
  147. package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
  148. package/src/runtime/layout/page-graph.ts +62 -209
  149. package/src/runtime/layout/page-story-resolver.ts +7 -12
  150. package/src/runtime/layout/paginated-layout-engine.ts +186 -11
  151. package/src/runtime/layout/project-block-fragments.ts +11 -0
  152. package/src/runtime/layout/projector.ts +90 -0
  153. package/src/runtime/layout/public-facet.ts +187 -442
  154. package/src/runtime/layout/resolved-formatting-state.ts +158 -26
  155. package/src/runtime/layout/table-render-plan.ts +1 -1
  156. package/src/runtime/prerender/cache-envelope.ts +6 -1
  157. package/src/runtime/prerender/prerender-document.ts +18 -23
  158. package/src/runtime/render/decoration-resolver.ts +1 -1
  159. package/src/runtime/render/render-frame-types.ts +20 -0
  160. package/src/runtime/render/render-kernel.ts +94 -25
  161. package/src/runtime/scopes/_formatting-seam.ts +262 -0
  162. package/src/runtime/scopes/_scope-dependencies.ts +49 -0
  163. package/src/runtime/scopes/action-validation.ts +356 -0
  164. package/src/runtime/scopes/attach-explanation.ts +102 -0
  165. package/src/runtime/scopes/audit-bundle.ts +71 -0
  166. package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
  167. package/src/runtime/scopes/compile-scope.ts +262 -0
  168. package/src/runtime/scopes/compiler-service.ts +431 -0
  169. package/src/runtime/scopes/create-issue.ts +107 -0
  170. package/src/runtime/scopes/enumerate-scopes.ts +543 -0
  171. package/src/runtime/scopes/evidence.ts +233 -0
  172. package/src/runtime/scopes/index.ts +150 -0
  173. package/src/runtime/scopes/position-map.ts +214 -0
  174. package/src/runtime/scopes/preservation-boundary.ts +91 -0
  175. package/src/runtime/scopes/projector.ts +49 -0
  176. package/src/runtime/scopes/replaceability.ts +87 -0
  177. package/src/runtime/scopes/replacement/apply.ts +228 -0
  178. package/src/runtime/scopes/replacement/compile.ts +59 -0
  179. package/src/runtime/scopes/replacement/propose.ts +42 -0
  180. package/src/runtime/scopes/resolve-reference.ts +347 -0
  181. package/src/runtime/scopes/review-bundle.ts +141 -0
  182. package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
  183. package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
  184. package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
  185. package/src/runtime/scopes/scope-kinds/field.ts +65 -0
  186. package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
  187. package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
  188. package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
  189. package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
  190. package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
  191. package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
  192. package/src/runtime/scopes/scope-kinds/table.ts +55 -0
  193. package/src/runtime/scopes/scope-range.ts +208 -0
  194. package/src/runtime/scopes/semantic-scope-types.ts +454 -0
  195. package/src/runtime/scopes/workflow-overlap.ts +92 -0
  196. package/src/runtime/selection/index.ts +1 -1
  197. package/src/runtime/structure-ops/fragment-insert.ts +1 -1
  198. package/src/runtime/structure-ops/index.ts +1 -1
  199. package/src/runtime/surface-projection.ts +232 -262
  200. package/src/runtime/units.ts +4 -2
  201. package/src/runtime/workflow/coordinator.ts +1348 -0
  202. package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
  203. package/src/runtime/workflow/index.ts +25 -0
  204. package/src/runtime/workflow/markup-mode-policy.ts +98 -0
  205. package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
  206. package/src/runtime/workflow/metadata-persistence.ts +306 -0
  207. package/src/runtime/workflow/metadata-writer.ts +123 -0
  208. package/src/runtime/workflow/overlay-store.ts +690 -0
  209. package/src/runtime/workflow/projector.ts +127 -0
  210. package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
  211. package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
  212. package/src/runtime/workflow/rail/types.ts +198 -0
  213. package/src/runtime/workflow/scope-rail-composer.ts +39 -0
  214. package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
  215. package/src/runtime/workflow/scope-writer.ts +188 -0
  216. package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
  217. package/src/runtime/workflow/visibility-policy.ts +129 -0
  218. package/src/session/_sync-legacy.ts +66 -0
  219. package/src/session/export/embedded-reconstitute.ts +104 -0
  220. package/src/session/export/export-diagnostics.ts +85 -0
  221. package/src/session/export/export-validation.ts +110 -0
  222. package/src/session/export/index.ts +34 -0
  223. package/src/session/export/preservation-reattach.ts +30 -0
  224. package/src/session/export/serialize-dispatch.ts +165 -0
  225. package/src/session/export/stateful-export-pipeline.ts +432 -0
  226. package/src/session/export/stateful-export.ts +684 -0
  227. package/src/session/import/canonical-assembly.ts +227 -0
  228. package/src/session/import/diagnostics-session.ts +54 -0
  229. package/src/session/import/embedded-discovery.ts +225 -0
  230. package/src/session/import/embedded-offload.ts +337 -0
  231. package/src/session/import/import-diagnostics.ts +69 -0
  232. package/src/session/import/loader-types.ts +313 -0
  233. package/src/session/import/loader.ts +1834 -0
  234. package/src/session/import/normalize.ts +195 -0
  235. package/src/session/import/package-parts.ts +217 -0
  236. package/src/session/import/package-read.ts +195 -0
  237. package/src/session/import/parse-orchestration.ts +105 -0
  238. package/src/session/import/part-constants.ts +70 -0
  239. package/src/session/import/part-discovery.ts +94 -0
  240. package/src/session/import/preservation-index.ts +46 -0
  241. package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
  242. package/src/session/import/review-import.ts +508 -0
  243. package/src/session/import/styles-consolidation.ts +281 -0
  244. package/src/session/import/workflow-scope-import.ts +256 -0
  245. package/src/session/index.ts +37 -0
  246. package/src/session/session-state.ts +69 -0
  247. package/src/session/session.ts +532 -0
  248. package/src/session/shared/protection.ts +228 -0
  249. package/src/session/shared/session-utils.ts +82 -0
  250. package/src/session/types.ts +499 -0
  251. package/src/shell/chart-snapshots.ts +96 -0
  252. package/src/shell/media-previews.ts +85 -0
  253. package/src/shell/overlay-anchor-bridge.ts +53 -0
  254. package/src/shell/paste-adapter.ts +23 -0
  255. package/src/shell/ref-commands.ts +1697 -0
  256. package/src/shell/ref-utilities.ts +48 -0
  257. package/src/shell/search.ts +51 -0
  258. package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
  259. package/src/shell/ui-subscriber-channels.ts +81 -0
  260. package/src/shell/use-collab-sync.ts +116 -0
  261. package/src/ui/WordReviewEditor.tsx +496 -2051
  262. package/src/ui/editor-shell-view.tsx +30 -1
  263. package/src/ui/editor-surface-controller.tsx +49 -1
  264. package/src/ui/headless/revision-decoration-model.ts +83 -0
  265. package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
  266. package/src/ui/headless/scoped-chrome-policy.ts +2 -2
  267. package/src/ui/headless/selection-tool-context.ts +1 -1
  268. package/src/ui/headless/selection-tool-resolver.ts +1 -1
  269. package/src/ui/runtime-shortcut-dispatch.ts +46 -1
  270. package/src/ui/ui-controller-factory.ts +221 -0
  271. package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
  272. package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
  273. package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
  274. package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
  275. package/src/ui-tailwind/chart/render/area.tsx +3 -3
  276. package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
  277. package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
  278. package/src/ui-tailwind/chart/render/combo.tsx +2 -2
  279. package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
  280. package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
  281. package/src/ui-tailwind/chart/render/line.tsx +3 -3
  282. package/src/ui-tailwind/chart/render/pie.tsx +6 -6
  283. package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
  284. package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
  285. package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
  286. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
  287. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
  288. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
  289. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
  290. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
  291. package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
  292. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
  293. package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
  294. package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
  295. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
  296. package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
  297. package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
  298. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
  299. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
  300. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
  301. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
  302. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
  303. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
  304. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
  305. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
  306. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
  307. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
  308. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
  309. package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
  310. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
  311. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
  312. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
  313. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
  314. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
  315. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
  316. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
  317. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
  318. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
  319. package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
  320. package/src/ui-tailwind/debug/README.md +57 -0
  321. package/src/ui-tailwind/debug/index.ts +3 -0
  322. package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
  323. package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
  324. package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
  325. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
  326. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
  327. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
  328. package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
  329. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
  330. package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
  331. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
  332. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
  333. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
  334. package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
  335. package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
  336. package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
  337. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
  338. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
  339. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
  340. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
  341. package/src/ui-tailwind/index.ts +0 -5
  342. package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
  343. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
  344. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
  345. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
  346. package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
  347. package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
  348. package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
  349. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
  350. package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
  351. package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
  352. package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
  353. package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
  354. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
  355. package/src/ui-tailwind/review-workspace/types.ts +408 -0
  356. package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
  357. package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
  358. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
  359. package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
  360. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
  361. package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
  362. package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
  363. package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
  364. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
  365. package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
  366. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
  367. package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
  368. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
  369. package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
  370. package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
  371. package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
  372. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
  373. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  374. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
  375. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
  376. package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
  377. package/src/ui-tailwind/ui-api-context.tsx +43 -0
  378. package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
  379. package/src/validation/compatibility-engine.ts +6 -6
  380. package/src/runtime/styles-cascade.ts +0 -33
  381. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
  382. /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
  383. /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
  384. /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Coord-06 §13c — resolve a *derived* paragraph-like scopeId to an
3
+ * `EditorAnchorProjection` (range anchor).
4
+ *
5
+ * Derived scopeIds are the paragraph / heading / list-item identifiers
6
+ * minted by `src/runtime/scopes/enumerate-scopes.ts` for blocks that
7
+ * aren't backed by an overlay marker. They don't live on the workflow
8
+ * overlay, so `overlayStore.scopes.find(...)` misses them — but the
9
+ * block itself exists in the canonical document and has a well-defined
10
+ * range. This resolver walks the canonical document, classifies each
11
+ * paragraph the same way the enumerator does, mints the candidate
12
+ * scopeId, and emits the range anchor on match.
13
+ *
14
+ * Shape rules:
15
+ * - `para:<paraId>` / `para:<blockIndex>`
16
+ * - `heading:<paraId>` / `heading:<level>:<blockIndex>`
17
+ * - `li:<paraId>` / `li:<instance>:<level>:<blockIndex>`
18
+ *
19
+ * Default `assoc` is `{ start: 1, end: -1 }` (greedy — same shipped
20
+ * shape as the pre-§13d scope-writer). Derived scopes don't carry a
21
+ * caller-authored `assoc` today; if a future slice exposes one on
22
+ * `attachExplanation` / `createIssue` inputs, this resolver accepts
23
+ * the override via the second argument.
24
+ *
25
+ * Non-paragraph kinds (table / table-row / table-cell / field /
26
+ * comment-thread / revision) are not in scope for §13c — CLM hot-path
27
+ * dominates paragraph / heading / list-item. Table-kind resolution is
28
+ * straightforward to add later (the scopeId grammar is `table:<index>`)
29
+ * but no consumer asks for it yet.
30
+ */
31
+
32
+ import type {
33
+ BlockNode,
34
+ DocumentRootNode,
35
+ InlineNode,
36
+ ParagraphNode,
37
+ } from "../../model/canonical-document.ts";
38
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
39
+ import type { EditorAnchorProjection } from "../../api/public-types.ts";
40
+ import {
41
+ buildParagraphScopeId,
42
+ detectParagraphKind,
43
+ } from "../../model/paragraph-scope-ids.ts";
44
+
45
+ function inlineLength(node: InlineNode): number {
46
+ switch (node.type) {
47
+ case "text":
48
+ return Array.from(node.text).length;
49
+ case "hyperlink":
50
+ case "field":
51
+ return node.children.reduce(
52
+ (total, child) => total + inlineLength(child as InlineNode),
53
+ 0,
54
+ );
55
+ case "bookmark_start":
56
+ case "bookmark_end":
57
+ case "scope_marker_start":
58
+ case "scope_marker_end":
59
+ return 0;
60
+ default:
61
+ return 1;
62
+ }
63
+ }
64
+
65
+ function paragraphLength(paragraph: ParagraphNode): number {
66
+ return paragraph.children.reduce(
67
+ (total, child) => total + inlineLength(child as InlineNode),
68
+ 0,
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Walk the canonical document and resolve a derived paragraph-like
74
+ * scopeId to its range anchor. Returns `null` when no paragraph /
75
+ * heading / list-item mints this scopeId.
76
+ */
77
+ export function resolveDerivedScopeAnchorFromCanonical(
78
+ document: CanonicalDocumentEnvelope,
79
+ scopeId: string,
80
+ assoc: { readonly start: -1 | 1; readonly end: -1 | 1 } = { start: 1, end: -1 },
81
+ ): EditorAnchorProjection | null {
82
+ // Fast-reject: derived paragraph-like scopeIds are always
83
+ // `para:…` / `heading:…` / `li:…`. Anything else (`table:…`,
84
+ // `field:…`, comment / revision ids, arbitrary host strings) can't
85
+ // match in this pass.
86
+ if (
87
+ !scopeId.startsWith("para:") &&
88
+ !scopeId.startsWith("heading:") &&
89
+ !scopeId.startsWith("li:")
90
+ ) {
91
+ return null;
92
+ }
93
+
94
+ const root = document.content as DocumentRootNode;
95
+ let cursor = 0;
96
+ const blocks = root.children;
97
+ for (let index = 0; index < blocks.length; index += 1) {
98
+ const block: BlockNode = blocks[index]!;
99
+ let thisBlockLength: number;
100
+ let matchedScopeId: string | null = null;
101
+
102
+ if (block.type === "paragraph") {
103
+ thisBlockLength = paragraphLength(block);
104
+ const kind = detectParagraphKind(block);
105
+ matchedScopeId = buildParagraphScopeId(kind, index, block);
106
+ } else if (block.type === "table" || block.type === "sdt") {
107
+ thisBlockLength = 1;
108
+ } else {
109
+ thisBlockLength = 1;
110
+ }
111
+
112
+ if (matchedScopeId !== null && matchedScopeId === scopeId) {
113
+ return {
114
+ kind: "range",
115
+ from: cursor,
116
+ to: cursor + thisBlockLength,
117
+ assoc,
118
+ };
119
+ }
120
+
121
+ cursor += thisBlockLength;
122
+ if (index < blocks.length - 1) cursor += 1;
123
+ }
124
+ return null;
125
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Workflow layer barrel.
3
+ *
4
+ * Layer 06 (workflow/review) — consolidated home for workflow scopes,
5
+ * scope markers, workflow markup, AI action policy, tamper gate, and the
6
+ * scope-rail composer. See `docs/architecture/06-workflow-review.md` for
7
+ * the canonical target and contracts W1–W9.
8
+ */
9
+
10
+ export * from "./query-scopes.ts";
11
+ export * from "./scope-resolver.ts";
12
+ export * from "./scope-tag-registry.ts";
13
+ export * from "./markup.ts";
14
+ export * from "./ai-action-policy.ts";
15
+ export * from "./tamper-gate.ts";
16
+ export * from "./rail/types.ts";
17
+ export * from "./rail/compose.ts";
18
+ export * from "./scope-writer.ts";
19
+ export * from "./metadata-writer.ts";
20
+ export * from "./projector.ts";
21
+ export * from "./overlay-store.ts";
22
+ export * from "./coordinator.ts";
23
+ export * from "./visibility-policy.ts";
24
+ export * from "./markup-mode-policy.ts";
25
+ export * from "./scope-rail-composer.ts";
@@ -0,0 +1,98 @@
1
+ /**
2
+ * X5 · Workflow markup-mode policy (class-A state) — runtime helpers.
3
+ *
4
+ * Mirrors the `visibility-policy.ts` shape for a single-value policy.
5
+ * Per `docs/plans/refactor/state-classes-cross-cutting.md` §X5:
6
+ *
7
+ * - L06 publishes `workflow.getMarkupModePolicy()` — class A.
8
+ * - L10 composes with class-C local preference in
9
+ * `ui.viewport.getEffectiveMarkupMode()`.
10
+ * - L03's `applyRevisionDisplay(run, revision, markupMode)` is
11
+ * unchanged — the composed value flows through.
12
+ *
13
+ * Invariants:
14
+ * - One policy record per document (not per-kind). Exposed via the
15
+ * `getMarkupModePolicy()` / `setMarkupModePolicy(policy)` pair.
16
+ * - `enforcement: "always"` hard-locks the mode; `"authored-default"`
17
+ * cedes to the local preference.
18
+ * - Mutations flow through the coordinator so they persist via
19
+ * `customXml/item1.xml` and broadcast via the Yjs document share.
20
+ */
21
+
22
+ import type {
23
+ OverlayVisibilityAuthorship,
24
+ WorkflowMarkupMode,
25
+ WorkflowMarkupModeEnforcement,
26
+ WorkflowMarkupModePolicy,
27
+ } from "../../api/public-types.ts";
28
+
29
+ export type {
30
+ WorkflowMarkupMode,
31
+ WorkflowMarkupModeEnforcement,
32
+ WorkflowMarkupModePolicy,
33
+ };
34
+
35
+ const MARKUP_MODE_SET: ReadonlySet<WorkflowMarkupMode> = new Set([
36
+ "clean",
37
+ "simple",
38
+ "all",
39
+ ]);
40
+ const ENFORCEMENT_SET: ReadonlySet<WorkflowMarkupModeEnforcement> = new Set([
41
+ "authored-default",
42
+ "always",
43
+ ]);
44
+
45
+ export function isWorkflowMarkupMode(value: unknown): value is WorkflowMarkupMode {
46
+ return (
47
+ typeof value === "string" &&
48
+ MARKUP_MODE_SET.has(value as WorkflowMarkupMode)
49
+ );
50
+ }
51
+
52
+ export function isWorkflowMarkupModeEnforcement(
53
+ value: unknown,
54
+ ): value is WorkflowMarkupModeEnforcement {
55
+ return (
56
+ typeof value === "string" &&
57
+ ENFORCEMENT_SET.has(value as WorkflowMarkupModeEnforcement)
58
+ );
59
+ }
60
+
61
+ export function isWorkflowMarkupModePolicy(
62
+ value: unknown,
63
+ ): value is WorkflowMarkupModePolicy {
64
+ if (value === null || typeof value !== "object") return false;
65
+ const c = value as { mode?: unknown; enforcement?: unknown };
66
+ return isWorkflowMarkupMode(c.mode) && isWorkflowMarkupModeEnforcement(c.enforcement);
67
+ }
68
+
69
+ export function createWorkflowMarkupModePolicy(input: {
70
+ mode: WorkflowMarkupMode;
71
+ enforcement: WorkflowMarkupModeEnforcement;
72
+ authoredBy?: OverlayVisibilityAuthorship;
73
+ }): WorkflowMarkupModePolicy {
74
+ return {
75
+ mode: input.mode,
76
+ enforcement: input.enforcement,
77
+ ...(input.authoredBy ? { authoredBy: { ...input.authoredBy } } : {}),
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Structural equality — used by the store to short-circuit no-op sets
83
+ * (no spurious subscriber wakeups).
84
+ */
85
+ export function workflowMarkupModePoliciesEqual(
86
+ left: WorkflowMarkupModePolicy | null | undefined,
87
+ right: WorkflowMarkupModePolicy | null | undefined,
88
+ ): boolean {
89
+ if (left === right) return true;
90
+ if (!left || !right) return false;
91
+ if (left.mode !== right.mode) return false;
92
+ if (left.enforcement !== right.enforcement) return false;
93
+ const la = left.authoredBy;
94
+ const ra = right.authoredBy;
95
+ if (!la && !ra) return true;
96
+ if (!la || !ra) return false;
97
+ return la.actorId === ra.actorId && la.at === ra.at;
98
+ }
@@ -20,15 +20,15 @@ import type {
20
20
  WorkflowProtectedRangeMarkup,
21
21
  WorkflowRevisionMarkup,
22
22
  WorkflowCommentMarkup,
23
- } from "../api/public-types";
24
- import { MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
25
- import { createPublicRangeAnchor } from "../core/selection/anchor-conversion.ts";
23
+ } from "../../api/public-types";
24
+ import { MAIN_STORY_TARGET } from "../../core/selection/mapping.ts";
25
+ import { createPublicRangeAnchor } from "../../core/selection/anchor-conversion.ts";
26
26
  import {
27
27
  projectSurfaceText,
28
28
  searchProjectedSurfaceText,
29
- } from "../core/search/search-text.ts";
30
- import { describeOpaqueFragment, isBlockedImportFeatureKey } from "../preservation/store.ts";
31
- import type { CanonicalDocumentEnvelope } from "../core/state/editor-state.ts";
29
+ } from "../../core/search/search-text.ts";
30
+ import { describeOpaqueFragment, isBlockedImportFeatureKey } from "../../preservation/store.ts";
31
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
32
32
 
33
33
  /**
34
34
  * Surface-derived markup (highlights + block-level opaque fragments).
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Layer 06 — Workflow metadata persistence + conflict resolution.
3
+ *
4
+ * Owns the semantics of converting scope metadata between the three
5
+ * persistence modes (overlay default / scope-level / entry-level
6
+ * cascading to "internal" | "external"), the version-mismatch
7
+ * detection that emits `metadata_conflict_detected`, and the
8
+ * internal ↔ external conversion routines that talk to a host-
9
+ * supplied `ScopeMetadataResolver`.
10
+ *
11
+ * Slice 4 rehousing (2026-04-22): this logic lived directly in
12
+ * `src/ui/WordReviewEditor.tsx` (lines 280-477) before the
13
+ * adversarial closure pass. It had no UI dependencies — it
14
+ * operated on the runtime's workflow metadata snapshot and the
15
+ * resolver — so it was a strong layering violation. Moving it
16
+ * behind a Layer-06 seam means:
17
+ *
18
+ * - The semantics are now inspectable + testable without booting
19
+ * the React tree.
20
+ * - A pending `WorkflowHostHandle` type can consume this module
21
+ * directly; v3 callers get the same conflict-resolution policy
22
+ * as the UI layer without the UI having to hand it to them.
23
+ * - The "conflict emission + deduplication by conflict key" rule
24
+ * is versioned in one place.
25
+ *
26
+ * W9 purity: this module imports only from `src/api/public-types`
27
+ * and the scope-metadata-resolver type file. Both are type-only
28
+ * module references allowed by
29
+ * `scripts/ci-check-workflow-layer-purity.mjs`.
30
+ */
31
+
32
+ import type {
33
+ MetadataPersistenceMode,
34
+ ScopeMetadataPersistence,
35
+ WorkflowMetadataEntry,
36
+ WorkflowMetadataSnapshot,
37
+ WorkflowOverlay,
38
+ WordReviewEditorEvent,
39
+ } from "../../api/public-types.ts";
40
+ import {
41
+ MetadataResolverMissingError,
42
+ } from "../../api/public-types.ts";
43
+ import type { ScopeMetadataResolver } from "../../api/scope-metadata-resolver-types.ts";
44
+
45
+ /* -------------------------------------------------------------------- *\
46
+ * Conflict keying — stable string identity per (scope, entry, field).
47
+ *
48
+ * The deduplication rule on `registerAndEmitConflict` depends on this
49
+ * key being stable, so the function is exported for host callers
50
+ * that want to find a previously-emitted conflict in their queue.
51
+ \* -------------------------------------------------------------------- */
52
+
53
+ export function conflictKey(input: {
54
+ scopeId?: string;
55
+ entryId?: string;
56
+ fieldKey?: string;
57
+ }): string {
58
+ return `${input.scopeId ?? ""}|${input.entryId ?? ""}|${input.fieldKey ?? ""}`;
59
+ }
60
+
61
+ /**
62
+ * §W5 — cascade for effective persistence mode. Field-level wins over
63
+ * scope-level wins over overlay-default; unset falls back to "internal".
64
+ * Pure function of the three inputs.
65
+ */
66
+ export function resolveEffectivePersistence(input: {
67
+ overlay?: MetadataPersistenceMode;
68
+ scope?: ScopeMetadataPersistence;
69
+ field?: ScopeMetadataPersistence;
70
+ }): "internal" | "external" {
71
+ if (input.field === "internal" || input.field === "external") return input.field;
72
+ if (input.scope === "internal" || input.scope === "external") return input.scope;
73
+ return input.overlay ?? "internal";
74
+ }
75
+
76
+ /**
77
+ * A pending conflict record held by the host while it decides how to
78
+ * resolve. Keyed by `conflictKey(...)`.
79
+ */
80
+ export interface PendingConflict {
81
+ scopeId?: string;
82
+ entryId?: string;
83
+ fieldKey?: string;
84
+ embedded: { value?: Record<string, unknown>; version?: number } | null;
85
+ external: { value?: Record<string, unknown>; version?: number } | null;
86
+ defaultPolicy: "prefer-latest";
87
+ }
88
+
89
+ /**
90
+ * Emit a single `metadata_conflict_detected` event and register the
91
+ * pending conflict, unless a conflict with the same key is already
92
+ * registered (deduplication). Pure side effect on the `pendingConflicts`
93
+ * map + the `onEvent` callback.
94
+ */
95
+ export function registerAndEmitConflict(args: {
96
+ onEvent: ((event: WordReviewEditorEvent) => void) | undefined;
97
+ documentId: string;
98
+ conflict: PendingConflict;
99
+ pendingConflicts: Map<string, PendingConflict>;
100
+ }): void {
101
+ const key = conflictKey(args.conflict);
102
+ // Guard: do not emit duplicate events for the same key in a single pass.
103
+ if (args.pendingConflicts.has(key)) return;
104
+ args.pendingConflicts.set(key, args.conflict);
105
+ args.onEvent?.({
106
+ type: "metadata_conflict_detected",
107
+ documentId: args.documentId,
108
+ scopeId: args.conflict.scopeId,
109
+ entryId: args.conflict.entryId,
110
+ fieldKey: args.conflict.fieldKey,
111
+ embedded: args.conflict.embedded,
112
+ external: args.conflict.external,
113
+ defaultPolicy: args.conflict.defaultPolicy,
114
+ });
115
+ }
116
+
117
+ /* -------------------------------------------------------------------- *\
118
+ * Narrow runtime interface — everything the conversion routines need
119
+ * from the runtime, specified as a structural type so callers can
120
+ * pass `DocumentRuntime` (or `WorkflowCoordinator`) verbatim without
121
+ * Layer-06 reaching up into `src/runtime/document-runtime.ts` or
122
+ * `src/api/v3/**`.
123
+ \* -------------------------------------------------------------------- */
124
+
125
+ export interface MetadataPersistenceRuntime {
126
+ getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot;
127
+ getWorkflowOverlay(): WorkflowOverlay | null;
128
+ setWorkflowMetadataEntries(entries: readonly WorkflowMetadataEntry[]): void;
129
+ }
130
+
131
+ /* -------------------------------------------------------------------- *\
132
+ * Conversion: scopes → internal (pull external payloads in-doc)
133
+ \* -------------------------------------------------------------------- */
134
+
135
+ export interface ConvertScopesToInternalInput {
136
+ runtime: MetadataPersistenceRuntime;
137
+ scopeIds: string[];
138
+ resolver: ScopeMetadataResolver | null;
139
+ /** When provided, version mismatches emit `metadata_conflict_detected`. */
140
+ documentId?: string;
141
+ onEvent?: (event: WordReviewEditorEvent) => void;
142
+ pendingConflicts?: Map<string, PendingConflict>;
143
+ }
144
+
145
+ /**
146
+ * Convert the given scopes' metadata entries to internal persistence
147
+ * (inline value in the customXml payload). For each entry currently
148
+ * in external mode, resolves the value via the host-supplied
149
+ * resolver, detects version mismatches (optional), and rewrites the
150
+ * entry with `metadataPersistence: "internal"` and the resolved
151
+ * value. Throws `MetadataResolverMissingError` when no resolver is
152
+ * supplied but conversion is attempted.
153
+ */
154
+ export async function convertScopesToInternal(
155
+ args: ConvertScopesToInternalInput,
156
+ ): Promise<void> {
157
+ if (!args.resolver) throw new MetadataResolverMissingError();
158
+ const snapshot = args.runtime.getWorkflowMetadataSnapshot();
159
+ const overlay = args.runtime.getWorkflowOverlay();
160
+
161
+ const nextEntries = await Promise.all(
162
+ snapshot.entries.map(async (entry) => {
163
+ if (!entry.scopeId || !args.scopeIds.includes(entry.scopeId)) return entry;
164
+ const scope = overlay?.scopes.find((s) => s.scopeId === entry.scopeId);
165
+ const effective = resolveEffectivePersistence({
166
+ overlay: overlay?.metadataPersistence,
167
+ scope: scope?.metadataPersistence,
168
+ field: entry.metadataPersistence,
169
+ });
170
+ if (effective !== "external" || !entry.storageRef) return entry;
171
+ const resolved = await args.resolver!.resolve(entry.storageRef);
172
+ if (!resolved) return entry;
173
+
174
+ // Conflict detection: compare embedded metadataVersion vs resolver version.
175
+ if (
176
+ args.pendingConflicts &&
177
+ args.documentId !== undefined &&
178
+ entry.metadataVersion !== undefined &&
179
+ resolved.version !== undefined &&
180
+ entry.metadataVersion !== resolved.version
181
+ ) {
182
+ registerAndEmitConflict({
183
+ onEvent: args.onEvent,
184
+ documentId: args.documentId,
185
+ pendingConflicts: args.pendingConflicts,
186
+ conflict: {
187
+ scopeId: entry.scopeId,
188
+ entryId: entry.entryId,
189
+ // External entries have no inline value; embedded side carries version only.
190
+ embedded: { version: entry.metadataVersion },
191
+ external: { value: resolved.value, version: resolved.version },
192
+ defaultPolicy: "prefer-latest",
193
+ },
194
+ });
195
+ }
196
+
197
+ // Default behavior: always apply the resolved value (host can override via resolveMetadataConflict).
198
+ return {
199
+ ...entry,
200
+ value: resolved.value,
201
+ metadataPersistence: "internal" as const,
202
+ storageRef: undefined,
203
+ metadataVersion: resolved.version ?? entry.metadataVersion,
204
+ };
205
+ }),
206
+ );
207
+
208
+ args.runtime.setWorkflowMetadataEntries(nextEntries);
209
+ }
210
+
211
+ /* -------------------------------------------------------------------- *\
212
+ * Conversion: scopes → external (publish payloads out of the doc)
213
+ \* -------------------------------------------------------------------- */
214
+
215
+ export interface ConvertScopesToExternalInput {
216
+ runtime: MetadataPersistenceRuntime;
217
+ scopeIds: string[];
218
+ resolver: ScopeMetadataResolver | null;
219
+ /** When provided, version-conflict publish errors emit `metadata_conflict_detected`. */
220
+ documentId?: string;
221
+ onEvent?: (event: WordReviewEditorEvent) => void;
222
+ pendingConflicts?: Map<string, PendingConflict>;
223
+ }
224
+
225
+ /**
226
+ * Convert the given scopes' metadata entries to external persistence
227
+ * (value lives in the host store; customXml carries only a ref +
228
+ * version). For each entry currently in internal mode, publishes
229
+ * the value via the host-supplied resolver, duck-types version-
230
+ * conflict errors into `metadata_conflict_detected`, and rewrites
231
+ * the entry with `metadataPersistence: "external"` and the new
232
+ * storage ref. Throws `MetadataResolverMissingError` when no
233
+ * resolver is supplied.
234
+ */
235
+ export async function convertScopesToExternal(
236
+ args: ConvertScopesToExternalInput,
237
+ ): Promise<void> {
238
+ if (!args.resolver) throw new MetadataResolverMissingError();
239
+ const snapshot = args.runtime.getWorkflowMetadataSnapshot();
240
+ const overlay = args.runtime.getWorkflowOverlay();
241
+
242
+ const nextEntries = await Promise.all(
243
+ snapshot.entries.map(async (entry) => {
244
+ if (!entry.scopeId || !args.scopeIds.includes(entry.scopeId)) return entry;
245
+ const scope = overlay?.scopes.find((s) => s.scopeId === entry.scopeId);
246
+ const effective = resolveEffectivePersistence({
247
+ overlay: overlay?.metadataPersistence,
248
+ scope: scope?.metadataPersistence,
249
+ field: entry.metadataPersistence,
250
+ });
251
+ if (effective === "external") return entry;
252
+
253
+ try {
254
+ const { ref, version } = await args.resolver!.publish({
255
+ scopeId: entry.scopeId,
256
+ metadataId: entry.metadataId,
257
+ entryId: entry.entryId,
258
+ value: entry.value ?? {},
259
+ expectedVersion: entry.metadataVersion,
260
+ });
261
+ return {
262
+ ...entry,
263
+ value: undefined,
264
+ metadataPersistence: "external" as const,
265
+ storageRef: ref,
266
+ metadataVersion: version,
267
+ };
268
+ } catch (err: unknown) {
269
+ // Duck-type version-conflict errors (HarnessVersionConflictError or compatible shapes).
270
+ if (
271
+ args.pendingConflicts &&
272
+ args.documentId !== undefined &&
273
+ err instanceof Error &&
274
+ (err.name === "HarnessVersionConflictError" ||
275
+ ("ref" in err && "expected" in err && "actual" in err))
276
+ ) {
277
+ const conflictErr = err as Error & {
278
+ ref?: string;
279
+ expected?: number;
280
+ actual?: number;
281
+ };
282
+ registerAndEmitConflict({
283
+ onEvent: args.onEvent,
284
+ documentId: args.documentId,
285
+ pendingConflicts: args.pendingConflicts,
286
+ conflict: {
287
+ scopeId: entry.scopeId,
288
+ entryId: entry.entryId,
289
+ // Embedded side: the entry's current inline value and metadataVersion.
290
+ embedded: { value: entry.value, version: entry.metadataVersion },
291
+ // External side: only the rowstore's actual version (no value available from error).
292
+ external: { version: conflictErr.actual },
293
+ defaultPolicy: "prefer-latest",
294
+ },
295
+ });
296
+ // Skip this entry — do not publish; leave it unchanged.
297
+ return entry;
298
+ }
299
+ // Non-conflict errors propagate normally.
300
+ throw err;
301
+ }
302
+ }),
303
+ );
304
+
305
+ args.runtime.setWorkflowMetadataEntries(nextEntries);
306
+ }