@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
@@ -48,24 +48,27 @@ import {
48
48
  isBlockedImportFeatureKey,
49
49
  } from "../preservation/store.ts";
50
50
  import { getStoryBlocks } from "./story-targeting.ts";
51
- import {
52
- createNumberingPrefixResolver,
53
- type NumberingPrefixResult,
54
- type NumberingPrefixResolver,
55
- } from "./numbering-prefix.ts";
56
51
  import {
57
52
  collectSectionContexts,
58
53
  findHeaderFooterDocumentEntry,
59
54
  resolveSectionVariants,
60
55
  } from "./story-context.ts";
61
- import { resolveTableStyleResolution } from "./table-style-resolver.ts";
56
+ // L03 boundary: surface-projection owns PROJECTION, not formatting semantics.
57
+ // Every formatting resolution goes through `FormattingContext` — the
58
+ // production hot-path entry point for Layer 03. Scattered sub-resolver
59
+ // calls are rejected by `scripts/ci-check-formatting-production-boundary.mjs`.
62
60
  import {
63
- resolveEffectiveParagraphFormatting,
64
- resolveEffectiveRunFormatting,
65
- resolveNumberingMarkerRunFormatting,
66
- } from "./paragraph-style-resolver.ts";
67
- import { resolveHyperlinkRunFormatting } from "./hyperlink-color-resolver.ts";
68
- import { concretizeThemeColors, ThemeColorResolver } from "./theme-color-resolver.ts";
61
+ createFormattingContext,
62
+ type FormattingContext,
63
+ type NumberingResolution,
64
+ } from "./formatting/formatting-context.ts";
65
+ import { isSupportedNumberingFormat } from "./formatting/numbering/prefix.ts";
66
+ import type { FormattingTelemetryEvent } from "./formatting/resolve-effective.ts";
67
+ // The ThemeColorResolver type is still exposed to a handful of helper
68
+ // callsites below (shading, cell fill) that accept a pre-built resolver.
69
+ // Surface-projection no longer constructs one — it reads
70
+ // `formattingContext.theme` instead.
71
+ import type { ThemeColorResolver } from "./formatting/theme-color.ts";
69
72
  import type { CanonicalParagraphFormatting, CanonicalRunFormatting } from "../model/canonical-document.ts";
70
73
 
71
74
  const SAFE_CSS_HEX_COLOR_RE = /^#?(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
