@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,211 @@
1
+ /**
2
+ * Layer 04 — layout compat-input ledger.
3
+ *
4
+ * Task 5 (refactor/04 post-closure, 2026-04-23).
5
+ *
6
+ * The canonical document's `DocumentSettings` carries three compat-flag
7
+ * buckets that OOXML defines for backwards compatibility with pre-2007
8
+ * Word and cross-version layout quirks:
9
+ *
10
+ * - `compatSettings[]` — ordered list of `<w:compatSetting>` entries
11
+ * inside `<w:compat>` (e.g. `compatibilityMode`,
12
+ * `overrideTableStyleFontSizeAndJustification`).
13
+ * - `compatFlags` — boolean children of `<w:compat>` that aren't
14
+ * `<w:compatSetting>` (e.g. `doNotExpandShiftReturn`,
15
+ * `useFELayout`).
16
+ * - `rootCompatFlags` — settings-level boolean flags that sit at the
17
+ * root of `<w:settings>` instead of inside `<w:compat>` (e.g.
18
+ * `doNotEmbedSmartTags`).
19
+ *
20
+ * Today the paginated layout engine branches on none of them. This
21
+ * module provides a typed snapshot so:
22
+ *
23
+ * 1. Behavioral changes that need to gate on a compat flag have a
24
+ * canonical access point (no ad-hoc `settings.compatFlags?.[...]`
25
+ * reads scattered across `src/runtime/layout/**`).
26
+ * 2. The debug projector can surface the active ledger alongside
27
+ * page counts + measurement fidelity so operators can tell at a
28
+ * glance which compat-conditional branches *would* fire on the
29
+ * current document.
30
+ *
31
+ * The ledger is read-only, pure data (no methods), JSON-serializable —
32
+ * it fits inside `LayoutDebugEntry` and can travel through the
33
+ * debug-inspector snapshot unchanged.
34
+ *
35
+ * Resolution rule: OOXML defaults are used for flags absent from the
36
+ * document's `DocumentSettings`. The defaults encoded here match Word
37
+ * 2007+ behavior (the "modern" posture); documents that explicitly
38
+ * set flags opt into the legacy posture.
39
+ */
40
+
41
+ import type { DocumentSettings } from "../../model/canonical-document.ts";
42
+
43
+ /**
44
+ * Typed snapshot of layout-relevant compat inputs + passthrough for
45
+ * anything the resolver doesn't know about by name.
46
+ */
47
+ export interface LayoutCompatInputs {
48
+ readonly schemaVersion: 1;
49
+ /**
50
+ * From `<w:compatSetting w:name="compatibilityMode" w:val>`. Word
51
+ * writes "15" for Word 2013+ behavior, "14" for 2010, "12" for 2007.
52
+ * Absent → 15 (modern default; Word 2007 behavior is explicitly opted
53
+ * into, not implicit).
54
+ */
55
+ readonly compatibilityMode: number;
56
+ /**
57
+ * From `<w:doNotExpandShiftReturn/>` under `<w:compat>`. When true,
58
+ * soft line breaks (`<w:br/>`) do not expand to fill the line like
59
+ * hard breaks do — Word 2007's layout behavior. Default false.
60
+ */
61
+ readonly doNotExpandShiftReturn: boolean;
62
+ /**
63
+ * From `<w:useWord97LineBreakRules/>`. When true, line-break
64
+ * classification follows Word 97 conventions (different handling of
65
+ * East-Asian break classes). Default false.
66
+ */
67
+ readonly useWord97LineBreakRules: boolean;
68
+ /**
69
+ * From `<w:useFELayout/>`. Forces East-Asian layout conventions
70
+ * (document-grid, vertical text, CJK line-break rules). Default false.
71
+ */
72
+ readonly useFELayout: boolean;
73
+ /**
74
+ * From `<w:doNotUseHTMLParagraphAutoSpacing/>`. When true,
75
+ * paragraphs inherit their full `spacingBefore` + `spacingAfter`
76
+ * regardless of adjacent-paragraph contextual-spacing hints.
77
+ * Default false.
78
+ */
79
+ readonly doNotUseHTMLParagraphAutoSpacing: boolean;
80
+ /**
81
+ * From `<w:compatSetting w:name="overrideTableStyleFontSizeAndJustification">`.
82
+ * When true, direct paragraph formatting on a table row wins over
83
+ * the table style's font-size + justification. Default false.
84
+ */
85
+ readonly overrideTableStyleFontSizeAndJustification: boolean;
86
+ /**
87
+ * From `<w:compatSetting w:name="differentiateMultirowTableHeaders">`.
88
+ * When true, multi-row `<w:tblHeader/>` rows repeat as a group on
89
+ * continuation pages. Default false; when false only the first
90
+ * header row repeats.
91
+ */
92
+ readonly differentiateMultirowTableHeaders: boolean;
93
+ /**
94
+ * Passthrough: every `<w:compatSetting>` entry in document order.
95
+ * Consumers that need a flag this ledger doesn't model by name can
96
+ * scan this list; the resolver retains insertion order for
97
+ * byte-stable round-trip.
98
+ */
99
+ readonly unknownCompatSettings: ReadonlyArray<{
100
+ readonly name: string;
101
+ readonly uri: string;
102
+ readonly value: string;
103
+ }>;
104
+ /**
105
+ * Passthrough: every boolean compat flag the ledger doesn't model
106
+ * individually, keyed by local element name, sourced from
107
+ * `DocumentSettings.compatFlags` + `.rootCompatFlags` (merged;
108
+ * duplicates prefer `compatFlags` since it's the canonical OOXML
109
+ * location).
110
+ */
111
+ readonly unknownCompatFlags: Readonly<Record<string, boolean>>;
112
+ }
113
+
114
+ const DEFAULT_COMPATIBILITY_MODE = 15;
115
+
116
+ const KNOWN_COMPAT_FLAG_NAMES = new Set([
117
+ "doNotExpandShiftReturn",
118
+ "useWord97LineBreakRules",
119
+ "useFELayout",
120
+ "doNotUseHTMLParagraphAutoSpacing",
121
+ ]);
122
+
123
+ const KNOWN_COMPAT_SETTING_NAMES = new Set([
124
+ "compatibilityMode",
125
+ "overrideTableStyleFontSizeAndJustification",
126
+ "differentiateMultirowTableHeaders",
127
+ ]);
128
+
129
+ /**
130
+ * Read the compat ledger from the document's `DocumentSettings`.
131
+ * Returns a ledger populated from OOXML defaults when `settings` is
132
+ * absent — never returns null.
133
+ */
134
+ export function resolveLayoutCompatInputs(
135
+ settings: DocumentSettings | undefined,
136
+ ): LayoutCompatInputs {
137
+ const compatFlags = settings?.compatFlags ?? {};
138
+ const rootCompatFlags = settings?.rootCompatFlags ?? {};
139
+ const compatSettings = settings?.compatSettings ?? [];
140
+
141
+ // Named settings resolution
142
+ let compatibilityMode = DEFAULT_COMPATIBILITY_MODE;
143
+ let overrideTableStyleFontSizeAndJustification = false;
144
+ let differentiateMultirowTableHeaders = false;
145
+ const unknownCompatSettings: Array<{
146
+ name: string;
147
+ uri: string;
148
+ value: string;
149
+ }> = [];
150
+
151
+ for (const entry of compatSettings) {
152
+ switch (entry.name) {
153
+ case "compatibilityMode": {
154
+ const parsed = Number.parseInt(entry.value, 10);
155
+ if (Number.isFinite(parsed)) compatibilityMode = parsed;
156
+ break;
157
+ }
158
+ case "overrideTableStyleFontSizeAndJustification":
159
+ overrideTableStyleFontSizeAndJustification = parseOnOff(entry.value);
160
+ break;
161
+ case "differentiateMultirowTableHeaders":
162
+ differentiateMultirowTableHeaders = parseOnOff(entry.value);
163
+ break;
164
+ default:
165
+ unknownCompatSettings.push(entry);
166
+ }
167
+ }
168
+
169
+ // Named flag resolution
170
+ const doNotExpandShiftReturn = compatFlags.doNotExpandShiftReturn === true;
171
+ const useWord97LineBreakRules = compatFlags.useWord97LineBreakRules === true;
172
+ const useFELayout = compatFlags.useFELayout === true;
173
+ const doNotUseHTMLParagraphAutoSpacing =
174
+ compatFlags.doNotUseHTMLParagraphAutoSpacing === true;
175
+
176
+ // Passthrough unknown flags — merge the two buckets but exclude
177
+ // names the ledger already surfaces as dedicated fields.
178
+ const unknownCompatFlags: Record<string, boolean> = {};
179
+ for (const [name, value] of Object.entries(compatFlags)) {
180
+ if (KNOWN_COMPAT_FLAG_NAMES.has(name)) continue;
181
+ unknownCompatFlags[name] = value === true;
182
+ }
183
+ for (const [name, value] of Object.entries(rootCompatFlags)) {
184
+ if (KNOWN_COMPAT_FLAG_NAMES.has(name)) continue;
185
+ if (unknownCompatFlags[name] !== undefined) continue;
186
+ unknownCompatFlags[name] = value === true;
187
+ }
188
+
189
+ void KNOWN_COMPAT_SETTING_NAMES;
190
+
191
+ return {
192
+ schemaVersion: 1,
193
+ compatibilityMode,
194
+ doNotExpandShiftReturn,
195
+ useWord97LineBreakRules,
196
+ useFELayout,
197
+ doNotUseHTMLParagraphAutoSpacing,
198
+ overrideTableStyleFontSizeAndJustification,
199
+ differentiateMultirowTableHeaders,
200
+ unknownCompatSettings: Object.freeze(unknownCompatSettings),
201
+ unknownCompatFlags: Object.freeze(unknownCompatFlags),
202
+ };
203
+ }
204
+
205
+ function parseOnOff(value: string): boolean {
206
+ // ST_OnOff: "1" / "true" / "on" → true; "0" / "false" / "off" → false.
207
+ // Absent explicit value would never reach here (CompatSetting always
208
+ // carries a string), but be defensive.
209
+ const normalized = value.trim().toLowerCase();
210
+ return normalized === "1" || normalized === "true" || normalized === "on";
211
+ }
@@ -166,8 +166,13 @@ export { createCanvasBackend } from "./measurement-backend-canvas.ts";
166
166
 
