@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,690 @@
1
+ /**
2
+ * Layer 06 — Workflow overlay store.
3
+ *
4
+ * Owns the per-runtime workflow state that used to live as top-level
5
+ * closure variables inside `document-runtime.ts`:
6
+ * - workflow overlay (scopes + candidates + work items + active-work-item)
7
+ * - workflow metadata (definitions + entries)
8
+ * - marker-backed scope-id set (derived from canonical document)
9
+ * - shared workflow state (collab round-locked mode)
10
+ * - scope-chrome visibility (local UI state; never collab-replicated)
11
+ *
12
+ * Plus the pure-ish derivations the store alone can safely compute:
13
+ * - `normalizeOverlayForDocument` — converts overlay anchors into
14
+ * normalized range / node / detached shapes given the current
15
+ * canonical document. Cached per (document, overlay) pair.
16
+ * - `buildDetachedScopeWarnings` + `createInvalidatedScopeWarning` —
17
+ * synthesize the `workflow_scope_invalidated` warning when a
18
+ * previously marker-backed scope has been detached.
19
+ * - `mergeDetachedScopeWarnings` — diff desired-vs-existing detached
20
+ * warnings so the runtime can emit precise `warning_added` /
21
+ * `warning_cleared` deltas.
22
+ *
23
+ * What it does NOT own (by design — those belong to the coordinator):
24
+ * - interaction-guard / workflow-scope / workflow-markup snapshot
25
+ * caches (require runtime-scope deps like revisionToken, render
26
+ * snapshot, selection).
27
+ * - blocked-reason composition + scope-matching heuristics (require
28
+ * protection snapshot + viewState.documentMode).
29
+ * - rail / card composition (requires page graph + render frame).
30
+ *
31
+ * This file must satisfy Layer-06 purity contract W9: no imports from
32
+ * `document-runtime.ts`, `render`, `ui*`, `api/v*`, or `io`.
33
+ * Enforced by `scripts/ci-check-workflow-layer-purity.mjs`.
34
+ */
35
+
36
+ import type {
37
+ EditorAnchorProjection,
38
+ ScopeChromeVisibilityState,
39
+ WorkflowMetadataDefinition,
40
+ WorkflowMetadataEntry,
41
+ WorkflowMetadataSnapshot,
42
+ WorkflowOverlay,
43
+ WorkflowScope,
44
+ } from "../../api/public-types.ts";
45
+ import {
46
+ type CanonicalDocumentEnvelope,
47
+ type EditorWarning as InternalEditorWarning,
48
+ } from "../../core/state/editor-state.ts";
49
+ import type { SharedWorkflowState } from "../collab/workflow-shared.ts";
50
+ import type { TelemetryBus } from "../debug/telemetry-bus.ts";
51
+ import { toInternalAnchorProjection } from "../../core/selection/anchor-conversion.ts";
52
+ import { buildDiagnosticFromLegacyWarningCode } from "../diagnostics/build-diagnostic.ts";
53
+ import { collectScopeLocations } from "./scope-resolver.ts";
54
+ import {
55
+ overlayVisibilityPoliciesEqual,
56
+ type OverlayKind,
57
+ type OverlayVisibilityPolicy,
58
+ } from "./visibility-policy.ts";
59
+ import {
60
+ workflowMarkupModePoliciesEqual,
61
+ type WorkflowMarkupModePolicy,
62
+ } from "./markup-mode-policy.ts";
63
+
64
+ export interface OverlayStoreConfig {
65
+ readonly initialOverlay: WorkflowOverlay | null;
66
+ readonly initialMetadataDefinitions: readonly WorkflowMetadataDefinition[];
67
+ readonly initialMetadataEntries: readonly WorkflowMetadataEntry[];
68
+ readonly initialVisibilityPolicies?: readonly OverlayVisibilityPolicy[];
69
+ /** X5 — class-A markup-mode policy. Single record; null = document
70
+ * predates the schema (consumer falls back to class-C default). */
71
+ readonly initialMarkupModePolicy?: WorkflowMarkupModePolicy | null;
72
+ readonly telemetryBus: TelemetryBus;
73
+ }
74
+
75
+ /** Result of `mergeDetachedScopeWarnings` — the existing `warnings`
76
+ * array replaced with the next-consistent state plus a per-warning
77
+ * delta suitable for emitting `warning_added` / `warning_cleared`
78
+ * events at the dispatch site. */
79
+ export interface MergeDetachedWarningsResult {
80
+ readonly nextWarnings: InternalEditorWarning[];
81
+ readonly added: InternalEditorWarning[];
82
+ readonly cleared: Array<{
83
+ readonly warningId: string;
84
+ readonly code: InternalEditorWarning["code"];
85
+ }>;
86
+ }
87
+
88
+ export interface OverlayStore {
89
+ // ---- state getters ----
90
+ getOverlay(): WorkflowOverlay | null;
91
+ getMetadataDefinitions(): readonly WorkflowMetadataDefinition[];
92
+ getMetadataEntries(): readonly WorkflowMetadataEntry[];
93
+ getMarkerBackedScopeIds(): ReadonlySet<string>;
94
+ getSharedWorkflowState(): SharedWorkflowState | null;
95
+ getScopeChromeVisibility(): ScopeChromeVisibilityState;
96
+ /** W10 — class-A visibility policy for a single overlay kind. Returns
97
+ * `null` when no policy has been authored for that kind. */
98
+ getVisibilityPolicy(kind: OverlayKind): OverlayVisibilityPolicy | null;
99
+ /** W10 — full policy set (zero or more entries; never more than one
100
+ * per OverlayKind). Order is deterministic per `OVERLAY_KINDS`. */
101
+ getVisibilityPolicies(): readonly OverlayVisibilityPolicy[];
102
+
103
+ // ---- state mutations (invoked by coordinator dispatch branches) ----
104
+ /** Replace the overlay and sync marker-backed scope set. Invalidates
105
+ * the normalization cache. Returns nothing — the caller is
106
+ * responsible for cache invalidation downstream (guard / scope /
107
+ * markup) and for emitting the matching workflow_* events. */
108
+ replaceOverlay(
109
+ next: WorkflowOverlay | null,
110
+ document: CanonicalDocumentEnvelope,
111
+ ): void;
112
+ replaceMetadataDefinitions(defs: readonly WorkflowMetadataDefinition[]): void;
113
+ replaceMetadataEntries(entries: readonly WorkflowMetadataEntry[]): void;
114
+ replaceSharedWorkflowState(state: SharedWorkflowState | null): void;
115
+ replaceScopeChromeVisibility(visibility: ScopeChromeVisibilityState): void;
116
+ /** W10 — set (or clear) a single overlay kind's visibility policy.
117
+ * Returns `true` when the policy set actually changed, `false` on
118
+ * no-op. Callers emit the matching workflow_* event + persist via
119
+ * customXml export only when this returns `true`. */
120
+ replaceVisibilityPolicy(
121
+ kind: OverlayKind,
122
+ policy: OverlayVisibilityPolicy | null,
123
+ ): boolean;
124
+ /** W10 — bulk replace the policy set (e.g., on reload or collab
125
+ * state sync). Same semantics as per-kind but atomic. */
126
+ replaceVisibilityPolicies(
127
+ policies: readonly OverlayVisibilityPolicy[],
128
+ ): boolean;
129
+ /** W10 — subscribe to any policy-set change (any kind). Listener
130
+ * fires after the store mutation lands, once per settled change.
131
+ * Returns an unsubscribe function. */
132
+ subscribeVisibilityPolicy(listener: () => void): () => void;
133
+ /** W10 — internal signal emitter. Coordinator/runtime invoke this
134
+ * AFTER a successful `replaceVisibilityPolicy` / `replaceVisibilityPolicies`
135
+ * call to notify subscribers. Kept on the store so the policy-notify
136
+ * pipeline stays self-contained (no external bus coupling). */
137
+ notifyVisibilityPolicyChanged(): void;
138
+
139
+ /** X5 — class-A markup-mode policy (single record; null = none). */
140
+ getMarkupModePolicy(): WorkflowMarkupModePolicy | null;
141
+ /** X5 — replace the policy record. Returns `true` on actual change. */
142
+ replaceMarkupModePolicy(policy: WorkflowMarkupModePolicy | null): boolean;
143
+ /** X5 — subscribe to markup-mode policy changes. */
144
+ subscribeMarkupModePolicy(listener: () => void): () => void;
145
+ /** X5 — internal signal emitter for markup-mode changes. */
146
+ notifyMarkupModePolicyChanged(): void;
147
+
148
+ // ---- derivations ----
149
+ /** Normalized overlay for a given document. Returns null when no
150
+ * overlay is set. Uses (document, overlay) reference equality to
151
+ * short-circuit. */
152
+ getNormalizedOverlay(document: CanonicalDocumentEnvelope): WorkflowOverlay | null;
153
+
154
+ /** Structured-clone metadata definitions + entries for API callers
155
+ * so downstream mutations don't leak back into the store. */
156
+ getMetadataSnapshot(): WorkflowMetadataSnapshot;
157
+
158
+ // ---- warning synthesis ----
159
+ /** Pure factory — builds a `workflow_scope_invalidated` warning for
160
+ * a detached scope, including diagnostic + llmMetadata for recovery. */
161
+ createInvalidatedScopeWarning(scope: WorkflowScope): InternalEditorWarning;
162
+
163
+ /** Diff the detached-scope warnings we *want* (one per detached
164
+ * scope in the overlay) against the warnings already in `existing`.
165
+ * Returns the next warning list plus a precise add/clear delta. */
166
+ mergeDetachedScopeWarnings(
167
+ overlay: WorkflowOverlay | null,
168
+ existing: readonly InternalEditorWarning[],
169
+ ): MergeDetachedWarningsResult;
170
+ }
171
+
172
+ /* ---------------------------------------------------------------- *\
173
+ * Private pure helpers (not exported — internal to the store)
174
+ \* ---------------------------------------------------------------- */
175
+
176
+ function workflowAnchorsEqual(
177
+ left: EditorAnchorProjection,
178
+ right: EditorAnchorProjection,
179
+ ): boolean {
180
+ if (left.kind !== right.kind) return false;
181
+ switch (left.kind) {
182
+ case "range":
183
+ return (
184
+ right.kind === "range" &&
185
+ left.from === right.from &&
186
+ left.to === right.to &&
187
+ left.assoc.start === right.assoc.start &&
188
+ left.assoc.end === right.assoc.end
189
+ );
190
+ case "node":
191
+ return right.kind === "node" && left.at === right.at;
192
+ case "detached":
193
+ return (
194
+ right.kind === "detached" &&
195
+ left.reason === right.reason &&
196
+ left.lastKnownRange.from === right.lastKnownRange.from &&
197
+ left.lastKnownRange.to === right.lastKnownRange.to
198
+ );
199
+ default:
200
+ return false;
201
+ }
202
+ }
203
+
204
+ function buildWarningSignature(warning: InternalEditorWarning): string {
205
+ return JSON.stringify({
206
+ code: warning.code,
207
+ severity: warning.severity,
208
+ message: warning.message,
209
+ source: warning.source,
210
+ featureEntryId: warning.featureEntryId ?? null,
211
+ details: warning.details ?? null,
212
+ affectedAnchor: warning.affectedAnchor ?? null,
213
+ });
214
+ }
215
+
216
+ function emitMarkerBackedDelta(
217
+ bus: TelemetryBus,
218
+ prior: ReadonlySet<string>,
219
+ next: ReadonlySet<string>,
220
+ ): void {
221
+ if (!bus.isEnabled("scope")) return;
222
+ const added: string[] = [];
223
+ const removed: string[] = [];
224
+ for (const id of next) if (!prior.has(id)) added.push(id);
225
+ for (const id of prior) if (!next.has(id)) removed.push(id);
226
+ if (added.length === 0 && removed.length === 0) return;
227
+ bus.emit({
228
+ channel: "scope",
229
+ type: "scope.marker_backed_delta",
230
+ t: 0,
231
+ payload: { added, removed, total: next.size },
232
+ });
233
+ }
234
+
235
+ /* ---------------------------------------------------------------- *\
236
+ * Factory
237
+ \* ---------------------------------------------------------------- */
238
+
239
+ export function createOverlayStore(config: OverlayStoreConfig): OverlayStore {
240
+ let overlay: WorkflowOverlay | null = config.initialOverlay
241
+ ? structuredClone(config.initialOverlay)
242
+ : null;
243
+ let metadataDefinitions: WorkflowMetadataDefinition[] = structuredClone(
244
+ [...config.initialMetadataDefinitions],
245
+ );
246
+ let metadataEntries: WorkflowMetadataEntry[] = structuredClone(
247
+ [...config.initialMetadataEntries],
248
+ );
249
+ let markerBackedScopeIds: ReadonlySet<string> = new Set<string>();
250
+ let sharedWorkflowState: SharedWorkflowState | null = null;
251
+ let scopeChromeVisibility: ScopeChromeVisibilityState = { mode: "all" };
252
+ // W10 — class-A visibility policies keyed by OverlayKind. Cloned on
253
+ // read so callers cannot mutate stored records.
254
+ let visibilityPolicies: Map<OverlayKind, OverlayVisibilityPolicy> = new Map(
255
+ (config.initialVisibilityPolicies ?? []).map((policy) => [
256
+ policy.kind,
257
+ structuredClone(policy),
258
+ ]),
259
+ );
260
+ const visibilityPolicyListeners = new Set<() => void>();
261
+ // X5 · markup-mode policy — single record, not per-kind.
262
+ let markupModePolicy: WorkflowMarkupModePolicy | null =
263
+ config.initialMarkupModePolicy
264
+ ? structuredClone(config.initialMarkupModePolicy)
265
+ : null;
266
+ const markupModePolicyListeners = new Set<() => void>();
267
+
268
+ let normalizedOverlayCache:
269
+ | {
270
+ document: CanonicalDocumentEnvelope;
271
+ overlay: WorkflowOverlay;
272
+ normalized: WorkflowOverlay;
273
+ }
274
+ | undefined;
275
+
276
+ function syncMarkerBackedScopeIds(
277
+ document: CanonicalDocumentEnvelope,
278
+ currentOverlay: WorkflowOverlay | null,
279
+ ): void {
280
+ const prior = markerBackedScopeIds;
281
+ const presentScopeIds = new Set(collectScopeLocations(document).keys());
282
+ if (!currentOverlay) {
283
+ markerBackedScopeIds = presentScopeIds;
284
+ emitMarkerBackedDelta(config.telemetryBus, prior, markerBackedScopeIds);
285
+ return;
286
+ }
287
+ const overlayScopeIds = new Set(
288
+ currentOverlay.scopes.map((scope) => scope.scopeId),
289
+ );
290
+ const next = new Set<string>();
291
+ for (const scopeId of markerBackedScopeIds) {
292
+ if (overlayScopeIds.has(scopeId)) {
293
+ next.add(scopeId);
294
+ }
295
+ }
296
+ for (const scopeId of presentScopeIds) {
297
+ if (overlayScopeIds.has(scopeId)) {
298
+ next.add(scopeId);
299
+ }
300
+ }
301
+ markerBackedScopeIds = next;
302
+ emitMarkerBackedDelta(config.telemetryBus, prior, next);
303
+ }
304
+
305
+ function normalizeOverlayForDocument(
306
+ document: CanonicalDocumentEnvelope,
307
+ source: WorkflowOverlay,
308
+ ): WorkflowOverlay {
309
+ if (
310
+ normalizedOverlayCache &&
311
+ normalizedOverlayCache.document === document &&
312
+ normalizedOverlayCache.overlay === source
313
+ ) {
314
+ return normalizedOverlayCache.normalized;
315
+ }
316
+
317
+ const scopeIdCounts = new Map<string, number>();
318
+ for (const scope of source.scopes) {
319
+ scopeIdCounts.set(scope.scopeId, (scopeIdCounts.get(scope.scopeId) ?? 0) + 1);
320
+ }
321
+ const locations = collectScopeLocations(document);
322
+ let changed = false;
323
+ const normalizedScopes = source.scopes.map((scope) => {
324
+ if ((scopeIdCounts.get(scope.scopeId) ?? 0) !== 1) {
325
+ return scope;
326
+ }
327
+ const location = locations.get(scope.scopeId);
328
+ const isMarkerBacked = markerBackedScopeIds.has(scope.scopeId);
329
+ let nextAnchor: EditorAnchorProjection | null = null;
330
+ if (
331
+ location &&
332
+ location.startPos !== undefined &&
333
+ location.endPos !== undefined
334
+ ) {
335
+ // Coord-06 §13d — preserve the scope's original `assoc` when
336
+ // normalizing marker positions. Pre-§13d this hardcoded
337
+ // `{ start: -1, end: 1 }` (fixed), dropping the caller's
338
+ // `{ start: 1, end: -1 }` (greedy — default) or any other
339
+ // variant authored via `CreateScopeFromBlockIdInput.assoc`.
340
+ // Agents pin signature-block / template-slot scopes with
341
+ // `{ -1, 1 }` via the v3 primitive; that pin is meaningless
342
+ // if normalization erases it every render.
343
+ const preservedAssoc: { start: -1 | 1; end: -1 | 1 } =
344
+ scope.anchor.kind === "range"
345
+ ? scope.anchor.assoc
346
+ : { start: -1, end: 1 };
347
+ nextAnchor = {
348
+ kind: "range",
349
+ from: Math.min(location.startPos, location.endPos),
350
+ to: Math.max(location.startPos, location.endPos),
351
+ assoc: preservedAssoc,
352
+ };
353
+ } else if (isMarkerBacked) {
354
+ const lastKnownRange =
355
+ scope.anchor.kind === "range"
356
+ ? { from: scope.anchor.from, to: scope.anchor.to }
357
+ : scope.anchor.kind === "node"
358
+ ? { from: scope.anchor.at, to: scope.anchor.at }
359
+ : scope.anchor.lastKnownRange;
360
+ nextAnchor = {
361
+ kind: "detached",
362
+ reason:
363
+ location &&
364
+ (location.startPos !== undefined || location.endPos !== undefined)
365
+ ? "deleted"
366
+ : "invalidatedByStructureChange",
367
+ lastKnownRange,
368
+ };
369
+ } else {
370
+ return scope;
371
+ }
372
+ if (workflowAnchorsEqual(scope.anchor, nextAnchor)) {
373
+ return scope;
374
+ }
375
+ changed = true;
376
+ return {
377
+ ...scope,
378
+ anchor: nextAnchor,
379
+ };
380
+ });
381
+
382
+ const normalized = changed
383
+ ? { ...source, scopes: normalizedScopes }
384
+ : source;
385
+ normalizedOverlayCache = {
386
+ document,
387
+ overlay: source,
388
+ normalized,
389
+ };
390
+ return normalized;
391
+ }
392
+
393
+ function createInvalidatedScopeWarning(
394
+ scope: WorkflowScope,
395
+ ): InternalEditorWarning {
396
+ const anchor = scope.anchor.kind === "detached" ? scope.anchor : null;
397
+ const subject = scope.label
398
+ ? `Workflow scope "${scope.label}" (${scope.scopeId})`
399
+ : `Workflow scope ${scope.scopeId}`;
400
+ const reasonPhrase =
401
+ anchor?.reason === "deleted"
402
+ ? "its anchored text was deleted"
403
+ : anchor?.reason === "invalidatedByStructureChange"
404
+ ? "document structure changed around it"
405
+ : "its anchor could not be resolved unambiguously";
406
+ const modePhrase =
407
+ scope.mode === "view"
408
+ ? "read-only enforcement"
409
+ : `${scope.mode} enforcement`;
410
+
411
+ return {
412
+ warningId: `warning:workflow-scope-invalidated:${scope.scopeId}`,
413
+ code: "workflow_scope_invalidated",
414
+ severity: "warning",
415
+ message: `${subject} was invalidated because ${reasonPhrase}. Reapply the scope before relying on ${modePhrase}.`,
416
+ source: "runtime",
417
+ // Internal* collapsed to public flat 2026-04-22 (see
418
+ // `docs/plans/cross-layer-coord-02.md §8`). `scope.anchor` is
419
+ // already public flat; no `toInternalAnchorProjection` wrap.
420
+ affectedAnchor: anchor ?? undefined,
421
+ diagnostic: buildDiagnosticFromLegacyWarningCode("workflow_scope_invalidated", {
422
+ diagnosticId: `warning-diag:workflow-scope-invalidated:${scope.scopeId}`,
423
+ technical: {
424
+ message: `${subject} lost its trusted anchor and is now detached.`,
425
+ source: "runtime",
426
+ },
427
+ details: {
428
+ scopeId: scope.scopeId,
429
+ label: scope.label,
430
+ mode: scope.mode,
431
+ reason: anchor?.reason,
432
+ lastKnownRange: anchor?.lastKnownRange,
433
+ storyTarget: scope.storyTarget,
434
+ reapplySuggested: true,
435
+ },
436
+ affectedAnchor: anchor ? scope.anchor : undefined,
437
+ llmMetadata: {
438
+ userSummary: `${subject} is no longer attached to trusted document content. Reapply the scope before relying on it.`,
439
+ remediation: {
440
+ kind: "fallback",
441
+ suggestion:
442
+ "Locate the intended text using warning.details.scopeId and warning.details.lastKnownRange, then call addScope again for the repaired range.",
443
+ },
444
+ recoveryClass: "requires-input",
445
+ echoedInput: {
446
+ scopeId: scope.scopeId,
447
+ lastKnownRange: anchor?.lastKnownRange,
448
+ },
449
+ },
450
+ }),
451
+ details: {
452
+ scopeId: scope.scopeId,
453
+ label: scope.label,
454
+ mode: scope.mode,
455
+ reason: anchor?.reason,
456
+ lastKnownRange: anchor?.lastKnownRange,
457
+ storyTarget: scope.storyTarget,
458
+ reapplySuggested: true,
459
+ actionabilityNote:
460
+ "Resolve the intended text again, then reapply the scope; the previous anchor is no longer trusted.",
461
+ },
462
+ };
463
+ }
464
+
465
+ function mergeDetachedScopeWarnings(
466
+ normalizedOverlay: WorkflowOverlay | null,
467
+ existing: readonly InternalEditorWarning[],
468
+ ): MergeDetachedWarningsResult {
469
+ const detachedScopesById = new Map<string, WorkflowScope>();
470
+ for (const scope of normalizedOverlay?.scopes ?? []) {
471
+ if (scope.anchor.kind === "detached") {
472
+ detachedScopesById.set(scope.scopeId, scope);
473
+ }
474
+ }
475
+
476
+ const retainedWarnings = existing.filter(
477
+ (warning) => warning.code !== "workflow_scope_invalidated",
478
+ );
479
+ const existingDetachedWarnings = existing.filter(
480
+ (warning) => warning.code === "workflow_scope_invalidated",
481
+ );
482
+ const existingById = new Map(
483
+ existingDetachedWarnings.map((warning) => [warning.warningId, warning] as const),
484
+ );
485
+ const desiredById = new Map(
486
+ [...detachedScopesById.values()].map((scope) => {
487
+ const warning = createInvalidatedScopeWarning(scope);
488
+ return [warning.warningId, warning] as const;
489
+ }),
490
+ );
491
+
492
+ const added: InternalEditorWarning[] = [];
493
+ const cleared: Array<{
494
+ warningId: string;
495
+ code: InternalEditorWarning["code"];
496
+ }> = [];
497
+
498
+ for (const [warningId, existingWarning] of existingById) {
499
+ const desiredWarning = desiredById.get(warningId);
500
+ if (!desiredWarning) {
501
+ cleared.push({ warningId, code: existingWarning.code });
502
+ continue;
503
+ }
504
+ if (
505
+ buildWarningSignature(existingWarning) !== buildWarningSignature(desiredWarning)
506
+ ) {
507
+ cleared.push({ warningId, code: existingWarning.code });
508
+ added.push(desiredWarning);
509
+ }
510
+ }
511
+
512
+ for (const [warningId, desiredWarning] of desiredById) {
513
+ if (!existingById.has(warningId)) {
514
+ added.push(desiredWarning);
515
+ }
516
+ }
517
+
518
+ return {
519
+ nextWarnings: [...retainedWarnings, ...desiredById.values()],
520
+ added,
521
+ cleared,
522
+ };
523
+ }
524
+
525
+ /* -------------------- public store surface -------------------- */
526
+
527
+ // Pre-populate the marker-backed set — deferred until after the
528
+ // coordinator first calls `replaceOverlay` with the runtime's
529
+ // initial document; until then the set is empty. The coordinator
530
+ // also invokes `syncInitialMarkerBackedScopeIds` directly from the
531
+ // runtime's constructor so the first getSnapshot() sees a populated
532
+ // set even before any overlay commands dispatch.
533
+
534
+ return {
535
+ getOverlay: () => overlay,
536
+ getMetadataDefinitions: () => metadataDefinitions,
537
+ getMetadataEntries: () => metadataEntries,
538
+ getMarkerBackedScopeIds: () => markerBackedScopeIds,
539
+ getSharedWorkflowState: () => sharedWorkflowState,
540
+ getScopeChromeVisibility: () => scopeChromeVisibility,
541
+ getVisibilityPolicy(kind) {
542
+ const entry = visibilityPolicies.get(kind);
543
+ return entry ? structuredClone(entry) : null;
544
+ },
545
+ getVisibilityPolicies() {
546
+ // Deterministic order by OverlayKind — needed for byte-identical
547
+ // re-export when unchanged. Stored keys are the canonical set.
548
+ return [...visibilityPolicies.values()].map((policy) => structuredClone(policy));
549
+ },
550
+
551
+ replaceOverlay(next, document) {
552
+ overlay = next ? structuredClone(next) : null;
553
+ normalizedOverlayCache = undefined;
554
+ syncMarkerBackedScopeIds(document, overlay);
555
+ },
556
+ replaceMetadataDefinitions(defs) {
557
+ metadataDefinitions = structuredClone([...defs]);
558
+ },
559
+ replaceMetadataEntries(entries) {
560
+ metadataEntries = structuredClone([...entries]);
561
+ },
562
+ replaceSharedWorkflowState(state) {
563
+ sharedWorkflowState = state;
564
+ },
565
+ replaceScopeChromeVisibility(visibility) {
566
+ scopeChromeVisibility = visibility;
567
+ },
568
+ replaceVisibilityPolicy(kind, policy) {
569
+ const existing = visibilityPolicies.get(kind) ?? null;
570
+ if (overlayVisibilityPoliciesEqual(existing, policy)) {
571
+ return false;
572
+ }
573
+ if (!policy) {
574
+ visibilityPolicies.delete(kind);
575
+ } else {
576
+ visibilityPolicies.set(kind, structuredClone(policy));
577
+ }
578
+ return true;
579
+ },
580
+ replaceVisibilityPolicies(next) {
581
+ // Fast equality via canonical-ordered serialization of the
582
+ // (kind, enforcement, defaultOn, authoredBy) tuple. Small set
583
+ // (≤ 6 kinds), so the allocation cost is negligible.
584
+ const currentKey = canonicalizePolicyMap(visibilityPolicies);
585
+ const nextMap = new Map<OverlayKind, OverlayVisibilityPolicy>();
586
+ for (const policy of next) {
587
+ nextMap.set(policy.kind, structuredClone(policy));
588
+ }
589
+ const nextKey = canonicalizePolicyMap(nextMap);
590
+ if (currentKey === nextKey) {
591
+ return false;
592
+ }
593
+ visibilityPolicies = nextMap;
594
+ return true;
595
+ },
596
+ subscribeVisibilityPolicy(listener) {
597
+ visibilityPolicyListeners.add(listener);
598
+ return () => {
599
+ visibilityPolicyListeners.delete(listener);
600
+ };
601
+ },
602
+ notifyVisibilityPolicyChanged() {
603
+ // Snapshot-and-iterate so an unsubscribe inside a listener
604
+ // doesn't mutate the set we're walking.
605
+ for (const listener of [...visibilityPolicyListeners]) {
606
+ try {
607
+ listener();
608
+ } catch {
609
+ // Isolate listener errors — one broken subscriber must not
610
+ // block the rest of the notification chain.
611
+ }
612
+ }
613
+ },
614
+ getMarkupModePolicy() {
615
+ return markupModePolicy ? structuredClone(markupModePolicy) : null;
616
+ },
617
+ replaceMarkupModePolicy(next) {
618
+ if (workflowMarkupModePoliciesEqual(markupModePolicy, next)) {
619
+ return false;
620
+ }
621
+ markupModePolicy = next ? structuredClone(next) : null;
622
+ return true;
623
+ },
624
+ subscribeMarkupModePolicy(listener) {
625
+ markupModePolicyListeners.add(listener);
626
+ return () => {
627
+ markupModePolicyListeners.delete(listener);
628
+ };
629
+ },
630
+ notifyMarkupModePolicyChanged() {
631
+ for (const listener of [...markupModePolicyListeners]) {
632
+ try {
633
+ listener();
634
+ } catch {
635
+ // Listener-throw isolation — same pattern as visibility policy.
636
+ }
637
+ }
638
+ },
639
+
640
+ getNormalizedOverlay(document) {
641
+ if (!overlay) return null;
642
+ return normalizeOverlayForDocument(document, overlay);
643
+ },
644
+ getMetadataSnapshot() {
645
+ return {
646
+ definitions: structuredClone(metadataDefinitions),
647
+ entries: structuredClone(metadataEntries),
648
+ };
649
+ },
650
+
651
+ createInvalidatedScopeWarning,
652
+ mergeDetachedScopeWarnings,
653
+ };
654
+ }
655
+
656
+ function canonicalizePolicyMap(
657
+ map: ReadonlyMap<OverlayKind, OverlayVisibilityPolicy>,
658
+ ): string {
659
+ const sorted = [...map.values()].sort((a, b) => a.kind.localeCompare(b.kind));
660
+ return sorted
661
+ .map(
662
+ (policy) =>
663
+ `${policy.kind}|${policy.enforcement}|${policy.defaultOn}|${
664
+ policy.authoredBy ? `${policy.authoredBy.actorId}@${policy.authoredBy.at}` : ""
665
+ }`,
666
+ )
667
+ .join(";");
668
+ }
669
+
670
+ /**
671
+ * Called once during `createDocumentRuntime` construction to seed the
672
+ * marker-backed scope-id set from the initial document. The store's
673
+ * factory intentionally leaves this empty so the first
674
+ * `replaceOverlay` from the coordinator is the sole writer of the set
675
+ * (simpler invariant). When no overlay is set initially, we still
676
+ * want the set populated so marker-only scopes are discoverable via
677
+ * `getMarkerBackedScopeIds()`. This helper performs that initial sync
678
+ * without firing a telemetry delta (prior set is definitionally empty).
679
+ */
680
+ export function seedMarkerBackedScopeIds(
681
+ store: OverlayStore,
682
+ document: CanonicalDocumentEnvelope,
683
+ ): void {
684
+ // The public surface only exposes `replaceOverlay` as the
685
+ // marker-backed mutator. Seeding without overlay replacement is
686
+ // achieved by calling replaceOverlay(currentOverlay, document) —
687
+ // idempotent on state but triggers the marker-backed sync.
688
+ const current = store.getOverlay();
689
+ store.replaceOverlay(current, document);
690
+ }