@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,407 @@
1
+ /**
2
+ * Layer 03 — `resolveEffectiveFormatting` façade over `FormattingContext`.
3
+ *
4
+ * Architecture contract F1: there is exactly one Layer-03 production
5
+ * boundary. That boundary is `FormattingContext` (see
6
+ * `./formatting-context.ts`) — production consumers (`surface-projection`,
7
+ * layout public-facet, etc.) construct one context per projection pass
8
+ * and call its methods directly for amortized per-document resolver
9
+ * reuse.
10
+ *
11
+ * `resolveEffectiveFormatting(doc, nodeRef, opts?)` is the external-API
12
+ * façade over that boundary. It constructs a per-call context from the
13
+ * inputs and delegates every dispatch branch to the context. Tests +
14
+ * agents + any caller that does not have a long-lived projection pass
15
+ * use this entry; production hot paths go through the context directly.
16
+ *
17
+ * The façade is PURE with respect to `doc` — it never mutates the
18
+ * document, never reads DOM geometry, and never schedules side effects
19
+ * outside the optional `opts.emitFormattingTelemetry` callback.
20
+ */
21
+
22
+ import type {
23
+ CanonicalDocument,
24
+ CanonicalFontTable,
25
+ FieldNode,
26
+ FieldRegistryEntry,
27
+ ParagraphNode,
28
+ RevisionRecord,
29
+ } from "../../model/canonical-document.ts";
30
+ import type { FieldPageGraph, FieldResolver } from "./field/resolver.ts";
31
+ import type { ThemeColorResolver } from "./theme-color.ts";
32
+ import type { NumberingPrefixResolver } from "./numbering/prefix.ts";
33
+ import {
34
+ createFormattingContext,
35
+ type FormattingContext,
36
+ } from "./formatting-context.ts";
37
+ import {
38
+ findParagraphByBlockId,
39
+ findTableByBlockId,
40
+ findInlineBySegmentIndex,
41
+ parseSegmentIndex,
42
+ resolveDirectRunFormattingAtSegment,
43
+ } from "./document-lookup.ts";
44
+ import type {
45
+ EffectiveFieldDisplay,
46
+ EffectiveFormatting,
47
+ } from "./formatting-types.ts";
48
+
49
+ /**
50
+ * Telemetry event shape emitted on the `formatting` channel when
51
+ * `opts.emitFormattingTelemetry` is provided.
52
+ */
53
+ export type FormattingTelemetryEvent =
54
+ | {
55
+ readonly type: "formatting.resolution_sampled";
56
+ readonly payload: {
57
+ readonly kind: NodeRef["kind"];
58
+ readonly blockId: string;
59
+ readonly hasDirect: boolean;
60
+ readonly hasTheme: boolean;
61
+ readonly hasNumbering: boolean;
62
+ };
63
+ }
64
+ | {
65
+ readonly type: "formatting.cache.invalidated";
66
+ readonly payload: {
67
+ readonly reason:
68
+ | "theme-resolver-built"
69
+ | "numbering-resolver-built"
70
+ | "field-resolver-built";
71
+ };
72
+ }
73
+ | {
74
+ /**
75
+ * Emitted when numbering resolution resolves a paragraph to a level
76
+ * whose `format` is not in `SUPPORTED_NUMBERING_FORMATS`. Exposes
77
+ * the L03 Task-A3 format-coverage gap observably without changing
78
+ * rendered output (the fallback path still produces `String(value)`
79
+ * so users see digits). Deduplication is expected to happen at the
80
+ * emit site — a surface-projection pass emits at most once per
81
+ * `(numberingInstanceId, level, format)` tuple.
82
+ */
83
+ readonly type: "formatting.numbering_format_unsupported";
84
+ readonly payload: {
85
+ readonly format: string;
86
+ readonly numberingInstanceId: string;
87
+ readonly level: number;
88
+ };
89
+ };
90
+
91
+ export type NodeRef =
92
+ | { kind: "paragraph"; blockId: string }
93
+ | { kind: "run"; blockId: string; runId: string }
94
+ | { kind: "table"; blockId: string }
95
+ | {
96
+ kind: "table-cell";
97
+ blockId: string;
98
+ rowIndex: number;
99
+ cellIndex: number;
100
+ }
101
+ | {
102
+ kind: "field";
103
+ blockId: string;
104
+ runId: string;
105
+ pageGraph?: FieldPageGraph;
106
+ }
107
+ | { kind: "drawing-frame"; blockId: string; inlineId: string };
108
+
109
+ export interface ResolveEffectiveFormattingOptions {
110
+ /** Active markup mode. When set together with `revision`, the
111
+ * returned run is decorated with `revisionDisplay` flags. */
112
+ readonly revisionMarkupMode?: "clean" | "simple" | "all";
113
+ /** Pre-built theme resolver. Passing one through avoids building
114
+ * a fresh `ThemeColorResolver` inside the façade; pass `undefined`
115
+ * to let the façade build from `doc.subParts.canonicalTheme`. */
116
+ readonly themeResolver?: ThemeColorResolver;
117
+ /** Pre-built numbering prefix resolver. When absent, the façade
118
+ * constructs one from `doc.numbering` per call. */
119
+ readonly numberingPrefixResolver?: NumberingPrefixResolver;
120
+ /** Font table reference (for ECMA-376 §17.3.2.26 substitution). */
121
+ readonly fontTable?: CanonicalFontTable;
122
+ /** Pre-built field resolver. Required for PAGE / NUMPAGES / REF /
123
+ * STYLEREF resolution. */
124
+ readonly fieldResolver?: FieldResolver;
125
+ /** Attached revision for `run` / `field` dispatch. */
126
+ readonly revision?: RevisionRecord;
127
+ /** Author-id → color palette for revision-display posture. */
128
+ readonly authorColorPalette?: ReadonlyMap<string, string>;
129
+ /** Probability-gated telemetry emit callback. */
130
+ readonly emitFormattingTelemetry?: (event: FormattingTelemetryEvent) => void;
131
+ }
132
+
133
+ export function resolveEffectiveFormatting(
134
+ doc: CanonicalDocument,
135
+ nodeRef: NodeRef,
136
+ opts?: ResolveEffectiveFormattingOptions,
137
+ ): EffectiveFormatting {
138
+ // Build a per-call façade context. Production consumers with a
139
+ // long-lived projection pass construct their own `FormattingContext`
140
+ // once and use it directly; the façade exists for external callers
141
+ // + tests that prefer the `(doc, nodeRef, opts)` entry shape.
142
+ const ctx = buildFacadeContext(doc, nodeRef, opts);
143
+ emitSampled(opts, {
144
+ type: "formatting.resolution_sampled",
145
+ payload: {
146
+ kind: nodeRef.kind,
147
+ blockId: nodeRef.blockId,
148
+ hasDirect: nodeRef.kind === "run" || nodeRef.kind === "field" || nodeRef.kind === "drawing-frame",
149
+ hasTheme: Boolean(opts?.themeResolver ?? doc.subParts?.canonicalTheme),
150
+ hasNumbering: Boolean(opts?.numberingPrefixResolver ?? doc.numbering),
151
+ },
152
+ });
153
+
154
+ switch (nodeRef.kind) {
155
+ case "paragraph":
156
+ return resolveParagraph(ctx, nodeRef.blockId);
157
+ case "run":
158
+ return resolveRun(ctx, doc, nodeRef.blockId, nodeRef.runId, opts);
159
+ case "table":
160
+ return resolveTable(ctx, nodeRef.blockId);
161
+ case "table-cell": {
162
+ const found = findTableByBlockId(doc, nodeRef.blockId);
163
+ if (!found) return { kind: "table-cell", paragraph: {}, run: {} };
164
+ // Resolve conditional regions for this cell so the floor cascade
165
+ // layers tblStylePr pPr/rPr for header rows, banded rows, first
166
+ // columns, etc. (L03 A1). Out-of-bounds row/cell indices fall
167
+ // back to no region info (same output as pre-A1).
168
+ const resolved = ctx.resolveTable(found.table);
169
+ const activeRegions =
170
+ resolved.rows[nodeRef.rowIndex]?.cells[nodeRef.cellIndex]
171
+ ?.activeConditionalRegions;
172
+ const { paragraph, run } = ctx.resolveTableCellFloor(
173
+ found.table.styleId,
174
+ activeRegions,
175
+ );
176
+ return { kind: "table-cell", paragraph, run };
177
+ }
178
+ case "field":
179
+ return resolveField(ctx, doc, nodeRef, opts);
180
+ case "drawing-frame":
181
+ return resolveDrawingFrame(ctx, doc, nodeRef.blockId, nodeRef.inlineId, opts);
182
+ }
183
+ }
184
+
185
+ function buildFacadeContext(
186
+ doc: CanonicalDocument,
187
+ nodeRef: NodeRef,
188
+ opts: ResolveEffectiveFormattingOptions | undefined,
189
+ ): FormattingContext {
190
+ const pageGraph =
191
+ nodeRef.kind === "field" ? nodeRef.pageGraph : undefined;
192
+ return createFormattingContext(doc, {
193
+ revisionMarkupMode: opts?.revisionMarkupMode,
194
+ authorColorPalette: opts?.authorColorPalette,
195
+ fontTable: opts?.fontTable,
196
+ pageGraph,
197
+ });
198
+ }
199
+
200
+ function resolveParagraph(
201
+ ctx: FormattingContext,
202
+ blockId: string,
203
+ ): Extract<EffectiveFormatting, { kind: "paragraph" }> {
204
+ const found = findParagraphByBlockId(ctx.doc, blockId);
205
+ if (!found) return { kind: "paragraph", paragraph: {} };
206
+ return { kind: "paragraph", paragraph: ctx.resolveParagraph(found.paragraph) };
207
+ }
208
+
209
+ function resolveRun(
210
+ ctx: FormattingContext,
211
+ doc: CanonicalDocument,
212
+ blockId: string,
213
+ runId: string,
214
+ opts: ResolveEffectiveFormattingOptions | undefined,
215
+ ): Extract<EffectiveFormatting, { kind: "run" }> {
216
+ void doc;
217
+ const found = findParagraphByBlockId(ctx.doc, blockId);
218
+ if (!found) return { kind: "run", run: {} };
219
+ const para = found.paragraph;
220
+ const direct = resolveDirectRunFormattingAtSegment(para, runId);
221
+ const run = ctx.resolveRunFromCanonicalInline({
222
+ paragraphStyleId: para.styleId,
223
+ characterStyleId: direct?.characterStyleId,
224
+ direct,
225
+ inHyperlink: detectHyperlinkContext(para, runId),
226
+ ...(opts?.revision ? { revision: opts.revision } : {}),
227
+ });
228
+ return { kind: "run", run };
229
+ }
230
+
231
+ function resolveTable(
232
+ ctx: FormattingContext,
233
+ blockId: string,
234
+ ): Extract<EffectiveFormatting, { kind: "table" }> {
235
+ const found = findTableByBlockId(ctx.doc, blockId);
236
+ if (!found) {
237
+ return {
238
+ kind: "table",
239
+ table: {
240
+ effectiveTblLook: {},
241
+ tableResolved: {},
242
+ rows: [],
243
+ },
244
+ };
245
+ }
246
+ return { kind: "table", table: ctx.resolveTable(found.table) };
247
+ }
248
+
249
+ function resolveField(
250
+ ctx: FormattingContext,
251
+ doc: CanonicalDocument,
252
+ nodeRef: Extract<NodeRef, { kind: "field" }>,
253
+ opts: ResolveEffectiveFormattingOptions | undefined,
254
+ ): Extract<EffectiveFormatting, { kind: "field" }> {
255
+ const found = findParagraphByBlockId(doc, nodeRef.blockId);
256
+ if (!found) {
257
+ return {
258
+ kind: "field",
259
+ run: {},
260
+ field: { refreshStatus: "unresolved" },
261
+ };
262
+ }
263
+
264
+ const para = found.paragraph;
265
+ const direct = resolveDirectRunFormattingAtSegment(para, nodeRef.runId);
266
+ const run = ctx.resolveRunFromCanonicalInline({
267
+ paragraphStyleId: para.styleId,
268
+ characterStyleId: undefined,
269
+ direct,
270
+ inHyperlink: false,
271
+ ...(opts?.revision ? { revision: opts.revision } : {}),
272
+ });
273
+
274
+ const fieldInline = findFieldNodeAtSegment(para, nodeRef.runId);
275
+ const registryEntry = fieldInline
276
+ ? findFieldRegistryEntryForParagraph(doc, found.paragraphIndex, fieldInline)
277
+ : undefined;
278
+
279
+ // The façade resolver was built with `nodeRef.pageGraph` threaded in,
280
+ // so `ctx.field` is present iff the caller supplied a page graph.
281
+ let field: EffectiveFieldDisplay;
282
+ if (!registryEntry) {
283
+ field = { refreshStatus: "unresolved" };
284
+ } else if (opts?.fieldResolver) {
285
+ // Honor an explicit pre-built fieldResolver over the context's own
286
+ // (preserves the opts-threading contract for external callers).
287
+ const resolved = opts.fieldResolver.resolve(registryEntry);
288
+ field = mapResolvedField(resolved, registryEntry);
289
+ } else {
290
+ field = ctx.resolveFieldDisplay(registryEntry, registryEntry.displayText);
291
+ }
292
+
293
+ return { kind: "field", run, field };
294
+ }
295
+
296
+ function resolveDrawingFrame(
297
+ ctx: FormattingContext,
298
+ doc: CanonicalDocument,
299
+ blockId: string,
300
+ inlineId: string,
301
+ opts: ResolveEffectiveFormattingOptions | undefined,
302
+ ): Extract<EffectiveFormatting, { kind: "drawing-frame" }> {
303
+ void doc;
304
+ const found = findParagraphByBlockId(ctx.doc, blockId);
305
+ if (!found) return { kind: "drawing-frame", run: {} };
306
+ const para = found.paragraph;
307
+ const direct = resolveDirectRunFormattingAtSegment(para, inlineId);
308
+ const run = ctx.resolveRunFromCanonicalInline({
309
+ paragraphStyleId: para.styleId,
310
+ characterStyleId: undefined,
311
+ direct,
312
+ inHyperlink: false,
313
+ ...(opts?.revision ? { revision: opts.revision } : {}),
314
+ });
315
+ return { kind: "drawing-frame", run };
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Helpers
320
+ // ---------------------------------------------------------------------------
321
+
322
+ function detectHyperlinkContext(para: ParagraphNode, runId: string): boolean {
323
+ const segmentIndex = parseSegmentIndex(runId);
324
+ if (segmentIndex === undefined) return false;
325
+ let s = 0;
326
+ for (const inline of para.children) {
327
+ if (inline.type === "hyperlink") {
328
+ for (let i = 0; i < inline.children.length; i += 1) {
329
+ if (s === segmentIndex) return true;
330
+ s += 1;
331
+ }
332
+ continue;
333
+ }
334
+ if (
335
+ inline.type === "bookmark_start" ||
336
+ inline.type === "bookmark_end" ||
337
+ inline.type === "scope_marker_start" ||
338
+ inline.type === "scope_marker_end"
339
+ ) {
340
+ continue;
341
+ }
342
+ if (s === segmentIndex) return false;
343
+ s += 1;
344
+ }
345
+ return false;
346
+ }
347
+
348
+ function findFieldNodeAtSegment(
349
+ para: ParagraphNode,
350
+ runId: string,
351
+ ): FieldNode | undefined {
352
+ const segmentIndex = parseSegmentIndex(runId);
353
+ if (segmentIndex === undefined) return undefined;
354
+ const found = findInlineBySegmentIndex(para, segmentIndex);
355
+ if (!found) return undefined;
356
+ return found.inline.type === "field" ? (found.inline as FieldNode) : undefined;
357
+ }
358
+
359
+ function findFieldRegistryEntryForParagraph(
360
+ doc: CanonicalDocument,
361
+ paragraphIndex: number,
362
+ fieldInline: FieldNode,
363
+ ): FieldRegistryEntry | undefined {
364
+ const registry = doc.fieldRegistry;
365
+ if (!registry) return undefined;
366
+ const all = [...registry.supported, ...registry.preserveOnly];
367
+ return all.find(
368
+ (entry) =>
369
+ entry.paragraphIndex === paragraphIndex &&
370
+ entry.fieldFamily === fieldInline.fieldFamily &&
371
+ entry.instruction === fieldInline.instruction,
372
+ );
373
+ }
374
+
375
+ function mapResolvedField(
376
+ resolved: { displayText?: string; refreshStatus: string } | undefined,
377
+ registry: FieldRegistryEntry,
378
+ ): EffectiveFieldDisplay {
379
+ if (!resolved) {
380
+ return {
381
+ refreshStatus: "unresolved",
382
+ ...(registry.displayText ? { displayText: registry.displayText } : {}),
383
+ };
384
+ }
385
+ const status: EffectiveFieldDisplay["refreshStatus"] =
386
+ resolved.refreshStatus === "current" || resolved.refreshStatus === "stale"
387
+ ? "resolved"
388
+ : resolved.refreshStatus === "unresolvable"
389
+ ? "unresolvable"
390
+ : "unresolved";
391
+ return {
392
+ refreshStatus: status,
393
+ ...(resolved.displayText !== undefined
394
+ ? { displayText: resolved.displayText }
395
+ : registry.displayText
396
+ ? { displayText: registry.displayText }
397
+ : {}),
398
+ };
399
+ }
400
+
401
+ function emitSampled(
402
+ opts: ResolveEffectiveFormattingOptions | undefined,
403
+ event: FormattingTelemetryEvent,
404
+ ): void {
405
+ if (!opts?.emitFormattingTelemetry) return;
406
+ opts.emitFormattingTelemetry(event);
407
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Layer 03 — revision-display posture application.
3
+ *
4
+ * UI / session capabilities decide which markup mode is active. This
5
+ * module owns the *application* of that mode to a run with an attached
6
+ * revision; it does NOT decide the mode. Reference: contract F6 in
7
+ * `docs/architecture/03-formatting-semantics.md` §52.
8
+ *
9
+ * Mode semantics:
10
+ * - "clean" — open deletions are hidden; insertions + accepted/rejected
11
+ * revisions render as final content.
12
+ * - "simple" — content visible but de-emphasized; simple inline markup.
13
+ * - "all" — strikethrough on open deletions; insertion-underline on
14
+ * insertions; formatting/property/move changes surface only
15
+ * via author color + change bars (owned by paragraph
16
+ * projection).
17
+ *
18
+ * `applyRevisionDisplay(run, revision, markupMode, authorColor?)` is pure
19
+ * and deterministic: same inputs → same output, no hidden state. That
20
+ * determinism lets downstream render code memoize on
21
+ * `(run, revision, markupMode)` identity.
22
+ *
23
+ * The `run: CanonicalRunFormatting` parameter lets this function inspect
24
+ * the already-cascaded run state to avoid redundant flags — e.g. a
25
+ * deletion run that already carries `strikethrough: true` via direct
26
+ * formatting skips the `all`-mode strikethrough flag so the rendered
27
+ * glyph is not double-struck.
28
+ */
29
+
30
+ import type {
31
+ CanonicalRunFormatting,
32
+ RevisionRecord,
33
+ } from "../../model/canonical-document.ts";
34
+ import type { RevisionDisplayFlags } from "./formatting-types.ts";
35
+
36
+ export type RevisionMarkupMode = "clean" | "simple" | "all";
37
+
38
+ export function applyRevisionDisplay(
39
+ run: CanonicalRunFormatting,
40
+ revision: RevisionRecord,
41
+ markupMode: RevisionMarkupMode,
42
+ authorColor?: string,
43
+ ): RevisionDisplayFlags {
44
+ switch (markupMode) {
45
+ case "clean": {
46
+ if (revision.kind === "deletion" && revision.status === "open") {
47
+ return buildFlags(markupMode, authorColor, { hidden: true });
48
+ }
49
+ return buildFlags(markupMode, authorColor);
50
+ }
51
+
52
+ case "simple": {
53
+ // Content visible, de-emphasized. No strikethrough / underline
54
+ // markers — the consumer renders a muted style (opacity /
55
+ // secondary color) uniformly.
56
+ return buildFlags(markupMode, authorColor, { deemphasize: true });
57
+ }
58
+
59
+ case "all": {
60
+ switch (revision.kind) {
61
+ case "deletion": {
62
+ // Short-circuit: if the run already carries strikethrough via
63
+ // direct formatting (uncommon but possible: a deletion whose
64
+ // source already had strikethrough as authoring style), do NOT
65
+ // add the revision strikethrough flag — avoids double-struck
66
+ // glyphs in render.
67
+ if (run.strikethrough === true) {
68
+ return buildFlags(markupMode, authorColor);
69
+ }
70
+ return buildFlags(markupMode, authorColor, { strikethrough: true });
71
+ }
72
+ case "insertion": {
73
+ // Mirror short-circuit for insertions. If the run already carries
74
+ // a single-underline via direct formatting, skip the insertion
75
+ // underline so the render doesn't over-paint.
76
+ if (run.underline === "single") {
77
+ return buildFlags(markupMode, authorColor);
78
+ }
79
+ return buildFlags(markupMode, authorColor, { insertionUnderline: true });
80
+ }
81
+ case "formatting":
82
+ case "property-change":
83
+ case "move": {
84
+ // Paragraph-level markers (change-bar, move-from/to arrows) are
85
+ // owned by the paragraph projection. For runs that carry these
86
+ // revision kinds, fall through to the author-color-only posture.
87
+ return buildFlags(markupMode, authorColor);
88
+ }
89
+ }
90
+ return buildFlags(markupMode, authorColor);
91
+ }
92
+ }
93
+ }
94
+
95
+ function buildFlags(
96
+ markupMode: RevisionMarkupMode,
97
+ authorColor: string | undefined,
98
+ extras: Omit<RevisionDisplayFlags, "markupMode" | "authorColor"> = {},
99
+ ): RevisionDisplayFlags {
100
+ return {
101
+ markupMode,
102
+ ...(authorColor !== undefined ? { authorColor } : {}),
103
+ ...extras,
104
+ };
105
+ }