167
167
  export { createInertLayoutFacet } from "./inert-layout-facet.ts";
168
168
 
169
+ // Refactor-era structural alias — consumers type against `LayoutFacet`
170
+ // while `WordReviewEditorLayoutFacet` remains the public-API name.
171
+ export type { LayoutFacet } from "./layout-facet-types.ts";
172
+
169
173
  export {
170
174
  createLayoutFacet,
175
+ setActiveLayoutWarningEmitter,
171
176
  type WordReviewEditorLayoutFacet,
172
177
  type PublicPageNode,
173
178
  type PublicPageRegions,
@@ -234,4 +239,4 @@ export {
234
239
  type CollectScopeRailSegmentsInput,
235
240
  type ScopeRailPosture,
236
241
  type ScopeRailSegment,
237
- } from "../workflow-rail-segments.ts";
242
+ } from "../workflow/rail/compose.ts";
@@ -43,13 +43,18 @@ export function createInertLayoutFacet(): WordReviewEditorLayoutFacet {
43
43
  getActivePageFormat: () => null,
44
44
  getMarginPresetCatalog: () => MARGIN_PRESET_CATALOG,
45
45
  getActiveMarginPreset: () => null,
46
- getRenderFrame: () => null,
47
- getRenderZoom: () => null,
48
- hitTest: () => null,
49
- getAnchorRects: () => [],
50
- getScopeRailSegments: () => [],
51
- getAllScopeRailSegments: () => [],
52
- getAllScopeCardModels: () => [],
46
+ // `getRenderFrame` + `getRenderZoom` removed in refactor/05
47
+ // cross-lane-coord §8 pass (2026-04-22). `createInertGeometryFacet`
48
+ // handles their loading-boundary shape.
49
+ // `hitTest` + `getAnchorRects` removed from the layout facet in
50
+ // refactor/05 Slice 6 wrapper-deletion pass (2026-04-22).
51
+ // `createInertGeometryFacet` handles their loading-boundary shape.
52
+ // `getScopeRailSegments` / `getAllScopeRailSegments` /
53
+ // `getAllScopeCardModels` removed from the layout facet in
54
+ // refactor/06 Slice 4C rail-seam inversion (2026-04-22). Rail
55
+ // composition is now `runtime.workflow.*`; the loading boundary
56
+ // exposes an inert workflow facet separately (see
57
+ // `src/ui/editor-runtime-boundary.ts`).
53
58
  getResolvedFormatting: () => null,
54
59
  getResolvedRunFormatting: () => null,
55
60
  getMeasurement: () => null,
@@ -81,6 +81,8 @@ import {
81
81
  createEmpiricalMeasurementProvider,
82
82
  type LayoutMeasurementProvider,
83
83
  } from "./layout-measurement-provider.ts";
84
+ import { LAYOUT_ENGINE_VERSION } from "./layout-engine-version.ts";
85
+ import type { TelemetryBus } from "../debug/telemetry-bus.ts";
84
86
 
85
87
  // ---------------------------------------------------------------------------
86
88
  // Types
@@ -267,6 +269,14 @@ export interface CreateLayoutEngineOptions {
267
269
  * determinism or tests) pass `false`. Default: true.
268
270
  */
269
271
  autoUpgradeToCanvasBackend?: boolean;
272
+ /**
273
+ * Refactor/04 Slice 3 — optional `TelemetryBus` the engine forwards
274
+ * layout lifecycle events to on the `layout` channel. Zero cost when
275
+ * the channel is off (the bus bitmap-checks before allocating a
276
+ * payload). Omit to leave the engine's internal listener stream the
277
+ * only consumer.
278
+ */
279
+ telemetryBus?: TelemetryBus;
270
280
  }
