@beyondwork/docx-react-component 1.0.66 → 1.0.69

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 -931
  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 -4795
  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,34 @@
1
+ /**
2
+ * Barrel for the Layer-01 export helpers.
3
+ *
4
+ * Slice 4 ships these as pure, P6-clean pieces of the export pipeline.
5
+ * Slice 5 will wire them together into a single export entry point on
6
+ * `DocxSession.export()`; until then the legacy
7
+ * `exportDocxEditorSession` in `src/io/docx-session.ts` continues to
8
+ * orchestrate while consuming these helpers one-by-one.
9
+ */
10
+
11
+ export {
12
+ ExportBlockedError,
13
+ formatBlockedExportMessage,
14
+ type ExportBlockedCode,
15
+ type ExportBlockedDetails,
16
+ } from "./export-diagnostics.ts";
17
+
18
+ export {
19
+ assertExportNotBlockedByCompatibility,
20
+ assertNoBlockingPreservedComments,
21
+ assertNoBlockingSkippedComments,
22
+ assertNoSkippedRevisions,
23
+ } from "./export-validation.ts";
24
+
25
+ export {
26
+ groupFooterRevisionsByPartPath,
27
+ groupHeaderRevisionsByPartPath,
28
+ groupNoteRevisionsByNoteId,
29
+ } from "./serialize-dispatch.ts";
30
+
31
+ export {
32
+ reattachPreservedParts,
33
+ reattachPreservedPackageParts,
34
+ } from "./preservation-reattach.ts";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Session-layer facade for preserved-part reattachment.
3
+ *
4
+ * Slice 4c of refactor/01 package-session. The actual reattach body
5
+ * lives under `src/io/export/reattach-preserved-parts.ts` (already in
6
+ * the `src/io/export/**` home that stays put per the target
7
+ * architecture). This module exposes it under the session-layer
8
+ * naming convention so consumers importing from
9
+ * `src/session/export/**` see one coherent entry surface.
10
+ *
11
+ * P6 clean — transits only `src/io/**`, which is allowed.
12
+ *
13
+ * What "reattach" means (architecture §P2 — preserve-first):
14
+ * Every package part that was present on input but not owned by
15
+ * the runtime export pipeline is copied back verbatim into the
16
+ * output package, along with its relationships. This is the
17
+ * round-trip byte-identity guarantee for parts the runtime does
18
+ * not understand (opaque-fragment store-backed parts, unknown
19
+ * OOXML, vendor extensions).
20
+ */
21
+
22
+ export {
23
+ reattachPreservedParts,
24
+ /**
25
+ * Expose the reattach entry point under a more session-shaped name
26
+ * for import sites that prefer it. The legacy name stays live for
27
+ * existing in-tree callers.
28
+ */
29
+ reattachPreservedParts as reattachPreservedPackageParts,
30
+ } from "../../io/export/reattach-preserved-parts.ts";
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Export-side serializer dispatch helpers.
3
+ *
4
+ * Slice 4b of refactor/01 package-session. Extracted from the inline
5
+ * sub-part serialization loops inside `exportDocxEditorSession`
6
+ * (`src/io/docx-session.ts:2360–2482`).
7
+ *
8
+ * Each helper takes pre-filtered inputs (the actionable revision list
9
+ * from the review layer, the canonical sub-part record) and returns a
10
+ * deterministic grouping by part path or note id. This lets the legacy
11
+ * file call these instead of the inline loops + keeps the session
12
+ * layer free of review-store reads (P6 clean).
13
+ *
14
+ * The helpers are explicitly generic over the revision record shape
15
+ * so they can be consumed by the legacy file without importing review
16
+ * types into `src/session/**`. Callers pass a `getStoryTarget`
17
+ * accessor that projects the revision's story target.
18
+ */
19
+
20
+ import type { EditorStoryTarget } from "../../api/public-types.ts";
21
+ import type { SubPartsCatalog } from "../../model/canonical-document.ts";
22
+
23
+ type HeaderPart = SubPartsCatalog["headers"][number];
24
+ type FooterPart = SubPartsCatalog["footers"][number];
25
+
26
+ /**
27
+ * Structural identity for `EditorStoryTarget` — matches the shape that
28
+ * `src/core/selection/mapping.ts` exports, inlined here to keep the
29
+ * session layer P6 clean. Behavior is identical: `main` matches `main`
30
+ * regardless of other fields; `header` / `footer` match on
31
+ * `relationshipId` + `variant` + (`sectionIndex` when both sides
32
+ * define it); notes match on `noteId`; everything else is !=.
33
+ */
34
+ function storyTargetEquals(
35
+ left: EditorStoryTarget | undefined,
36
+ right: EditorStoryTarget | undefined,
37
+ ): boolean {
38
+ if (left === right) return true;
39
+ if (!left || !right) return left === right;
40
+ if (left.kind !== right.kind) return false;
41
+ if (left.kind === "main") return true;
42
+ if (left.kind === "header" && right.kind === "header") {
43
+ return (
44
+ left.relationshipId === right.relationshipId &&
45
+ left.variant === right.variant &&
46
+ (left.sectionIndex === undefined || right.sectionIndex === undefined
47
+ ? true
48
+ : left.sectionIndex === right.sectionIndex)
49
+ );
50
+ }
51
+ if (left.kind === "footer" && right.kind === "footer") {
52
+ return (
53
+ left.relationshipId === right.relationshipId &&
54
+ left.variant === right.variant &&
55
+ (left.sectionIndex === undefined || right.sectionIndex === undefined
56
+ ? true
57
+ : left.sectionIndex === right.sectionIndex)
58
+ );
59
+ }
60
+ if (left.kind === "footnote" && right.kind === "footnote") {
61
+ return left.noteId === right.noteId;
62
+ }
63
+ if (left.kind === "endnote" && right.kind === "endnote") {
64
+ return left.noteId === right.noteId;
65
+ }
66
+ return false;
67
+ }
68
+
69
+ /**
70
+ * Group the actionable revisions by header part path.
71
+ *
72
+ * Each entry stores the canonical header record AND the revisions
73
+ * that target it. When multiple header records share a part path
74
+ * (unusual but possible), the entry is seeded with the first record
75
+ * seen; if a later record carries matching revisions, the entry's
76
+ * `header` reference is overwritten so the serializer uses the record
77
+ * that owns those revisions.
78
+ */
79
+ export function groupHeaderRevisionsByPartPath<R>(
80
+ headers: readonly HeaderPart[],
81
+ actionableRevisions: readonly R[],
82
+ getStoryTarget: (revision: R) => EditorStoryTarget | undefined,
83
+ ): Map<string, { header: HeaderPart; revisions: R[] }> {
84
+ const result = new Map<string, { header: HeaderPart; revisions: R[] }>();
85
+ for (const header of headers) {
86
+ const entry = result.get(header.partPath) ?? { header, revisions: [] };
87
+ const matching = actionableRevisions.filter((revision) =>
88
+ storyTargetEquals(
89
+ getStoryTarget(revision) ?? { kind: "main" },
90
+ {
91
+ kind: "header",
92
+ relationshipId: header.relationshipId,
93
+ variant: header.variant,
94
+ ...(header.sectionIndex !== undefined
95
+ ? { sectionIndex: header.sectionIndex }
96
+ : {}),
97
+ },
98
+ ),
99
+ );
100
+ entry.revisions.push(...matching);
101
+ if (matching.length > 0) {
102
+ entry.header = header;
103
+ }
104
+ result.set(header.partPath, entry);
105
+ }
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * Symmetric with `groupHeaderRevisionsByPartPath`.
111
+ */
112
+ export function groupFooterRevisionsByPartPath<R>(
113
+ footers: readonly FooterPart[],
114
+ actionableRevisions: readonly R[],
115
+ getStoryTarget: (revision: R) => EditorStoryTarget | undefined,
116
+ ): Map<string, { footer: FooterPart; revisions: R[] }> {
117
+ const result = new Map<string, { footer: FooterPart; revisions: R[] }>();
118
+ for (const footer of footers) {
119
+ const entry = result.get(footer.partPath) ?? { footer, revisions: [] };
120
+ const matching = actionableRevisions.filter((revision) =>
121
+ storyTargetEquals(
122
+ getStoryTarget(revision) ?? { kind: "main" },
123
+ {
124
+ kind: "footer",
125
+ relationshipId: footer.relationshipId,
126
+ variant: footer.variant,
127
+ ...(footer.sectionIndex !== undefined
128
+ ? { sectionIndex: footer.sectionIndex }
129
+ : {}),
130
+ },
131
+ ),
132
+ );
133
+ entry.revisions.push(...matching);
134
+ if (matching.length > 0) {
135
+ entry.footer = footer;
136
+ }
137
+ result.set(footer.partPath, entry);
138
+ }
139
+ return result;
140
+ }
141
+
142
+ /**
143
+ * Group actionable revisions by `storyTarget.noteId` for the given
144
+ * note kind (`footnote` or `endnote`). The return value is a
145
+ * `Record<noteId, R[]>` suitable for passing directly into
146
+ * `serializeFootnotesXml` / `serializeEndnotesXml`.
147
+ *
148
+ * Revisions whose story target is not a note of the given kind are
149
+ * excluded.
150
+ */
151
+ export function groupNoteRevisionsByNoteId<R>(
152
+ actionableRevisions: readonly R[],
153
+ kind: "footnote" | "endnote",
154
+ getStoryTarget: (revision: R) => EditorStoryTarget | undefined,
155
+ ): Record<string, R[]> {
156
+ const result: Record<string, R[]> = {};
157
+ for (const revision of actionableRevisions) {
158
+ const target = getStoryTarget(revision);
159
+ if (!target || target.kind !== kind) continue;
160
+ const noteId = target.noteId;
161
+ if (!(noteId in result)) result[noteId] = [];
162
+ result[noteId]!.push(revision);
163
+ }
164
+ return result;
165
+ }
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Stateful-export-pipeline helpers extracted from `src/io/docx-session.ts`
3
+ * (slice 5e-6f.1). These functions run on the export side only — they
4
+ * decide whether source bytes can be reused, ensure host-owned metadata
5
+ * parts (core/app properties + workflow payload) are present, build the
6
+ * core-properties XML, and weave `w:permStart` / `w:permEnd` protection
7
+ * anchors into the serialized document XML.
8
+ *
9
+ * Export-only by contract: every caller lives inside
10
+ * `exportDocxEditorSession`. Protection-extraction helpers
11
+ * (`extractDocumentProtection` / `buildProtectionSnapshot` / …) are
12
+ * load-path-shared and stay in docx-session.ts until slice 5e-7 rewires
13
+ * `DocxSession.open` directly.
14
+ *
15
+ * P6-clean: imports reach into src/io/ooxml, src/io/export,
16
+ * src/api/public-types, and src/model/canonical-document. Nothing from
17
+ * src/runtime, src/review, src/core, or src/ui-tailwind.
18
+ */
19
+
20
+ import type { EditorSessionState, ProtectionSnapshot } from "../../api/public-types.ts";
21
+ import type { CanonicalDocument } from "../../model/canonical-document.ts";
22
+ import { buildAppPropertiesXml } from "../../io/export/build-app-properties-xml.ts";
23
+ import { ExportSession } from "../../io/export/export-session.ts";
24
+ import { mapParagraphBoundaries } from "../../io/export/serialize-comments.ts";
25
+ import type { OpcPackage } from "../../io/opc/package-reader.ts";
26
+ import {
27
+ CONTENT_TYPES_PATH,
28
+ PACKAGE_RELATIONSHIPS_PATH,
29
+ getRelationshipsPartPath,
30
+ resolveRelationshipTarget,
31
+ } from "../../io/ooxml/part-manifest.ts";
32
+ import {
33
+ buildWorkflowPayloadParts,
34
+ WORKFLOW_PAYLOAD_CONTENT_TYPE,
35
+ WORKFLOW_PAYLOAD_CUSTOM_PROPS_CONTENT_TYPE,
36
+ WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
37
+ WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
38
+ WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
39
+ type EditorStatePayload,
40
+ } from "../../io/ooxml/workflow-payload.ts";
41
+
42
+ type CanonicalDocumentEnvelope = CanonicalDocument;
43
+
44
+ /**
45
+ * Minimal shape of the session-import state object that the export pipeline
46
+ * consumes. The full ImportedDocxState interface lives in docx-session.ts;
47
+ * these are the subset of fields this module actually reads.
48
+ */
49
+ export interface ExportPipelineState {
50
+ sourcePackage: OpcPackage;
51
+ sourceDocumentPartPath: string;
52
+ sourceCommentsPartPath?: string;
53
+ sourceCommentsExtendedPartPath?: string;
54
+ sourceCommentsIdsPartPath?: string;
55
+ sourcePeoplePartPath?: string;
56
+ }
57
+
58
+ // Core + app properties OPC addresses. Moved here from docx-session.ts
59
+ // alongside the helpers that consume them; the legacy file re-imports.
60
+ export const APP_PROPERTIES_PART_PATH = "/docProps/app.xml";
61
+ export const CORE_PROPERTIES_PART_PATH = "/docProps/core.xml";
62
+ export const APP_PROPERTIES_CONTENT_TYPE =
63
+ "application/vnd.openxmlformats-officedocument.extended-properties+xml";
64
+ export const CORE_PROPERTIES_CONTENT_TYPE =
65
+ "application/vnd.openxmlformats-package.core-properties+xml";
66
+ export const APP_PROPERTIES_RELATIONSHIP_TYPE =
67
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
68
+ export const CORE_PROPERTIES_RELATIONSHIP_TYPE =
69
+ "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
70
+
71
+ /**
72
+ * Read a Node-style environment variable without referencing the Node-only
73
+ * `process` global in the production build (which excludes @types/node).
74
+ * Returns `undefined` in browser environments or when the var is unset.
75
+ */
76
+ export function readNodeEnvVar(name: string): string | undefined {
77
+ const proc = (globalThis as unknown as {
78
+ process?: { env?: Record<string, string | undefined> };
79
+ }).process;
80
+ return proc?.env?.[name];
81
+ }
82
+
83
+ export function canReuseSourceBytesForCurrentDocument(
84
+ state: ExportPipelineState,
85
+ document: CanonicalDocumentEnvelope,
86
+ ): boolean {
87
+ // The validator-loop CI harness (§1 / §7) needs to exercise the full
88
+ // serializer pipeline — including A.1 / A.2 / A.3 / A.6 / A.7 / A.8 /
89
+ // A.9 fixes — so it sets DOCX_VALIDATOR_FORCE_REGEN=1 to disable the
90
+ // fast-path byte-reuse. Never set this flag in production; it forces a
91
+ // full XML regeneration on every export and is only used by the CCEP
92
+ // validator harness to prove our serializer's correctness.
93
+ if (readNodeEnvVar("DOCX_VALIDATOR_FORCE_REGEN") === "1") {
94
+ return false;
95
+ }
96
+ if (requiresHostMetadataNormalization(state.sourcePackage, state.sourceDocumentPartPath)) {
97
+ return false;
98
+ }
99
+
100
+ const commentThreads = Object.values(document.review.comments);
101
+ const hasLiveComments = commentThreads.some((thread) => thread.anchor.kind !== "detached");
102
+ const hasRuntimeAuthoredActiveRevisions = Object.values(document.review.revisions).some((revision) =>
103
+ revision.status === "open" && revision.metadata?.source === "runtime"
104
+ );
105
+ if (hasRuntimeAuthoredActiveRevisions) {
106
+ return false;
107
+ }
108
+ if (!hasLiveComments) {
109
+ return true;
110
+ }
111
+
112
+ return Boolean(
113
+ state.sourceCommentsPartPath &&
114
+ state.sourceCommentsExtendedPartPath &&
115
+ state.sourceCommentsIdsPartPath &&
116
+ state.sourcePeoplePartPath,
117
+ );
118
+ }
119
+
120
+ export function requiresHostMetadataNormalization(
121
+ sourcePackage: OpcPackage,
122
+ sourceDocumentPartPath: string,
123
+ ): boolean {
124
+ return (
125
+ isSuspiciouslySkeletalWordPackage(sourcePackage, sourceDocumentPartPath) &&
126
+ !hasHostSafeMetadataPackageStructure(sourcePackage)
127
+ );
128
+ }
129
+
130
+ export function ensureHostMetadataParts(
131
+ exportSession: ExportSession,
132
+ sourcePackage: OpcPackage,
133
+ document: CanonicalDocumentEnvelope,
134
+ ): void {
135
+ const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
136
+ if (!corePropertiesPart || corePropertiesPart.contentType !== CORE_PROPERTIES_CONTENT_TYPE) {
137
+ exportSession.replaceOwnedPart({
138
+ path: CORE_PROPERTIES_PART_PATH,
139
+ bytes: corePropertiesPart?.bytes ?? new TextEncoder().encode(buildCorePropertiesXml(document)),
140
+ contentType: CORE_PROPERTIES_CONTENT_TYPE,
141
+ compression: corePropertiesPart?.compression,
142
+ });
143
+ }
144
+
145
+ const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
146
+ if (!appPropertiesPart || appPropertiesPart.contentType !== APP_PROPERTIES_CONTENT_TYPE) {
147
+ exportSession.replaceOwnedPart({
148
+ path: APP_PROPERTIES_PART_PATH,
149
+ bytes: appPropertiesPart?.bytes ?? new TextEncoder().encode(buildAppPropertiesXml()),
150
+ contentType: APP_PROPERTIES_CONTENT_TYPE,
151
+ compression: appPropertiesPart?.compression,
152
+ });
153
+ }
154
+
155
+ exportSession.ensurePackageRelationship({
156
+ type: CORE_PROPERTIES_RELATIONSHIP_TYPE,
157
+ target: CORE_PROPERTIES_PART_PATH,
158
+ preferredId: "rIdDocPropsCore",
159
+ });
160
+ exportSession.ensurePackageRelationship({
161
+ type: APP_PROPERTIES_RELATIONSHIP_TYPE,
162
+ target: APP_PROPERTIES_PART_PATH,
163
+ preferredId: "rIdDocPropsApp",
164
+ });
165
+ }
166
+
167
+ export function ensureWorkflowPayloadParts(
168
+ exportSession: ExportSession,
169
+ sessionState: EditorSessionState,
170
+ document: CanonicalDocumentEnvelope,
171
+ sourcePackage: OpcPackage,
172
+ resolvedPartPaths: {
173
+ payloadPartPath: string;
174
+ itemPropsPartPath: string;
175
+ },
176
+ editorState?: EditorStatePayload,
177
+ ): void {
178
+ const payloadParts = buildWorkflowPayloadParts({
179
+ sourcePackage,
180
+ workflowMetadata: sessionState.workflowMetadata,
181
+ workflowOverlay: sessionState.workflowOverlay,
182
+ visibilityPolicies: sessionState.visibilityPolicies,
183
+ markupModePolicy: sessionState.markupModePolicy ?? null,
184
+ editorState,
185
+ documentId: sessionState.documentId,
186
+ createdAt: document.createdAt,
187
+ updatedAt: document.updatedAt,
188
+ producerVersion: sessionState.editorBuild,
189
+ });
190
+ if (!payloadParts) {
191
+ return;
192
+ }
193
+ if (
194
+ payloadParts.payloadPartPath !== resolvedPartPaths.payloadPartPath ||
195
+ payloadParts.itemPropsPartPath !== resolvedPartPaths.itemPropsPartPath
196
+ ) {
197
+ throw new Error(
198
+ "Workflow payload export resolved inconsistent customXml paths; export session ownership no longer matches payload serialization.",
199
+ );
200
+ }
201
+
202
+ const payloadPart = sourcePackage.parts.get(payloadParts.payloadPartPath);
203
+ const itemPropsPart = sourcePackage.parts.get(payloadParts.itemPropsPartPath);
204
+ const customPropsPart = sourcePackage.parts.get(WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH);
205
+
206
+ exportSession.replaceOwnedPart({
207
+ path: payloadParts.payloadPartPath,
208
+ bytes: new TextEncoder().encode(payloadParts.payloadPartXml),
209
+ contentType: payloadPart?.contentType ?? WORKFLOW_PAYLOAD_CONTENT_TYPE,
210
+ relationships: payloadParts.payloadRelationships,
211
+ compression: payloadPart?.compression,
212
+ });
213
+ exportSession.replaceOwnedPart({
214
+ path: payloadParts.itemPropsPartPath,
215
+ bytes: new TextEncoder().encode(payloadParts.itemPropsXml),
216
+ contentType: itemPropsPart?.contentType ?? WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
217
+ compression: itemPropsPart?.compression,
218
+ });
219
+ exportSession.replaceOwnedPart({
220
+ path: WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
221
+ bytes: new TextEncoder().encode(payloadParts.customPropertiesXml),
222
+ contentType: customPropsPart?.contentType ?? WORKFLOW_PAYLOAD_CUSTOM_PROPS_CONTENT_TYPE,
223
+ compression: customPropsPart?.compression,
224
+ });
225
+
226
+ exportSession.ensurePackageRelationship({
227
+ type: WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
228
+ target: WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
229
+ preferredId: "rIdBwWorkflowCustomProps",
230
+ });
231
+ }
232
+
233
+ export function hasHostSafeMetadataPackageStructure(sourcePackage: OpcPackage): boolean {
234
+ const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
235
+ const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
236
+ return (
237
+ corePropertiesPart?.contentType === CORE_PROPERTIES_CONTENT_TYPE &&
238
+ appPropertiesPart?.contentType === APP_PROPERTIES_CONTENT_TYPE &&
239
+ hasPackageRelationshipTarget(
240
+ sourcePackage,
241
+ CORE_PROPERTIES_RELATIONSHIP_TYPE,
242
+ CORE_PROPERTIES_PART_PATH,
243
+ ) &&
244
+ hasPackageRelationshipTarget(
245
+ sourcePackage,
246
+ APP_PROPERTIES_RELATIONSHIP_TYPE,
247
+ APP_PROPERTIES_PART_PATH,
248
+ )
249
+ );
250
+ }
251
+
252
+ export function hasPackageRelationshipTarget(
253
+ sourcePackage: OpcPackage,
254
+ relationshipType: string,
255
+ targetPartPath: string,
256
+ ): boolean {
257
+ return sourcePackage.manifest.packageRelationships.some(
258
+ (relationship) =>
259
+ relationship.type === relationshipType &&
260
+ relationship.targetMode === "internal" &&
261
+ resolveRelationshipTarget(null, relationship) === targetPartPath,
262
+ );
263
+ }
264
+
265
+ export function isSuspiciouslySkeletalWordPackage(
266
+ sourcePackage: OpcPackage,
267
+ sourceDocumentPartPath: string,
268
+ ): boolean {
269
+ const allowedPaths = new Set<string>([
270
+ CONTENT_TYPES_PATH,
271
+ PACKAGE_RELATIONSHIPS_PATH,
272
+ sourceDocumentPartPath,
273
+ ]);
274
+ const relationshipsPartPath = getRelationshipsPartPath(sourceDocumentPartPath);
275
+ if (relationshipsPartPath) {
276
+ allowedPaths.add(relationshipsPartPath);
277
+ }
278
+
279
+ return [...sourcePackage.parts.keys()].every((partPath) => allowedPaths.has(partPath));
280
+ }
281
+
282
+ export function buildCorePropertiesXml(document: CanonicalDocumentEnvelope): string {
283
+ const { metadata } = document;
284
+ const keywords =
285
+ Array.isArray(metadata.keywords) && metadata.keywords.length > 0
286
+ ? metadata.keywords.join(", ")
287
+ : undefined;
288
+ const propertyLines = [
289
+ xmlNode("dc:title", metadata.title),
290
+ xmlNode("dc:subject", metadata.subject),
291
+ xmlNode("dc:description", metadata.description),
292
+ xmlNode("dc:language", metadata.language),
293
+ xmlNode("cp:keywords", keywords),
294
+ xmlNode("cp:category", metadata.category),
295
+ xmlNode('dcterms:created xsi:type="dcterms:W3CDTF"', document.createdAt),
296
+ xmlNode('dcterms:modified xsi:type="dcterms:W3CDTF"', document.updatedAt),
297
+ ].filter((line): line is string => Boolean(line));
298
+
299
+ return [
300
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
301
+ '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">',
302
+ ...propertyLines.map((line) => ` ${line}`),
303
+ "</cp:coreProperties>",
304
+ ].join("\n");
305
+ }
306
+
307
+ export function xmlNode(tagName: string, value: string | undefined): string | undefined {
308
+ if (typeof value !== "string" || value.length === 0) {
309
+ return undefined;
310
+ }
311
+
312
+ return `<${tagName}>${escapeXml(value)}</${tagName.split(" ", 1)[0]}>`;
313
+ }
314
+
315
+ export function serializeProtectionRangesIntoDocumentXml(
316
+ documentXml: string,
317
+ protection: ProtectionSnapshot,
318
+ paragraphs = mapParagraphBoundaries(documentXml),
319
+ ): string {
320
+ if (protection.ranges.length === 0) {
321
+ return documentXml;
322
+ }
323
+
324
+ const insertions = new Map<number, string[]>();
325
+
326
+ for (const range of protection.ranges) {
327
+ if (typeof range.start !== "number" || typeof range.end !== "number") {
328
+ continue;
329
+ }
330
+ const rangeStart = range.start;
331
+ const rangeEnd = range.end;
332
+
333
+ const startParagraph = paragraphs.find(
334
+ (candidate) => rangeStart >= candidate.start && rangeStart <= candidate.end,
335
+ );
336
+ const endParagraph = paragraphs.find(
337
+ (candidate) => rangeEnd >= candidate.start && rangeEnd <= candidate.end,
338
+ );
339
+ if (!startParagraph || !endParagraph) {
340
+ continue;
341
+ }
342
+
343
+ const startIndex =
344
+ startParagraph.boundaries.get(rangeStart) ??
345
+ findNearestBoundaryIndex(startParagraph.boundaries, rangeStart, "backward");
346
+ const endIndex =
347
+ endParagraph.boundaries.get(rangeEnd) ??
348
+ findNearestBoundaryIndex(endParagraph.boundaries, rangeEnd, "forward");
349
+ if (startIndex === undefined || endIndex === undefined) {
350
+ continue;
351
+ }
352
+
353
+ const permStartXml = [
354
+ `<w:permStart`,
355
+ ` w:id="${escapeXmlAttribute(range.rangeId)}"`,
356
+ range.editorGroup ? ` w:edGrp="${escapeXmlAttribute(range.editorGroup)}"` : "",
357
+ range.editor ? ` w:ed="${escapeXmlAttribute(range.editor)}"` : "",
358
+ `/>`,
359
+ ].join("");
360
+ const permEndXml = `<w:permEnd w:id="${escapeXmlAttribute(range.rangeId)}"/>`;
361
+
362
+ pushProtectionInsertion(insertions, startIndex, permStartXml);
363
+ pushProtectionInsertion(insertions, endIndex, permEndXml);
364
+ }
365
+
366
+ if (insertions.size === 0) {
367
+ return documentXml;
368
+ }
369
+
370
+ const parts: string[] = [];
371
+ let cursor = 0;
372
+ for (const [index, snippets] of [...insertions.entries()].sort(([left], [right]) => left - right)) {
373
+ parts.push(documentXml.slice(cursor, index));
374
+ parts.push(...snippets);
375
+ cursor = index;
376
+ }
377
+ parts.push(documentXml.slice(cursor));
378
+ return parts.join("");
379
+ }
380
+
381
+ function pushProtectionInsertion(
382
+ insertions: Map<number, string[]>,
383
+ index: number,
384
+ xml: string,
385
+ ): void {
386
+ const existing = insertions.get(index);
387
+ if (existing) {
388
+ existing.push(xml);
389
+ return;
390
+ }
391
+ insertions.set(index, [xml]);
392
+ }
393
+
394
+ function findNearestBoundaryIndex(
395
+ boundaries: Map<number, number>,
396
+ position: number,
397
+ direction: "backward" | "forward",
398
+ ): number | undefined {
399
+ const ordered = [...boundaries.entries()].sort(([left], [right]) => left - right);
400
+ if (direction === "backward") {
401
+ for (let index = ordered.length - 1; index >= 0; index -= 1) {
402
+ const [boundaryPos, boundaryIndex] = ordered[index]!;
403
+ if (boundaryPos <= position) {
404
+ return boundaryIndex;
405
+ }
406
+ }
407
+ return undefined;
408
+ }
409
+ for (const [boundaryPos, boundaryIndex] of ordered) {
410
+ if (boundaryPos >= position) {
411
+ return boundaryIndex;
412
+ }
413
+ }
414
+ return undefined;
415
+ }
416
+
417
+ export function escapeXml(value: string): string {
418
+ return value
419
+ .replace(/&/g, "&amp;")
420
+ .replace(/</g, "&lt;")
421
+ .replace(/>/g, "&gt;")
422
+ .replace(/\"/g, "&quot;")
423
+ .replace(/'/g, "&apos;");
424
+ }
425
+
426
+ export function escapeXmlAttribute(value: string): string {
427
+ return value
428
+ .replace(/&/g, "&amp;")
429
+ .replace(/</g, "&lt;")
430
+ .replace(/>/g, "&gt;")
431
+ .replace(/"/g, "&quot;");
432
+ }