@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,686 @@
1
+ // @internal — Debug projector.
2
+ //
3
+ // Composes already-shipped DocumentRuntime reads into one typed
4
+ // `DebugInspectorSnapshot`. Consumed in two places:
5
+ //
6
+ // 1. The harness dev drawer — `HarnessDebugPanel` retargets off its
7
+ // current per-snapshot props onto a single `DebugInspectorSnapshot`
8
+ // read here.
9
+ // 2. The Phase 2 `services/debug/` Railway service — the hidden
10
+ // `/session/[id]` page exposes `window.__debug.captureDebugSnapshot(query)`
11
+ // that calls this function on the mounted editor's runtime; agents
12
+ // fetch the same JSON through the HTTPS API.
13
+ //
14
+ // Priority ordering (user intent 2026-04-20):
15
+ // 1. Scope debugging — drives `ScopeDebugEntry[]` with the three-way
16
+ // source discrimination (`marker-backed` | `overlay-only` | `detached`).
17
+ // 2. OOXML audit — surfaced via `health.warnings` + `health.compatibility`
18
+ // and through the Phase 2 service's scenarios.
19
+ // 3. Rendering — secondary; `layout` is minimal in Phase 0.
20
+ //
21
+ // Perf note: read-only. No DOM measurement. No PM state inspection. Every
22
+ // read is an already-shipped runtime method. See CLAUDE.md perf invariants
23
+ // 1, 4, 7.
24
+ //
25
+ // F-7 / api-channel scope: every runtime read below goes through `safeCall`
26
+ // (see bottom of file) which invokes the method directly on the runtime
27
+ // object. `api`-channel emissions come from the `wrapRefForTelemetry` Proxy
28
+ // applied at `src/ui/WordReviewEditor.tsx` around the `useImperativeHandle`
29
+ // ref — NOT from the runtime itself. So building a DebugInspectorSnapshot
30
+ // never emits `api` events. This is architectural, not a bug: the debug
31
+ // service's own internal reads must not pollute the trace that records
32
+ // agent-origin method calls. See
33
+ // `services/debug/docs/substrate-code-review-2026-04-20.md` §F-7.
34
+
35
+ import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
36
+ import type { DocumentRuntime } from "../document-runtime.ts";
37
+ import { collectScopeLocations } from "../workflow/scope-resolver.ts";
38
+ import { projectWorkflowDebugEntry } from "../workflow/projector.ts";
39
+ import type {
40
+ EditorWarning,
41
+ ScopeQueryResult,
42
+ WorkflowOverlay,
43
+ WorkflowScope,
44
+ } from "../../api/public-types.ts";
45
+ import type {
46
+ DebugInspectorHealthSection,
47
+ DebugInspectorLayoutSection,
48
+ DebugInspectorQuery,
49
+ DebugInspectorReviewSection,
50
+ DebugInspectorSelectionSection,
51
+ DebugInspectorSessionSection,
52
+ DebugInspectorSnapshot,
53
+ DebugInspectorWorkflowSection,
54
+ RuntimeTelemetryEvent,
55
+ ScopeDebugEntry,
56
+ ScopeDebugSource,
57
+ ScopeMarkerState,
58
+ } from "./types.ts";
59
+ import type { TelemetryBus } from "./telemetry-bus.ts";
60
+ import { buildFormattingDebugEntry } from "../formatting/projector.ts";
61
+ import { buildLayoutDebugEntry } from "../layout/projector.ts";
62
+ import { createGeometryFacet } from "../geometry/geometry-facet.ts";
63
+ import { buildGeometryDebugEntry } from "../geometry/projector.ts";
64
+
65
+ /**
66
+ * Optional `TelemetryBus` accessor — when passed, the projector pulls the
67
+ * recent event tail and the current channel state into the snapshot. When
68
+ * absent the snapshot omits `events` / `channels` (Phase 0 callers).
69
+ */
70
+ export interface BuildDebugInspectorSnapshotOptions {
71
+ readonly bus?: TelemetryBus;
72
+ }
73
+
74
+ export function buildDebugInspectorSnapshot(
75
+ runtime: DocumentRuntime,
76
+ query?: DebugInspectorQuery,
77
+ options?: BuildDebugInspectorSnapshotOptions,
78
+ ): DebugInspectorSnapshot {
79
+ const partialFallbacks: string[] = [];
80
+
81
+ const session = projectSession(runtime, partialFallbacks);
82
+
83
+ const warnings = safeCall(runtime, (r) => r.getWarnings(), [], partialFallbacks, "warnings");
84
+ const compatibility = safeCall(
85
+ runtime,
86
+ (r) => r.getCompatibilityReport(),
87
+ null,
88
+ partialFallbacks,
89
+ "compatibility",
90
+ );
91
+ const guard = safeCall(
92
+ runtime,
93
+ (r) => r.getInteractionGuardSnapshot(),
94
+ null,
95
+ partialFallbacks,
96
+ "interaction-guard",
97
+ );
98
+ const fatalError = extractFatalError(runtime, partialFallbacks);
99
+ const protection = safeCall(
100
+ runtime,
101
+ (r) => r.getProtectionSnapshot(),
102
+ null,
103
+ partialFallbacks,
104
+ "protection",
105
+ );
106
+
107
+ const health: DebugInspectorHealthSection = {
108
+ compatibility: compatibility ?? emptyCompatibilityReport(),
109
+ warnings,
110
+ blockedReasons: guard?.blockedReasons ?? [],
111
+ fatalError,
112
+ readOnly:
113
+ protection?.enforcementActive === true && protection?.editType === "readOnly",
114
+ };
115
+
116
+ const context = safeCall(
117
+ runtime,
118
+ (r) => r.getRuntimeContextAnalytics(),
119
+ null,
120
+ partialFallbacks,
121
+ "context-analytics",
122
+ );
123
+
124
+ const selection = projectSelection(runtime, partialFallbacks);
125
+
126
+ const overlay = safeCall(
127
+ runtime,
128
+ (r) => r.getWorkflowOverlay(),
129
+ null,
130
+ partialFallbacks,
131
+ "workflow-overlay",
132
+ );
133
+ const scopeResults = safeCall(
134
+ runtime,
135
+ (r) => r.queryScopes(),
136
+ [] as ScopeQueryResult[],
137
+ partialFallbacks,
138
+ "query-scopes",
139
+ );
140
+ const markerBacked = safeCall(
141
+ runtime,
142
+ (r) => r.getMarkerBackedScopeIds(),
143
+ new Set<string>(),
144
+ partialFallbacks,
145
+ "marker-backed-ids",
146
+ );
147
+ const document = safeGetDocument(runtime, partialFallbacks);
148
+ const markerLocations = document
149
+ ? collectScopeLocations(document)
150
+ : new Map<string, { startPos?: number; endPos?: number }>();
151
+
152
+ const scopes = projectScopes({
153
+ runtime,
154
+ scopeResults,
155
+ overlay,
156
+ markerBackedScopeIds: markerBacked,
157
+ markerLocations,
158
+ warnings,
159
+ scopeFilter: query?.scopeFilter,
160
+ partialFallbacks,
161
+ });
162
+
163
+ const scopeChannelOn = options?.bus?.getChannels?.().scope ?? false;
164
+ const warningChannelOn = options?.bus?.getChannels?.().warning ?? false;
165
+ const includeDebugEntry = scopeChannelOn || warningChannelOn;
166
+ const workflow = projectWorkflow(
167
+ overlay,
168
+ scopes,
169
+ includeDebugEntry ? runtime : null,
170
+ partialFallbacks,
171
+ );
172
+ const review = projectReview(runtime, partialFallbacks);
173
+ const layout = projectLayout(runtime, partialFallbacks);
174
+
175
+ const events: readonly RuntimeTelemetryEvent[] | undefined =
176
+ query?.includeEvents && options?.bus
177
+ ? options.bus.tailAll(query.maxEvents ?? 64)
178
+ : query?.includeEvents
179
+ ? []
180
+ : undefined;
181
+
182
+ // Phase C1 — formatting projector gate.
183
+ // Channel-gated + document-dependent. Zero cost when off.
184
+ const formatting =
185
+ options?.bus?.isEnabled("formatting") && document
186
+ ? buildFormattingDebugEntry({ doc: document })
187
+ : undefined;
188
+
189
+ // Refactor/04 Slice 3 — layout projector gate. Channel-gated; reads
190
+ // existing facet state only (no pagination triggered). Zero cost when
191
+ // off.
192
+ const layoutDebug = options?.bus?.isEnabled("layout")
193
+ ? buildLayoutDebugEntry({
194
+ facet: runtime.getLayoutFacet(),
195
+ // Task 5 — surface the compat-input ledger when the document
196
+ // envelope is available. When the debug path runs against a
197
+ // runtime with no canonical fallback, `document` is undefined
198
+ // and the projector omits `compatInputs`.
199
+ ...(document?.subParts?.settings
200
+ ? { settings: document.subParts.settings }
201
+ : {}),
202
+ })
203
+ : undefined;
204
+
205
+ // Refactor/05 Slice 6 closure gap — `DebugInspectorSnapshot.geometry?`
206
+ // was declared in v35 but `build-debug-inspector-snapshot.ts` never
207
+ // called `buildGeometryDebugEntry`, so the declared field was always
208
+ // `undefined` even with the `layout` channel on. Wiring it here closes
209
+ // that closure gap. The mounted shell owns the long-lived geometry
210
+ // facet via its render kernel; this debug-path instance is a
211
+ // lightweight facet constructed on demand from the runtime's layout
212
+ // facet (no kernel → Slice-1-proxy path), which is sufficient for the
213
+ // projector to read frame-independent fields (`pageCount`, viewport
214
+ // scaffolding, and the sample-block id projection). Channel-gated so
215
+ // cold path cost stays zero. When `refactor/05` follow-up exposes
216
+ // `runtime.getGeometryFacet()`, swap to that handle and drop the
217
+ // on-demand factory call.
218
+ const geometry = options?.bus?.isEnabled("layout")
219
+ ? buildGeometryDebugEntry({
220
+ facet: createGeometryFacet({ layoutFacet: runtime.getLayoutFacet() }),
221
+ pageCount: runtime.getLayoutFacet().getPageCount(),
222
+ })
223
+ : undefined;
224
+
225
+ const snapshot: DebugInspectorSnapshot = {
226
+ schemaVersion: 1,
227
+ generatedAtUtc: new Date().toISOString(),
228
+ session,
229
+ health,
230
+ context,
231
+ selection,
232
+ workflow,
233
+ scopes,
234
+ review,
235
+ layout,
236
+ ...(events !== undefined ? { events } : {}),
237
+ ...(query?.includeTextProjection ? { textProjection: null } : {}),
238
+ ...(partialFallbacks.length > 0 ? { partialFallbacks } : {}),
239
+ ...(options?.bus ? { channels: options.bus.getChannels() } : {}),
240
+ ...(formatting !== undefined ? { formatting } : {}),
241
+ ...(layoutDebug !== undefined ? { layoutDebug } : {}),
242
+ ...(geometry !== undefined ? { geometry } : {}),
243
+ };
244
+
245
+ return snapshot;
246
+ }
247
+
248
+ /* -------------------------------------------------------------------------- */
249
+
250
+ function projectSession(
251
+ runtime: DocumentRuntime,
252
+ fallbacks: string[],
253
+ ): DebugInspectorSessionSection {
254
+ const render = safeCall(
255
+ runtime,
256
+ (r) => r.getRenderSnapshot(),
257
+ null,
258
+ fallbacks,
259
+ "render-snapshot-session",
260
+ ) as {
261
+ documentId?: string;
262
+ sessionId?: string;
263
+ isDirty?: boolean;
264
+ documentMode?: string;
265
+ activeStory?: DebugInspectorSessionSection["activeStory"];
266
+ } | null;
267
+ const viewState = safeCall(
268
+ runtime,
269
+ (r) => r.getViewState(),
270
+ null,
271
+ fallbacks,
272
+ "view-state",
273
+ ) as {
274
+ workspaceMode?: "canvas" | "page";
275
+ viewMode?: string;
276
+ measurementFidelity?: string | null;
277
+ } | null;
278
+ const pageLayout = safeCall(
279
+ runtime,
280
+ (r) => r.getPageLayoutSnapshot(),
281
+ null,
282
+ fallbacks,
283
+ "page-layout",
284
+ ) as { currentPage?: number } | null;
285
+ const exportBlocked =
286
+ (safeCall(
287
+ runtime,
288
+ (r) => r.getCompatibilityReport(),
289
+ null,
290
+ fallbacks,
291
+ "compat-for-session",
292
+ ) as { blockExport?: boolean } | null)?.blockExport === true;
293
+
294
+ return {
295
+ documentId: render?.documentId ?? "",
296
+ sessionId: render?.sessionId ?? "",
297
+ dirty: render?.isDirty ?? false,
298
+ exportBlocked: exportBlocked ?? false,
299
+ activeStory: render?.activeStory ?? { kind: "main" },
300
+ workspaceMode: viewState?.workspaceMode ?? "canvas",
301
+ viewMode: viewState?.viewMode ?? "editing",
302
+ documentMode: render?.documentMode ?? "editing",
303
+ measurementFidelity: viewState?.measurementFidelity ?? null,
304
+ currentPage: pageLayout?.currentPage ?? null,
305
+ };
306
+ }
307
+
308
+ function projectSelection(
309
+ runtime: DocumentRuntime,
310
+ fallbacks: string[],
311
+ ): DebugInspectorSelectionSection {
312
+ const render = safeCall(runtime, (r) => r.getRenderSnapshot(), null, fallbacks, "render-snapshot");
313
+ const anchor =
314
+ (render as { selection?: { activeRange?: unknown } } | null)?.selection?.activeRange ??
315
+ null;
316
+ const location = anchor
317
+ ? safeCall(
318
+ runtime,
319
+ (r) => r.getLocationForAnchor(anchor as never),
320
+ null,
321
+ fallbacks,
322
+ "location-for-anchor",
323
+ )
324
+ : safeCall(
325
+ runtime,
326
+ (r) => r.getCurrentLocation(),
327
+ null,
328
+ fallbacks,
329
+ "current-location",
330
+ );
331
+ return { anchor: anchor as never, location };
332
+ }
333
+
334
+ interface ProjectScopesInput {
335
+ runtime: DocumentRuntime;
336
+ scopeResults: ScopeQueryResult[];
337
+ overlay: WorkflowOverlay | null;
338
+ markerBackedScopeIds: ReadonlySet<string>;
339
+ markerLocations: Map<string, { startPos?: number; endPos?: number }>;
340
+ warnings: readonly EditorWarning[];
341
+ scopeFilter: DebugInspectorQuery["scopeFilter"];
342
+ partialFallbacks: string[];
343
+ }
344
+
345
+ function projectScopes(input: ProjectScopesInput): ScopeDebugEntry[] {
346
+ const {
347
+ runtime,
348
+ scopeResults,
349
+ overlay,
350
+ markerBackedScopeIds,
351
+ markerLocations,
352
+ warnings,
353
+ scopeFilter,
354
+ partialFallbacks,
355
+ } = input;
356
+
357
+ const overlayById = new Map<string, WorkflowScope>();
358
+ for (const scope of overlay?.scopes ?? []) {
359
+ overlayById.set(scope.scopeId, scope);
360
+ }
361
+ const warningByScope = new Map<string, EditorWarning>();
362
+ for (const warning of warnings) {
363
+ const scopeId = (warning as { scopeId?: string }).scopeId;
364
+ if (scopeId && !warningByScope.has(scopeId)) {
365
+ warningByScope.set(scopeId, warning);
366
+ }
367
+ }
368
+
369
+ const entries: ScopeDebugEntry[] = [];
370
+ const seen = new Set<string>();
371
+
372
+ const allIds = new Set<string>([
373
+ ...scopeResults.map((r) => r.scope.scopeId),
374
+ ...(overlay?.scopes.map((s) => s.scopeId) ?? []),
375
+ ]);
376
+
377
+ for (const scopeId of allIds) {
378
+ if (seen.has(scopeId)) continue;
379
+ seen.add(scopeId);
380
+
381
+ const queryResult = scopeResults.find((r) => r.scope.scopeId === scopeId) ?? null;
382
+ const overlayScope = overlayById.get(scopeId) ?? null;
383
+ const marker = markerLocations.get(scopeId);
384
+ const markerState: ScopeMarkerState = deriveMarkerState(marker);
385
+ const liveScope = safeCall(
386
+ runtime,
387
+ (r) => r.getScope(scopeId),
388
+ null,
389
+ partialFallbacks,
390
+ `get-scope:${scopeId}`,
391
+ );
392
+
393
+ const liveAnchor =
394
+ liveScope?.anchor ??
395
+ overlayScope?.anchor ??
396
+ ({ kind: "detached", lastKnownRange: { from: 0, to: 0 }, reason: "deleted" } as const);
397
+ const isLiveDetached =
398
+ liveAnchor !== null &&
399
+ typeof liveAnchor === "object" &&
400
+ (liveAnchor as { kind?: string }).kind === "detached";
401
+
402
+ let source: ScopeDebugSource;
403
+ if (isLiveDetached) {
404
+ source = "detached";
405
+ } else if (markerBackedScopeIds.has(scopeId) && markerState === "complete") {
406
+ source = "marker-backed";
407
+ } else if (markerBackedScopeIds.has(scopeId)) {
408
+ source = "detached";
409
+ } else {
410
+ source = "overlay-only";
411
+ }
412
+
413
+ const detachedReason = isLiveDetached
414
+ ? ((liveAnchor as { reason?: ScopeDebugEntry["detachedReason"] }).reason ?? "deleted")
415
+ : source === "detached"
416
+ ? "invalidatedByStructureChange"
417
+ : undefined;
418
+
419
+ const trusted = source === "marker-backed" || source === "overlay-only";
420
+
421
+ const location = safeCall(
422
+ runtime,
423
+ (r) => r.getLocationForAnchor(liveAnchor as never, overlayScope?.storyTarget),
424
+ null,
425
+ partialFallbacks,
426
+ `location-for-scope:${scopeId}`,
427
+ );
428
+
429
+ const warning = warningByScope.get(scopeId) ?? null;
430
+
431
+ const entry: ScopeDebugEntry = {
432
+ scopeId,
433
+ label: overlayScope?.label ?? queryResult?.scope.label,
434
+ mode: (liveScope?.mode ?? overlayScope?.mode ?? "comment") as ScopeDebugEntry["mode"],
435
+ visibility: (overlayScope?.visibility ?? "visible") as ScopeDebugEntry["visibility"],
436
+ storyTarget: overlayScope?.storyTarget ?? { kind: "main" },
437
+ source,
438
+ liveAnchor: liveAnchor as ScopeDebugEntry["liveAnchor"],
439
+ storedOverlayAnchor: overlayScope?.anchor ?? null,
440
+ trusted,
441
+ markerState,
442
+ ...(detachedReason ? { detachedReason } : {}),
443
+ ...(warning ? { warningId: warning.warningId } : {}),
444
+ ...(isLiveDetached
445
+ ? {
446
+ lastKnownRange: (liveAnchor as { lastKnownRange?: { from: number; to: number } })
447
+ .lastKnownRange,
448
+ }
449
+ : {}),
450
+ location,
451
+ ...(overlayScope?.metadata && overlayScope.metadata.length > 0
452
+ ? {
453
+ // F-15: iterate every metadata entry; previously we dropped entries
454
+ // past index 0, which broke scopes with multiple metadata refs.
455
+ metadataLookups: overlayScope.metadata.map((m) => ({
456
+ ...(m?.key ? { metadataId: m.key } : {}),
457
+ ...(m?.storageRef ? { storageRef: String(m.storageRef) } : {}),
458
+ ...(m?.metadataVersion !== undefined
459
+ ? { metadataVersion: m.metadataVersion }
460
+ : {}),
461
+ })),
462
+ }
463
+ : {}),
464
+ };
465
+
466
+ if (matchesScopeFilter(entry, scopeFilter)) {
467
+ entries.push(entry);
468
+ }
469
+ }
470
+
471
+ // F-8: sort by doc-order (first-marker start position) when the scope is
472
+ // marker-backed in the document. Overlay-only / detached scopes lack a
473
+ // live position; push them to the end and keep them deterministically
474
+ // ordered by scopeId. This matches the plan's "top-to-bottom traversal"
475
+ // assumption in doc-debug-stack.md §diagnostic isolation.
476
+ const positionOf = (scopeId: string): number | undefined =>
477
+ markerLocations.get(scopeId)?.startPos;
478
+ entries.sort((a, b) => {
479
+ const aPos = positionOf(a.scopeId);
480
+ const bPos = positionOf(b.scopeId);
481
+ if (aPos !== undefined && bPos !== undefined) return aPos - bPos;
482
+ if (aPos !== undefined) return -1;
483
+ if (bPos !== undefined) return 1;
484
+ return a.scopeId.localeCompare(b.scopeId);
485
+ });
486
+ return entries;
487
+ }
488
+
489
+ function projectWorkflow(
490
+ overlay: WorkflowOverlay | null,
491
+ scopes: readonly ScopeDebugEntry[],
492
+ runtimeForDebugEntry: DocumentRuntime | null,
493
+ fallbacks: string[],
494
+ ): DebugInspectorWorkflowSection {
495
+ let markerBackedCount = 0;
496
+ let overlayOnlyCount = 0;
497
+ let detachedCount = 0;
498
+ for (const s of scopes) {
499
+ if (s.source === "marker-backed") markerBackedCount += 1;
500
+ else if (s.source === "overlay-only") overlayOnlyCount += 1;
501
+ else detachedCount += 1;
502
+ }
503
+
504
+ const base: DebugInspectorWorkflowSection = {
505
+ overlay,
506
+ activeWorkItemId: overlay?.activeWorkItemId ?? null,
507
+ metadataPersistenceMode: overlay?.metadataPersistence ?? null,
508
+ candidateCount: overlay?.candidates?.length ?? 0,
509
+ scopeCount: scopes.length,
510
+ markerBackedCount,
511
+ overlayOnlyCount,
512
+ detachedCount,
513
+ };
514
+
515
+ if (!runtimeForDebugEntry) return base;
516
+
517
+ const metadataSnapshot = safeCall(
518
+ runtimeForDebugEntry,
519
+ (r) => r.getWorkflowMetadataSnapshot(),
520
+ null,
521
+ fallbacks,
522
+ "workflow-metadata-snapshot",
523
+ );
524
+ const markupSnapshot = safeCall(
525
+ runtimeForDebugEntry,
526
+ (r) => r.getWorkflowMarkupSnapshot(),
527
+ null,
528
+ fallbacks,
529
+ "workflow-markup-snapshot",
530
+ );
531
+ const guard = safeCall(
532
+ runtimeForDebugEntry,
533
+ (r) => r.getInteractionGuardSnapshot(),
534
+ null,
535
+ fallbacks,
536
+ "interaction-guard-snapshot",
537
+ );
538
+
539
+ const debugEntry = projectWorkflowDebugEntry({
540
+ overlay,
541
+ metadataDefinitions: metadataSnapshot?.definitions ?? [],
542
+ metadataEntries: metadataSnapshot?.entries ?? [],
543
+ protectedRangeCount: markupSnapshot?.protectedRanges.length ?? 0,
544
+ scopeCounts: {
545
+ markerBacked: markerBackedCount,
546
+ overlayOnly: overlayOnlyCount,
547
+ detached: detachedCount,
548
+ },
549
+ guard,
550
+ });
551
+
552
+ return { ...base, debugEntry };
553
+ }
554
+
555
+ function projectReview(
556
+ runtime: DocumentRuntime,
557
+ fallbacks: string[],
558
+ ): DebugInspectorReviewSection {
559
+ const render = safeCall(runtime, (r) => r.getRenderSnapshot(), null, fallbacks, "render-for-review");
560
+ const comments =
561
+ (render as { comments?: { openCommentIds?: string[]; detachedCommentIds?: string[] } } | null)
562
+ ?.comments ?? null;
563
+ const tracked =
564
+ (render as {
565
+ trackedChanges?: { pendingChangeIds?: string[]; detachedChangeIds?: string[] };
566
+ } | null)?.trackedChanges ?? null;
567
+ return {
568
+ unresolvedCommentCount: comments?.openCommentIds?.length ?? 0,
569
+ pendingRevisionCount: tracked?.pendingChangeIds?.length ?? 0,
570
+ detachedCommentCount: comments?.detachedCommentIds?.length ?? 0,
571
+ detachedRevisionCount: tracked?.detachedChangeIds?.length ?? 0,
572
+ };
573
+ }
574
+
575
+ function projectLayout(
576
+ runtime: DocumentRuntime,
577
+ fallbacks: string[],
578
+ ): DebugInspectorLayoutSection {
579
+ const pageLayout = safeCall(
580
+ runtime,
581
+ (r) => r.getPageLayoutSnapshot(),
582
+ null,
583
+ fallbacks,
584
+ "page-layout-summary",
585
+ );
586
+ const sections = safeCall(runtime, (r) => r.getSections(), [], fallbacks, "sections");
587
+ const stories = safeCall(
588
+ runtime,
589
+ (r) => r.getDocumentTextStream(),
590
+ [],
591
+ fallbacks,
592
+ "document-text-stream",
593
+ );
594
+ return {
595
+ pageCount: (pageLayout as { pages?: unknown[] } | null)?.pages?.length ?? null,
596
+ sectionCount: sections.length,
597
+ storyCount: stories.length,
598
+ };
599
+ }
600
+
601
+ /* -------------------------------------------------------------------------- */
602
+
603
+ function deriveMarkerState(
604
+ marker: { startPos?: number; endPos?: number } | undefined,
605
+ ): ScopeMarkerState {
606
+ if (!marker) return "missing";
607
+ const hasStart = marker.startPos !== undefined;
608
+ const hasEnd = marker.endPos !== undefined;
609
+ if (hasStart && hasEnd) return "complete";
610
+ if (hasStart) return "start-only";
611
+ if (hasEnd) return "end-only";
612
+ return "missing";
613
+ }
614
+
615
+ function matchesScopeFilter(
616
+ entry: ScopeDebugEntry,
617
+ filter: DebugInspectorQuery["scopeFilter"],
618
+ ): boolean {
619
+ if (!filter) return true;
620
+ if (filter.scopeIds && !filter.scopeIds.includes(entry.scopeId)) return false;
621
+ if (filter.modes && !filter.modes.includes(entry.mode)) return false;
622
+ if (filter.source) {
623
+ const allowed = Array.isArray(filter.source) ? filter.source : [filter.source];
624
+ if (!allowed.includes(entry.source)) return false;
625
+ }
626
+ if (filter.onlyDetached && entry.source !== "detached") return false;
627
+ return true;
628
+ }
629
+
630
+ function safeCall<T>(
631
+ runtime: DocumentRuntime,
632
+ fn: (r: DocumentRuntime) => T,
633
+ fallback: T,
634
+ fallbacks: string[],
635
+ tag: string,
636
+ ): T {
637
+ try {
638
+ return fn(runtime);
639
+ } catch (err) {
640
+ fallbacks.push(`${tag}:${(err as Error).message ?? "error"}`);
641
+ return fallback;
642
+ }
643
+ }
644
+
645
+ function safeGetDocument(
646
+ runtime: DocumentRuntime,
647
+ fallbacks: string[],
648
+ ): CanonicalDocumentEnvelope | null {
649
+ return safeCall(
650
+ runtime,
651
+ (r) => r.getCanonicalDocument(),
652
+ null,
653
+ fallbacks,
654
+ "canonical-document",
655
+ );
656
+ }
657
+
658
+ function extractFatalError(
659
+ runtime: DocumentRuntime,
660
+ fallbacks: string[],
661
+ ): { code: string; message: string } | null {
662
+ const state = safeCall(
663
+ runtime,
664
+ (r) => r.getViewState() as { fatalError?: { code: string; message: string } | null },
665
+ null,
666
+ fallbacks,
667
+ "fatal-error",
668
+ );
669
+ return state?.fatalError ?? null;
670
+ }
671
+
672
+ // F-14: the previous implementation returned an ad-hoc shape silenced with
673
+ // `as never`. The real CompatibilityReport interface (see
674
+ // `src/api/public-types.ts`) requires reportVersion, generatedAt,
675
+ // blockExport, featureEntries, warnings, errors — all declared here so a
676
+ // downstream destructure doesn't throw on missing fields.
677
+ function emptyCompatibilityReport(): DebugInspectorHealthSection["compatibility"] {
678
+ return {
679
+ reportVersion: "compatibility-report/1",
680
+ generatedAt: new Date().toISOString(),
681
+ blockExport: false,
682
+ featureEntries: [],
683
+ warnings: [],
684
+ errors: [],
685
+ };
686
+ }