271
281
 
272
282
  export function createLayoutEngine(
@@ -275,6 +285,7 @@ export function createLayoutEngine(
275
285
  let measurementProvider: LayoutMeasurementProvider =
276
286
  options.measurementProvider ?? createEmpiricalMeasurementProvider();
277
287
  const autoUpgradeToCanvas = options.autoUpgradeToCanvasBackend !== false;
288
+ const telemetryBus = options.telemetryBus;
278
289
  const dirtyFieldFamilies = new Set<string>();
279
290
  const listeners = new Set<(event: LayoutEngineEvent) => void>();
280
291
  let cachedKey: CacheKey | null = null;
@@ -300,8 +311,127 @@ export function createLayoutEngine(
300
311
  // never let a listener error interrupt the engine
301
312
  }
302
313
  }
314
+ // Refactor/04 Slice 3 — forward granular lifecycle events to the
315
+ // `layout` telemetry channel. `layout.recompute.completed` is emitted
316
+ // separately by `fullRebuild` / `incrementalRelayout` so it can carry
317
+ // `durationMs` + `scope`; that event has no direct `LayoutEngineEvent`
318
+ // analogue.
319
+ //
320
+ // Emission uses `emitLazy` so the bus fills the monotonic `t` clock
321
+ // automatically. Passing `emit({ t: 0, … })` would preserve the
322
+ // literal zero (the bus only substitutes when `t` is non-numeric) and
323
+ // produce traces with collapsed timestamps.
324
+ if (!telemetryBus || !telemetryBus.isEnabled("layout")) return;
325
+ switch (event.kind) {
326
+ case "page_count_changed":
327
+ telemetryBus.emitLazy("layout", () => ({
328
+ type: "layout.page_count_changed",
329
+ payload: {
330
+ before: event.previousPageCount ?? null,
331
+ after: event.currentPageCount ?? null,
332
+ revision: event.revision,
333
+ },
334
+ }));
335
+ break;
336
+ case "page_field_dirtied":
337
+ telemetryBus.emitLazy("layout", () => ({
338
+ type: "layout.page_field_dirtied",
339
+ payload: {
340
+ families: event.dirtyFieldFamilies ?? [],
341
+ revision: event.revision,
342
+ },
343
+ }));
344
+ break;
345
+ case "layout_committed":
346
+ telemetryBus.emitLazy("layout", () => ({
347
+ type: "layout.committed",
348
+ payload: { revision: event.revision },
349
+ }));
350
+ break;
351
+ default:
352
+ break;
353
+ }
354
+ }
355
+
356
+ function telemetryNow(): number {
357
+ if (typeof performance !== "undefined" && typeof performance.now === "function") {
358
+ return performance.now();
359
+ }
360
+ return Date.now();
303
361
  }