@@ -93,6 +96,48 @@ interface ParagraphAccumulator {
93
96
 
94
97
  export interface SurfaceProjectionOptions {
95
98
  viewportBlockRange?: { start: number; end: number } | null;
99
+ /**
100
+ * Active markup mode. When set together with the document's
101
+ * `review.revisions`, surface-projection tags each text segment whose
102
+ * offset falls inside a revision's anchor range with
103
+ * `segment.revisionDisplay` flags via `FormattingContext`.
104
+ *
105
+ * Architecture §F6: the layer applies the markup-mode decision (from
106
+ * UI / session capabilities); it does not decide it. When absent,
107
+ * segments carry no `revisionDisplay` field and the legacy decoration
108
+ * path continues to own rendering (back-compat during consumer
109
+ * migration).
110
+ */
111
+ revisionMarkupMode?: "clean" | "simple" | "all";
112
+ /**
113
+ * Live markup-mode callback. When set, wins over `revisionMarkupMode`
114
+ * on each projection pass — the composition site (L10
115
+ * `ui.viewport.getEffectiveMarkupMode()`) updates the rendered posture
116
+ * without re-parsing the document. Returning `undefined` means "no
117
+ * posture this pass". See `03-formatting-semantics.md` §F6.
118
+ */
119
+ getEffectiveMarkupMode?: () => "clean" | "simple" | "all" | undefined;
120
+ /**
121
+ * Author-id → color palette for revision-display posture. Consumed by
122
+ * `FormattingContext.resolveRunFromMarks({revision})` via the layer's
123
+ * `applyRevisionDisplay` function when the active revision carries an
124
+ * `authorId`.
125
+ */
126
+ authorColorPalette?: ReadonlyMap<string, string>;
127
+ /**
128
+ * Emit callback for L03 formatting telemetry. Bridges the numbering
129
+ * format-coverage gap (`formatting.numbering_format_unsupported`) onto
130
+ * the runtime telemetry bus when the document resolves a numbering
131
+ * level whose format is not in `SUPPORTED_NUMBERING_FORMATS` — the
132
+ * fallback path still renders `String(value)` so output is unchanged,
133
+ * but agents + the debug pane see the fidelity gap.
134
+ *
135
+ * Typically wired via `buildFormattingTelemetryBridge(handle.debug.bus)`
136
+ * in `src/runtime/formatting/telemetry-bridge.ts`. Surface-projection
137
+ * deduplicates emissions per `(numberingInstanceId, level, format)`
138
+ * tuple within a single projection pass.
139
+ */
140
+ emitFormattingTelemetry?: (event: FormattingTelemetryEvent) => void;
96
141
  }
97
142
 
98
143
  export function createEditorSurfaceSnapshot(
@@ -108,7 +153,25 @@ export function createEditorSurfaceSnapshot(
108
153
  });
109
154
  const blocks: SurfaceBlockSnapshot[] = [];
110
155
  const lockedFragmentIds: string[] = [];
111
- const numberingPrefixResolver = createNumberingPrefixResolver(document.numbering);
156
+ // L03 boundary: construct one formatting context per projection pass.
157
+ // The context owns the theme resolver + numbering prefix counter +
158
+ // style catalog walks. Surface-projection delegates every formatting
159
+ // semantics question to it and keeps only projection concerns
160
+ // (id synthesis, viewport culling, chart/image metadata).
161
+ const formattingContext = createFormattingContext(document, {
162
+ ...(options.revisionMarkupMode ? { revisionMarkupMode: options.revisionMarkupMode } : {}),
163
+ ...(options.getEffectiveMarkupMode
164
+ ? { getEffectiveMarkupMode: options.getEffectiveMarkupMode }
165
+ : {}),
166
+ ...(options.authorColorPalette ? { authorColorPalette: options.authorColorPalette } : {}),
167
+ });
168
+ // L03 §F6: revision range index is owned by `FormattingContext`
169
+ // itself when `revisionMarkupMode` is set. Surface-projection calls
170
+ // `formattingContext.findRevisionAtRange(from, to)` per segment to
171
+ // look up the attached revision + passes it to
172
+ // `formattingContext.resolveRunFromMarks({revision})`. The layer
173
+ // applies the posture; surface-projection just carries the flags
174
+ // through to `segment.revisionDisplay`.
112
175
  // Open a chartModelStore build pass tagged with the document envelope.
113
176
  // Every `chart_preview` node populated during the pass records its id
114
177
  // in the pass's seen-set; `endBuildPass()` below evicts store entries
@@ -116,6 +179,14 @@ export function createEditorSurfaceSnapshot(
116
179
  // earlier builds (same owner, not seen this pass). Previously the
117
180
  // store grew unbounded across document loads — documented v1 gap.
118
181
  chartModelStore.beginBuildPass(document);
182
+ // Per-pass dedup set for L03 `formatting.numbering_format_unsupported`
183
+ // emissions. Keys are `"${numberingInstanceId}|${level}|${format}"`.
184
+ // Only populated when the caller wires `emitFormattingTelemetry` —
185
+ // otherwise zero cost.
186
+ const unsupportedNumberingFormatsSeen: Set<string> | null = options
187
+ .emitFormattingTelemetry
188
+ ? new Set<string>()
189
+ : null;
119
190
  let cursor = 0;
120
191
  const counters = {
121
192
  paragraph: 0,
@@ -143,7 +214,7 @@ export function createEditorSurfaceSnapshot(
143
214
  document,
144
215
  cursor,
145
216
  counters,
146
- numberingPrefixResolver,
217
+ formattingContext,
147
218
  activeStory.kind !== "main",
148
219
  !isInViewport,
149
220
  );
@@ -178,7 +249,7 @@ export function createEditorSurfaceSnapshot(
178
249
  }
179
250
  }
180
251
 
181
- const secondaryStories = createSecondaryStorySurfaces(document, numberingPrefixResolver);
252
+ const secondaryStories = createSecondaryStorySurfaces(document, formattingContext);
182
253
 
183
254
  // Close the chartModelStore build pass. Evicts entries from previous
184
255
  // documents + stale entries whose ids no longer appear in the current
@@ -186,6 +257,36 @@ export function createEditorSurfaceSnapshot(
186
257
  // chart_preview nodes).
187
258
  chartModelStore.endBuildPass();
188
259
 
260
+ // L03 Task A3 item 5 — emit `formatting.numbering_format_unsupported`
261
+ // once per unique `(numberingInstanceId, level, format)` tuple when
262
+ // the resolved format is outside `SUPPORTED_NUMBERING_FORMATS`. The
263
+ // rendered output is unchanged (fallback is `String(value)`); this
264
+ // signal exposes the fidelity gap to agents + the debug pane without
265
+ // changing behavior. Zero cost when the caller does not wire
266
+ // `emitFormattingTelemetry`.
267
+ if (
268
+ options.emitFormattingTelemetry !== undefined &&
269
+ unsupportedNumberingFormatsSeen !== null
270
+ ) {
271
+ for (const block of blocks) {
272
+ if (block.kind !== "paragraph") continue;
273
+ if (!block.resolvedNumbering || !block.numbering) continue;
274
+ const fmt = block.resolvedNumbering.format;
275
+ if (isSupportedNumberingFormat(fmt)) continue;
276
+ const key = `${block.numbering.numberingInstanceId}|${block.resolvedNumbering.level}|${fmt}`;
277
+ if (unsupportedNumberingFormatsSeen.has(key)) continue;
278
+ unsupportedNumberingFormatsSeen.add(key);
279
+ options.emitFormattingTelemetry({
280
+ type: "formatting.numbering_format_unsupported",
281
+ payload: {
282
+ format: fmt,
283
+ numberingInstanceId: block.numbering.numberingInstanceId,
284
+ level: block.resolvedNumbering.level,
285
+ },
286
+ });
287
+ }
288
+ }
289
+
189
290
  return {
190
291
  storySize: cursor,
191
292
  plainText: createPlainText(blocks),
@@ -208,7 +309,7 @@ function createSurfaceBlock(
208
309
  customXml: number;
209
310
  altChunk: number;
210
311
  },
211
- numberingPrefixResolver: NumberingPrefixResolver,
312
+ formattingContext: FormattingContext,
212
313
  promoteSecondaryStoryTextBoxes: boolean,
213
314
  cullBuild: boolean = false,
214
315
  ): { block: SurfaceBlockSnapshot; lockedFragmentIds: string[]; nextCursor: number } {
@@ -249,7 +350,7 @@ function createSurfaceBlock(
249
350
  document,
250
351
  cursor,
251
352
  counters,
252
- numberingPrefixResolver,
353
+ formattingContext,
253
354
  promoteSecondaryStoryTextBoxes,
254
355
  cullBuild,
255
356
  );
@@ -289,7 +390,7 @@ function createSurfaceBlock(
289
390
  document,
290
391
  cursor,
291
392
  counters,
292
- numberingPrefixResolver,
393
+ formattingContext,
293
394
  promoteSecondaryStoryTextBoxes,
294
395
  cullBuild,
295
396
  );
@@ -382,7 +483,7 @@ function createSurfaceBlock(
382
483
  block,
383
484
  document,
384
485
  cursor,
385
- numberingPrefixResolver,
486
+ formattingContext,
386
487
  promoteSecondaryStoryTextBoxes,
387
488
  cullBuild,
388
489
  );
@@ -401,7 +502,7 @@ function createTableBlock(
401
502
  customXml: number;
402
503
  altChunk: number;
403
504
  },
404
- numberingPrefixResolver: NumberingPrefixResolver,
505
+ formattingContext: FormattingContext,
405
506
  promoteSecondaryStoryTextBoxes: boolean,
406
507
  cullBuild: boolean = false,
407
508
  ): { block: SurfaceBlockSnapshot; lockedFragmentIds: string[]; nextCursor: number } {
@@ -409,13 +510,11 @@ function createTableBlock(
409
510
  let innerCursor = cursor;
410
511
  const rows: SurfaceTableRowSnapshot[] = [];
411
512
  const rowSpans = computeTableRowSpans(table);
412
- const resolvedTable = resolveTableStyleResolution(table, document.styles.tables);
413
- // SOW gap G3 build the theme color resolver once per table so every cell
414
- // shading with `w:themeFill` can resolve the theme slot + tint/shade without
415
- // reconstructing the resolver per cell.
416
- const tableThemeResolver = document.subParts?.canonicalTheme
417
- ? new ThemeColorResolver(document.subParts.canonicalTheme)
418
- : undefined;
513
+ // L03 boundary: the table-style cascade + theme resolver come from the
514
+ // projection-pass-scoped formatting context so every block in the pass
515
+ // shares one cached theme resolver.
516
+ const resolvedTable = formattingContext.resolveTable(table);
517
+ const tableThemeResolver = formattingContext.theme;
419
518
 
420
519
  // SOW gap G5 — fold table-level borders into per-cell rendering. The CCEP
421
520
  // SOW form tables ship only `w:tblBorders` (single sz=4 on every side + insideH
@@ -449,7 +548,7 @@ function createTableBlock(
449
548
  document,
450
549
  innerCursor,
451
550
  counters,
452
- numberingPrefixResolver,
551
+ formattingContext,
453
552
  promoteSecondaryStoryTextBoxes,
454
553
  cullBuild,
455
554
  );
@@ -841,7 +940,7 @@ function createSdtBlock(
841
940
  customXml: number;
842
941
  altChunk: number;
843
942
  },
844
- numberingPrefixResolver: NumberingPrefixResolver,
943
+ formattingContext: FormattingContext,
845
944
  promoteSecondaryStoryTextBoxes: boolean,
846
945
  cullBuild: boolean = false,
847
946
  ): { block: SurfaceBlockSnapshot; lockedFragmentIds: string[]; nextCursor: number } {
@@ -855,7 +954,7 @@ function createSdtBlock(
855
954
  document,
856
955
  innerCursor,
857
956
  counters,
858
- numberingPrefixResolver,
957
+ formattingContext,
859
958
  promoteSecondaryStoryTextBoxes,
860
959
  cullBuild,
861
960
  );
@@ -891,7 +990,7 @@ function createParagraphBlock(
891
990
  paragraph: ParagraphNode,
892
991
  document: CanonicalDocumentEnvelope,
893
992
  start: number,
894
- numberingPrefixResolver: NumberingPrefixResolver,
993
+ formattingContext: FormattingContext,
895
994
  promoteSecondaryStoryTextBoxes: boolean,
896
995
  cullBuild: boolean = false,
897
996
  ): {
@@ -899,51 +998,40 @@ function createParagraphBlock(
899
998
  nextCursor: number;
900
999
  lockedFragmentIds: string[];
901
1000
  } {
902
- const themeResolver = document.subParts?.canonicalTheme
903
- ? new ThemeColorResolver(document.subParts.canonicalTheme)
904
- : undefined;
1001
+ // L03 boundary: the formatting context owns the theme resolver +
1002
+ // numbering counter + style-catalog walks. Surface-projection reads
1003
+ // `formattingContext.theme` (constructed once per projection pass)
1004
+ // and delegates every formatting semantics question to the context.
1005
+ const themeResolver = formattingContext.theme;
905
1006
  // L7 Phase 2.9 — viewport bail. When the paragraph is outside the
906
1007
  // viewport, the returned block is discarded (the outer caller in
907
1008
  // `createEditorSurfaceSnapshot` replaces it with a placeholder-culled
908
- // entry and only consumes `nextCursor`). Skip the two style-catalog
909
- // walks (`resolveEffectiveParagraphFormatting`,
910
- // `resolveNumberingMarkerRunFormatting`) their results are not read
911
- // by the placeholder path. Segment-level work inside
912
- // `appendInlineSegments` is suppressed symmetrically via the same
913
- // `cullBuild` flag, preserving cursor arithmetic.
914
- // Always resolve numbering so the counter advances for culled paragraphs too.
915
- const effectiveNumbering = resolveEffectiveParagraphNumbering(document, paragraph);
916
- const resolvedNumbering =
917
- !cullBuild && effectiveNumbering
918
- ? numberingPrefixResolver.resolveDetailed(effectiveNumbering, paragraph)
919
- : effectiveNumbering
920
- ? advanceNumberingCounterOnly(numberingPrefixResolver, effectiveNumbering)
921
- : null;
922
-
923
- // Task 11: compute cascaded paragraph formatting (expensive — styles-catalog walk).
924
- const stylesCatalog = document.styles;
925
- const directParagraphFormatting = cullBuild
926
- ? undefined
927
- : buildDirectParagraphFormattingFromNode(paragraph);
1009
+ // entry and only consumes `nextCursor`). The context's numbering
1010
+ // counter is always advanced so state stays correct for culled
1011
+ // paragraphs; the paragraph cascade + marker rPr walks are skipped.
1012
+ const effectiveNumbering = formattingContext.resolveEffectiveParagraphNumbering(paragraph);
1013
+ let resolvedNumbering: NumberingResolution | null = null;
1014
+ if (effectiveNumbering) {
1015
+ if (!cullBuild) {
1016
+ resolvedNumbering = formattingContext.resolveParagraphNumbering(paragraph);
1017
+ } else {
1018
+ // Counter-advance only for the culled path.
1019
+ formattingContext.numbering.resolve(effectiveNumbering);
1020
+ }
1021
+ }
1022
+
928
1023
  const resolvedParagraphFormatting = cullBuild
929
1024
  ? undefined
930
- : resolveEffectiveParagraphFormatting(
931
- { styleId: paragraph.styleId, direct: directParagraphFormatting },
932
- stylesCatalog,
933
- );
1025
+ : formattingContext.resolveParagraphCascade(paragraph);
934
1026
  const surfaceResolvedParagraphFormatting = resolvedParagraphFormatting
935
1027
  ? resolveSurfaceParagraphFormatting(resolvedParagraphFormatting, themeResolver)
936
1028
  : undefined;
937
1029
 
938
- // Task 11: compute cascaded marker run formatting (expensive).
939
1030
  const markerRunProperties =
940
1031
  !cullBuild && effectiveNumbering
941
- ? resolveNumberingMarkerRunFormatting(
942
- {
943
- paragraphStyleId: paragraph.styleId,
944
- levelRunProperties: resolvedNumbering?.markerRunProperties,
945
- },
946
- stylesCatalog,
1032
+ ? formattingContext.resolveNumberingMarkerRunFormatting(
1033
+ paragraph.styleId,
1034
+ resolvedNumbering?.markerRunProperties,
947
1035
  )
948
1036
  : undefined;
949
1037
 
@@ -1009,6 +1097,7 @@ function createParagraphBlock(
1009
1097
  undefined,
1010
1098
  cullBuild,
1011
1099
  themeResolver,
1100
+ formattingContext,
1012
1101
  );
1013
1102
  cursor = result.nextCursor;
1014
1103
  lockedFragmentIds.push(...result.lockedFragmentIds);
@@ -1022,155 +1111,14 @@ function createParagraphBlock(
1022
1111
  };
1023
1112
  }
1024
1113
 
1025
- function advanceNumberingCounterOnly(
1026
- resolver: NumberingPrefixResolver,
1027
- numbering: NonNullable<ParagraphNode["numbering"]>,
1028
- ): null {
1029
- resolver.resolve(numbering);
1030
- return null;
1031
- }
1032
-
1033
- function resolveEffectiveParagraphNumbering(
1034
- document: CanonicalDocumentEnvelope,
1035
- paragraph: ParagraphNode,
1036
- ): ParagraphNode["numbering"] | undefined {
1037
- if (paragraph.numbering) {
1038
- return paragraph.numbering;
1039
- }
1040
- if (!paragraph.styleId) {
1041
- return undefined;
1042
- }
1043
-
1044
- const paragraphStyles = document.styles?.paragraphs ?? {};
1045
- const styleChain = collectParagraphStyleChain(document, paragraph.styleId);
1046
- let styleNumbering:
1047
- | CanonicalDocumentEnvelope["styles"]["paragraphs"][string]["numbering"]
1048
- | undefined;
1049
- for (const styleId of styleChain) {
1050
- const style = paragraphStyles[styleId];
1051
- if (style?.numbering) {
1052
- styleNumbering = style.numbering;
1053
- break;
1054
- }
1055
- }
1056
- if (!styleNumbering) {
1057
- return undefined;
1058
- }
1059
- if (styleNumbering.level !== undefined) {
1060
- return {
1061
- numberingInstanceId: styleNumbering.numberingInstanceId,
1062
- level: styleNumbering.level,
1063
- };
1064
- }
1065
-
1066
- const resolvedLevel = resolveStyleLinkedNumberingLevel(
1067
- document,
1068
- styleNumbering.numberingInstanceId,
1069
- styleChain,
1070
- );
1071
- return resolvedLevel !== undefined
1072
- ? {
1073
- numberingInstanceId: styleNumbering.numberingInstanceId,
1074
- level: resolvedLevel,
1075
- }
1076
- : undefined;
1077
- }
1078
-
1079
- function collectParagraphStyleChain(
1080
- document: CanonicalDocumentEnvelope,
1081
- styleId: string,
1082
- ): string[] {
1083
- const paragraphStyles = document.styles?.paragraphs ?? {};
1084
- const chain: string[] = [];
1085
- const visited = new Set<string>();
1086
- let currentStyleId: string | undefined = styleId;
1087
-
1088
- while (currentStyleId && !visited.has(currentStyleId)) {
1089
- visited.add(currentStyleId);
1090
- chain.push(currentStyleId);
1091
- currentStyleId = paragraphStyles[currentStyleId]?.basedOn;
1092
- }
1093
-
1094
- return chain;
1095
- }
1096
-
1097
- function resolveStyleLinkedNumberingLevel(
1098
- document: CanonicalDocumentEnvelope,
1099
- numberingInstanceId: string,
1100
- styleChain: readonly string[],
1101
- ): number | undefined {
1102
- const instance = document.numbering.instances[numberingInstanceId];
1103
- if (!instance) {
1104
- return undefined;
1105
- }
1106
-
1107
- for (const styleId of styleChain) {
1108
- const overrideMatch = instance.overrides.find(
1109
- (override) => override.levelDefinition?.paragraphStyleId === styleId,
1110
- );
1111
- if (overrideMatch) {
1112
- return overrideMatch.level;
1113
- }
1114
- }
1115
-
1116
- const abstractDefinition = document.numbering.abstractDefinitions[instance.abstractNumberingId];
1117
- if (!abstractDefinition) {
1118
- return undefined;
1119
- }
1120
-
1121
- for (const styleId of styleChain) {
1122
- const levelMatch = abstractDefinition.levels.find(
1123
- (levelDefinition) => levelDefinition.paragraphStyleId === styleId,
1124
- );
1125
- if (levelMatch) {
1126
- return levelMatch.level;
1127
- }
1128
- }
1129
-
1130
- return undefined;
1131
- }
1132
-
1133
- function buildDirectRunFormattingFromMarks(
1134
- marks: SurfaceTextMark[] | undefined,
1135
- markAttrs: {
1136
- backgroundColor?: string;
1137
- charSpacing?: number;
1138
- kerning?: number;
1139
- textFill?: string;
1140
- fontFamily?: string;
1141
- fontSize?: number;
1142
- textColor?: string;
1143
- } | undefined,
1144
- ): CanonicalRunFormatting | undefined {
1145
- const direct: CanonicalRunFormatting = {};
1146
- if (marks) {
1147
- if (marks.includes("bold")) direct.bold = true;
1148
- if (marks.includes("italic")) direct.italic = true;
1149
- if (marks.includes("underline")) direct.underline = "single";
1150
- if (marks.includes("strikethrough")) direct.strikethrough = true;
1151
- if (marks.includes("doubleStrikethrough")) direct.doubleStrikethrough = true;
1152
- if (marks.includes("vanish")) direct.vanish = true;
1153
- if (marks.includes("allCaps")) direct.allCaps = true;
1154
- if (marks.includes("smallCaps")) direct.smallCaps = true;
1155
- }
1156
- if (markAttrs) {
1157
- if (markAttrs.fontFamily) {
1158
- direct.fontFamily = markAttrs.fontFamily;
1159
- direct.fontFamilyAscii = markAttrs.fontFamily;
1160
- }
1161
- if (typeof markAttrs.fontSize === "number") {
1162
- // markAttrs.fontSize is already in half-points from cloneMarks
1163
- direct.fontSizeHalfPoints = markAttrs.fontSize;
1164
- }
1165
- if (markAttrs.textColor) {
1166
- direct.colorHex = markAttrs.textColor.replace(/^#/, "");
1167
- }
1168
- if (markAttrs.backgroundColor) {
1169
- direct.highlight = markAttrs.backgroundColor;
1170
- }
1171
- }
1172
- return Object.keys(direct).length > 0 ? direct : undefined;
1173
- }
1114
+ // `advanceNumberingCounterOnly`, `resolveEffectiveParagraphNumbering`,
1115
+ // `collectParagraphStyleChain`, `resolveStyleLinkedNumberingLevel`, and
1116
+ // `buildDirectRunFormattingFromMarks` were removed from surface-projection
1117
+ // in the Layer-03 production-boundary migration. Their behavior now lives
1118
+ // on `FormattingContext` (`resolveEffectiveParagraphNumbering`,
1119
+ // `resolveParagraphNumbering`, `resolveRunFromMarks`, and the internal
1120
+ // `resolveStyleLinkedNumberingLevel` in `formatting-context.ts`). Surface-
1121
+ // projection owns projection concerns only.
1174
1122
 
1175
1123
  function appendInlineSegments(
1176
1124
  paragraph: ParagraphAccumulator,
@@ -1181,58 +1129,70 @@ function appendInlineSegments(
1181
1129
  hyperlinkHref?: string,
1182
1130
  cullBuild: boolean = false,
1183
1131
  themeResolver?: ThemeColorResolver,
1132
+ formattingContext?: FormattingContext,
1184
1133
  ): { nextCursor: number; lockedFragmentIds: string[] } {
1185
1134
  switch (node.type) {
1186
1135
  case "text": {
1187
1136
  const cloned = node.marks ? cloneMarks(node.marks) : { marks: [] as SurfaceTextMark[] };
1188
- // L7 Phase 2.9 — skip the styles-catalog run-cascade walk when
1189
- // the block will be culled. `resolveEffectiveRunFormatting`
1190
- // dominates per-text-segment cost on style-heavy docs; the
1191
- // placeholder path does not read `resolvedRunFormatting`.
1192
- const directRunFormatting = cullBuild
1193
- ? undefined
1194
- : buildDirectRunFormattingFromMarks(
1195
- cloned.marks.length > 0 ? cloned.marks : undefined,
1196
- cloned.markAttrs,
1197
- );
1198
- // V7 runs inside <w:hyperlink> go through the hyperlink color
1199
- // cascade (auto-applies the Hyperlink character style + resolves
1200
- // theme hlink slot with Word-default fallback). Non-hyperlink
1201
- // runs take the unchanged cascade path.
1202
- const runResolveInput = {
1203
- paragraphStyleId: paragraph.styleId,
1204
- characterStyleId: undefined,
1205
- direct: directRunFormatting,
1206
- };
1207
- // L2.c — non-hyperlink body text also gets theme-color concretization
1208
- // so paragraph styles declaring `<w:color w:themeColor="accent1"/>`
1209
- // render with their theme slot's hex instead of falling back to
1210
- // default black. Hyperlink branch already resolves theme via the
1211
- // Hyperlink-style cascade; only the non-hyperlink path was missing.
1212
- const resolvedRunFormatting = cullBuild
1213
- ? {}
1214
- : hyperlinkHref
1215
- ? resolveHyperlinkRunFormatting(
1216
- runResolveInput,
1217
- document.styles,
1218
- themeResolver,
1219
- )
1220
- : concretizeThemeColors(
1221
- resolveEffectiveRunFormatting(runResolveInput, document.styles),
1222
- themeResolver,
1223
- );
1137
+ const segmentFrom = start;
1138
+ const segmentTo = start + Array.from(node.text).length;
1139
+ // L7 Phase 2.9 skip the run-cascade walk when the block will be
1140
+ // culled. The placeholder path does not read `resolvedRunFormatting`.
1141
+ //
1142
+ // L03 boundary: the run cascade + theme concretization + hyperlink
1143
+ // four-tier fallback + F6 revision-display posture ALL flow through
1144
+ // `formattingContext.resolveRunFromMarks`. Surface-projection hands
1145
+ // the context the projected marks + the hyperlink-context flag +
1146
+ // the attached revision (looked up via L03's per-context revision
1147
+ // index); the context owns every cascade decision.
1148
+ const attachedRevision =
1149
+ !cullBuild && formattingContext
1150
+ ? formattingContext.findRevisionAtRange(segmentFrom, segmentTo)
1151
+ : undefined;
1152
+ const resolvedRunFormatting =
1153
+ cullBuild || !formattingContext
1154
+ ? {}
1155
+ : formattingContext.resolveRunFromMarks({
1156
+ paragraphStyleId: paragraph.styleId,
1157
+ marks: {
1158
+ marks: cloned.marks.length > 0 ? cloned.marks : undefined,
1159
+ markAttrs: cloned.markAttrs,
1160
+ },
1161
+ inHyperlink: hyperlinkHref !== undefined,
1162
+ ...(attachedRevision ? { revision: attachedRevision } : {}),
1163
+ });
1164
+ // Pull the F6 revision-display flags off the resolved record so
1165
+ // they ride the surface segment for render consumers. Research
1166
+ // doc §F6: the surface is the single place that maps segment →
1167
+ // revision; render reads `segment.revisionDisplay` directly.
1168
+ const revisionDisplay =
1169
+ (resolvedRunFormatting as { revisionDisplay?: unknown }).revisionDisplay;
1170
+ // Strip `revisionDisplay` from the object that lands on
1171
+ // `resolvedRunFormatting` — the snapshot surface carries it as a
1172
+ // top-level segment field (`segment.revisionDisplay`), not as a
1173
+ // sub-field of the cascade. This keeps legacy consumers of
1174
+ // `resolvedRunFormatting` stable while exposing the flags at a
1175
+ // discoverable site.
1176
+ const resolvedRunFormattingWithoutRevisionDisplay:
1177
+ Record<string, unknown> = { ...(resolvedRunFormatting as Record<string, unknown>) };
1178
+ delete resolvedRunFormattingWithoutRevisionDisplay.revisionDisplay;
1224
1179
  paragraph.segments.push({
1225
1180
  segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
1226
1181
  kind: "text",
1227
- from: start,
1228
- to: start + Array.from(node.text).length,
1182
+ from: segmentFrom,
1183
+ to: segmentTo,
1229
1184
  text: node.text,
1230
1185
  ...(cloned.marks.length > 0 ? { marks: cloned.marks } : {}),
1231
1186
  ...(cloned.markAttrs ? { markAttrs: cloned.markAttrs } : {}),
1232
- ...(Object.keys(resolvedRunFormatting).length > 0 ? { resolvedRunFormatting } : {}),
1187
+ ...(Object.keys(resolvedRunFormattingWithoutRevisionDisplay).length > 0
1188
+ ? { resolvedRunFormatting: resolvedRunFormattingWithoutRevisionDisplay as CanonicalRunFormatting }
1189
+ : {}),
1233
1190
  ...(hyperlinkHref ? { hyperlinkHref } : {}),
1191
+ ...(revisionDisplay
1192
+ ? { revisionDisplay: revisionDisplay as Extract<SurfaceInlineSegment, { kind: "text" }>["revisionDisplay"] }
1193
+ : {}),
1234
1194
  });
1235
- return { nextCursor: start + Array.from(node.text).length, lockedFragmentIds: [] };
1195
+ return { nextCursor: segmentTo, lockedFragmentIds: [] };
1236
1196
  }
1237
1197
  case "tab":
1238
1198
  paragraph.segments.push({
@@ -1264,6 +1224,7 @@ function appendInlineSegments(
1264
1224
  node.href,
1265
1225
  cullBuild,
1266
1226
  themeResolver,
1227
+ formattingContext,
1267
1228
  );
1268
1229
  cursor = result.nextCursor;
1269
1230
  }
@@ -1479,6 +1440,7 @@ function appendInlineSegments(
1479
1440
  refHyperlinkHref ?? hyperlinkHref,
1480
1441
  cullBuild,
1481
1442
  themeResolver,
1443
+ formattingContext,
1482
1444
  );
1483
1445
  cursor = result.nextCursor;
1484
1446
  lockedIds.push(...result.lockedFragmentIds);
@@ -1844,7 +1806,7 @@ function createPlainText(
1844
1806
 
1845
1807
  function createSecondaryStorySurfaces(
1846
1808
  document: CanonicalDocumentEnvelope,
1847
- numberingPrefixResolver: NumberingPrefixResolver,
1809
+ formattingContext: FormattingContext,
1848
1810
  ): SecondaryStorySurface[] {
1849
1811
  const surfaces: SecondaryStorySurface[] = [];
1850
1812
  const subParts = document.subParts;
@@ -1876,7 +1838,7 @@ function createSecondaryStorySurfaces(
1876
1838
  `Header · ${headerVariant.variant}`,
1877
1839
  header.blocks,
1878
1840
  document,
1879
- numberingPrefixResolver,
1841
+ formattingContext,
1880
1842
  ),
1881
1843
  );
1882
1844
  }
@@ -1904,7 +1866,7 @@ function createSecondaryStorySurfaces(
1904
1866
  `Footer · ${footerVariant.variant}`,
1905
1867
  footer.blocks,
1906
1868
  document,
1907
- numberingPrefixResolver,
1869
+ formattingContext,
1908
1870
  ),
1909
1871
  );
1910
1872
  }
@@ -1913,13 +1875,13 @@ function createSecondaryStorySurfaces(
1913
1875
  const footnotes = Object.values(subParts.footnoteCollection?.footnotes ?? {}).sort(compareNoteIds);
1914
1876
  for (const note of footnotes) {
1915
1877
  const target: EditorStoryTarget = { kind: "footnote", noteId: note.noteId };
1916
- surfaces.push(createStorySurface(target, `Footnote ${note.noteId}`, note.blocks, document, numberingPrefixResolver));
1878
+ surfaces.push(createStorySurface(target, `Footnote ${note.noteId}`, note.blocks, document, formattingContext));
1917
1879
  }
1918
1880
 
1919
1881
  const endnotes = Object.values(subParts.footnoteCollection?.endnotes ?? {}).sort(compareNoteIds);
1920
1882
  for (const note of endnotes) {
1921
1883
  const target: EditorStoryTarget = { kind: "endnote", noteId: note.noteId };
1922
- surfaces.push(createStorySurface(target, `Endnote ${note.noteId}`, note.blocks, document, numberingPrefixResolver));
1884
+ surfaces.push(createStorySurface(target, `Endnote ${note.noteId}`, note.blocks, document, formattingContext));
1923
1885
  }
1924
1886
 
1925
1887
  return surfaces;
@@ -1930,7 +1892,7 @@ function createStorySurface(
1930
1892
  label: string,
1931
1893
  blocks: readonly BlockNode[],
1932
1894
  document: CanonicalDocumentEnvelope,
1933
- numberingPrefixResolver: NumberingPrefixResolver,
1895
+ formattingContext: FormattingContext,
1934
1896
  ): SecondaryStorySurface {
1935
1897
  const surfaceBlocks: SurfaceBlockSnapshot[] = [];
1936
1898
  let cursor = 0;
@@ -1949,7 +1911,7 @@ function createStorySurface(
1949
1911
  document,
1950
1912
  cursor,
1951
1913
  counters,
1952
- numberingPrefixResolver,
1914
+ formattingContext,
1953
1915
  true,
1954
1916
  );
1955
1917
  surfaceBlocks.push(surfaceBlock.block);
@@ -2167,7 +2129,7 @@ function buildDirectParagraphFormattingFromNode(
2167
2129
  }
2168
2130
 
2169
2131
  function toSurfaceResolvedNumbering(
2170
- numbering: NumberingPrefixResult,
2132
+ numbering: NumberingResolution,
2171
2133
  ): NonNullable<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["resolvedNumbering"]> {
2172
2134
  return {
2173
2135
  level: numbering.level,
@@ -2508,3 +2470,11 @@ function createFloatingImageDetail(
2508
2470
  function hasMediaItem(media: MediaCatalog, mediaId: string): boolean {
2509
2471
  return mediaId in media.items;
2510
2472
  }
2473
+
2474
+ // Revision range index was moved into `FormattingContext` itself in the
2475
+ // §A production-boundary pass. Surface-projection now calls
2476
+ // `formattingContext.findRevisionAtRange(from, to)` at each text segment
2477
+ // push-point and hands the attached revision to
2478
+ // `formattingContext.resolveRunFromMarks({revision})` (which invokes
2479
+ // `applyRevisionDisplay` per F6). The result flows onto
2480
+ // `segment.revisionDisplay` for render consumers.