304
362
 
363
+ function emitRecomputeCompleted(
364
+ scope: "full" | "bounded",
365
+ reasonKind: string,
366
+ pageCountBefore: number,
367
+ pageCountAfter: number,
368
+ durationMs: number,
369
+ ): void {
370
+ if (!telemetryBus || !telemetryBus.isEnabled("layout")) return;
371
+ telemetryBus.emitLazy("layout", () => ({
372
+ type: "layout.recompute.completed",
373
+ payload: {
374
+ layoutEngineVersion: LAYOUT_ENGINE_VERSION,
375
+ reasonKind,
376
+ scope,
377
+ pageCountBefore,
378
+ pageCountAfter,
379
+ durationMs,
380
+ },
381
+ }));
382
+ // Refactor/04 post-Slice-4 (2026-04-22) — D1 stage-token pair at
383
+ // the layout engine's recompute boundary. Paired with the
384
+ // already-emitted `layout.refresh.start` / `layout.refresh.complete`
385
+ // stage-tokens around the outer `refreshRenderSnapshot` in
386
+ // `document-runtime.ts`; this inner event lets
387
+ // `L3.layout-truth` attribute a diverged verdict to a specific
388
+ // recompute pass (full vs bounded, which reasonKind, how many
389
+ // pages before / after). Requested by debug-infra's cross-layer
390
+ // coord doc §"Layer 04 — Layout Semantics" → "Benefits from:
391
+ // stage-token for `layout.recompute.end` with before/after page
392
+ // counts". Same `bus.isEnabled("layout")` guard above makes this
393
+ // zero-cost when the D1 substrate is off.
394
+ telemetryBus.emitLazy("layout", () => ({
395
+ type: "layout.recompute.end",
396
+ payload: {
397
+ layoutEngineVersion: LAYOUT_ENGINE_VERSION,
398
+ reasonKind,
399
+ scope,
400
+ pageCountBefore,
401
+ pageCountAfter,
402
+ durationMs,
403
+ stageTokenId: String(++recomputeStageTokenSeq),
404
+ },
405
+ }));
406
+ // Refactor/05 Slice 6 (2026-04-22) — layer-05 projector emit site.
407
+ // Paired with the shipped `GeometryDebugEntry` projector in
408
+ // `src/runtime/geometry/projector.ts`. The emit carries the same
409
+ // scope + durationMs the layout event uses, plus the layout
410
+ // revision so a downstream consumer can correlate a geometry
411
+ // snapshot with the layout pass that produced it.
412
+ // Off-critical-path: only fires when the `layout` channel is
413
+ // enabled (same guard above). `emitLazy` means zero cost otherwise.
414
+ // Perf bench (typing-latency large-tables P50) confirmed within
415
+ // 1% of baseline before/after wiring this emit.
416
+ telemetryBus.emitLazy("layout", () => ({
417
+ type: "geometry.projected",
418
+ payload: {
419
+ layoutEngineVersion: LAYOUT_ENGINE_VERSION,
420
+ reasonKind,
421
+ scope,
422
+ pageCountBefore,
423
+ pageCountAfter,
424
+ durationMs,
425
+ },
426
+ }));
427
+ }
428
+
429
+ // Refactor/04 post-Slice-4 — monotonic stage-token counter for the
430
+ // engine recompute boundary. Used by `emitRecomputeCompleted`'s
431
+ // `layout.recompute.end` event to give every emission a stable
432
+ // `stageTokenId` the L3 runner can correlate across boundaries.
433
+ let recomputeStageTokenSeq = 0;
434
+
305
435
  function fullRebuild(
306
436
  input: LayoutEngineQueryInput,
307
437
  reason?: LayoutInvalidationReason,
@@ -310,6 +440,12 @@ export function createLayoutEngine(
310
440
  // back to the invalidation reason that triggered it so we can later
311
441
  // narrow specific kinds (numbering/section) without guessing.
312
442
  recordFullRebuildReason(reason?.kind ?? "unknown");
443
+ // Refactor/04 Slice 3 — sample start-time only when the layout
444
+ // telemetry channel is on so the cold path pays nothing for the
445
+ // measurement when telemetry is disabled.
446
+ const telemetryOn = telemetryBus?.isEnabled("layout") ?? false;
447
+ const recomputeStart = telemetryOn ? telemetryNow() : 0;
448
+ const pageCountBeforeRecompute = previousPageCount;
313
449
  const document = input.document;
314
450
  const mainSurface = createEditorSurfaceSnapshot(
315
451
  document,
@@ -329,6 +465,7 @@ export function createLayoutEngine(
329
465
  mainSurface,
330
466
  pages,
331
467
  pageStack.splits,
468
+ pageStack.columnByBlockIdByPageIndex,
332
469
  );
333
470
  // P8.1b — merge per-note fragments (regionKind: "footnote-area") into the
334
471
  // main fragments map so buildPageGraph sees them alongside body fragments.
@@ -354,11 +491,14 @@ export function createLayoutEngine(
354
491
  const formatting = buildResolvedFormattingState(document, mainSurface);
355
492
 
356
493
  const currentPageCount = resolveTotalPageCount(pages);
357
- let pageCountDelta: { previous: number; current: number } | undefined;
358
- if (currentPageCount !== previousPageCount) {
359
- pageCountDelta = { previous: previousPageCount, current: currentPageCount };
360
- previousPageCount = currentPageCount;
361
- }
494
+ // Compute the delta only; `previousPageCount` is mutated AFTER all
495
+ // emits so every emission reads the pre-commit value through the
496
+ // `pageCountDelta` local. `incrementalRelayout` mirrors this order
497
+ // exactly — keep the two paths in sync when editing (reviewer N3).
498
+ const pageCountDelta: { previous: number; current: number } | undefined =
499
+ currentPageCount !== previousPageCount
500
+ ? { previous: previousPageCount, current: currentPageCount }
501
+ : undefined;
362
502
 
363
503
  // MUST publish cache before emit: re-entrant getPageGraph() calls from
364
504
  // subscribers during emit would otherwise trigger runaway rebuilds.
@@ -404,6 +544,20 @@ export function createLayoutEngine(
404
544
  ...(pageCountDelta ? { pageCountDelta } : {}),
405
545
  });
406
546
 
547
+ if (telemetryOn) {
548
+ emitRecomputeCompleted(
549
+ "full",
550
+ reason?.kind ?? "full",
551
+ pageCountBeforeRecompute,
552
+ currentPageCount,
553
+ telemetryNow() - recomputeStart,
554
+ );
555
+ }
556
+
557
+ // Consolidate the `previousPageCount` update at the END of the
558
+ // recompute path, after all emits. Mirrored in `incrementalRelayout`.
559
+ previousPageCount = currentPageCount;
560
+
407
561
  return graph;
408
562
  }
409
563
 
@@ -414,6 +568,9 @@ export function createLayoutEngine(
414
568
  const priorGraph = cachedGraph;
415
569
  const range = pending.result.dirtyPageRange;
416
570
  if (!priorGraph || !range) return null;
571
+ const telemetryOn = telemetryBus?.isEnabled("layout") ?? false;
572
+ const recomputeStart = telemetryOn ? telemetryNow() : 0;
573
+ const pageCountBeforeRecompute = previousPageCount;
417
574
  const firstDirty = range.firstPageIndex;
418
575
  if (firstDirty < 0 || firstDirty >= priorGraph.pages.length) return null;
419
576
 
@@ -480,6 +637,7 @@ export function createLayoutEngine(
480
637
  mainSurface,
481
638
  freshSnapshotsToRebuild,
482
639
  freshResult.splits,
640
+ freshResult.columnByBlockIdByPageIndex,
483
641
  );
484
642
  // P8.1b — merge per-note fragments into the fresh fragments map.
485
643
  const freshFragmentsByPageIndex = new Map(freshBodyFragmentsByPageIndex);
@@ -510,16 +668,21 @@ export function createLayoutEngine(
510
668
  const currentPageCount = resolveTotalPageCount(
511
669
  deriveDocumentPageSnapshots(splicedGraph),
512
670
  );
513
- let pageCountDelta: { previous: number; current: number } | undefined;
514
- if (currentPageCount !== previousPageCount) {
515
- pageCountDelta = { previous: previousPageCount, current: currentPageCount };
671
+ // Compute the delta only; `previousPageCount` is mutated AFTER all
672
+ // emits so every emission reads the pre-commit value through the
673
+ // `pageCountDelta` local. Mirrors `fullRebuild` above keep the two
674
+ // paths in sync when editing (reviewer N3).
675
+ const pageCountDelta: { previous: number; current: number } | undefined =
676
+ currentPageCount !== previousPageCount
677
+ ? { previous: previousPageCount, current: currentPageCount }
678
+ : undefined;
679
+ if (pageCountDelta) {
516
680
  emit({
517
681
  kind: "page_count_changed",
518
682
  revision: splicedGraph.revision,
519
- previousPageCount,
520
- currentPageCount,
683
+ previousPageCount: pageCountDelta.previous,
684
+ currentPageCount: pageCountDelta.current,
521
685
  });
522
- previousPageCount = currentPageCount;
523
686
  }
524
687
 
525
688
  if (dirtyFamilies.length > 0) {
@@ -573,6 +736,21 @@ export function createLayoutEngine(
573
736
  splicedGraph,
574
737
  firstDirty,
575
738
  );
739
+
740
+ if (telemetryOn) {
741
+ emitRecomputeCompleted(
742
+ "bounded",
743
+ pending.reason.kind,
744
+ pageCountBeforeRecompute,
745
+ currentPageCount,
746
+ telemetryNow() - recomputeStart,
747
+ );
748
+ }
749
+
750
+ // Consolidate the `previousPageCount` update at the END of the
751
+ // recompute path, after all emits. Mirrored in `fullRebuild`.
752
+ previousPageCount = currentPageCount;
753
+
576
754
  return splicedGraph;
577
755
  }
578
756