@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,1834 @@
1
+ /**
2
+ * `loadDocxSessionAsync` — session-layer owner of the async load path.
3
+ *
4
+ * Slice 5e-7.e.1 relocates the body of `loadDocxEditorSessionAsync` from
5
+ * `src/io/docx-session.ts` into this module. The legacy symbol in
6
+ * `docx-session.ts` now delegates here in one function call so existing
7
+ * consumers (tests, services/debug, `DocxSession.open`) observe
8
+ * byte-identical behavior while the orchestration lives under
9
+ * `src/session/**`.
10
+ *
11
+ * Slice 5e-7.f: the export pipeline (`runStatefulExport` at
12
+ * `src/session/export/stateful-export.ts`) is imported directly —
13
+ * both load and export now live under `src/session/**`, so the earlier
14
+ * `LoaderDependencies` injection pattern was removed. The
15
+ * `session.ts → docx-session.ts` debt exception remains the only
16
+ * cross-layer transit; the loader body is fully in-session.
17
+ *
18
+ * Runtime-type seams:
19
+ * - `laycacheEnvelope` is `unknown` on `LoadDocxEditorSessionAsyncOptions`
20
+ * (the concrete `CacheEnvelope` lives at
21
+ * `src/runtime/prerender/cache-envelope.ts`). This module narrows it
22
+ * to a structural subset `LaycacheEnvelopeSubset` defined locally —
23
+ * only the fields the loader actually reads (`canonicalDocument`,
24
+ * `compatibilityReport`). Zero runtime-layer imports.
25
+ * - `telemetryBus` is `unknown` on the same options. This module
26
+ * narrows to `Parameters<typeof setActiveParseTelemetryBus>[0]`
27
+ * (the parser's own type for its bus parameter) at the single
28
+ * call site. Zero runtime-layer imports.
29
+ *
30
+ * Performance invariants preserved verbatim from the legacy body:
31
+ * - Invariant 8 — `onProgressiveSnapshot` fires after the body-normalize
32
+ * stage + post-yield, only on the cold path (no laycache envelope).
33
+ * - Invariant 9 — chart-preview resolution deferred off critical path
34
+ * when `onChartPreviewsReady` is supplied; inline otherwise.
35
+ * - Invariant 10 — no blocking work between the laycache probe (run
36
+ * in `DocxSession.open` before this function is called) and the
37
+ * loader body. The short-circuit branch is the first work this
38
+ * function does after the OPC read + relationship resolve.
39
+ */
40
+
41
+ import type {
42
+ CompatibilityReport,
43
+ EditorSessionState,
44
+ } from "../../api/public-types.ts";
45
+ import { runStatefulExport } from "../export/stateful-export.ts";
46
+ import { editorSessionStateFromPersistedSnapshot } from "../../api/session-state.ts";
47
+ // `src/api/diagnostic-conversion.ts` deleted 2026-04-22 when the
48
+ // Internal* diagnostic types collapsed to public-type aliases (see
49
+ // `docs/plans/cross-layer-coord-02.md §8`). Compatibility reports
50
+ // flow through as-is — no conversion needed.
51
+ import type { CanonicalDocument } from "../../model/canonical-document.ts";
52
+ import type {
53
+ CommentImportDiagnostic,
54
+ ImportedCommentDefinition,
55
+ ParsedCommentsResult,
56
+ } from "../../io/ooxml/parse-comments.ts";
57
+ import type {
58
+ BlockNode as _BlockNode,
59
+ FootnoteCollection,
60
+ HeaderDocument,
61
+ FooterDocument,
62
+ InlineNode as _InlineNode,
63
+ MediaCatalog as _MediaCatalog,
64
+ NumberingCatalog as _NumberingCatalog,
65
+ OpaqueFragmentRecord as _OpaqueFragmentRecord,
66
+ PreservedPackagePart as _PreservedPackagePart,
67
+ SubPartsCatalog,
68
+ } from "../../model/canonical-document.ts";
69
+ import { readOpcPackage, type OpcPackage } from "../../io/opc/package-reader.ts";
70
+ import {
71
+ parseMainDocumentXml,
72
+ setActiveParseTelemetryBus,
73
+ } from "../../io/ooxml/parse-main-document.ts";
74
+ import {
75
+ normalizeParsedTextDocument,
76
+ normalizeParsedTextDocumentAsync,
77
+ } from "../../io/normalize/normalize-text.ts";
78
+ import {
79
+ createChartPartLookup,
80
+ resolveChartPreviewsForDocument,
81
+ scheduleChartPreviewResolution,
82
+ } from "../../io/chart-preview-resolver.ts";
83
+ import {
84
+ resolveRelationshipTarget,
85
+ } from "../../io/ooxml/part-manifest.ts";
86
+ import {
87
+ parseWorkflowPayloadEnvelopeFromPackage,
88
+ } from "../../io/ooxml/workflow-payload.ts";
89
+ import {
90
+ classifyCorruptPackageError,
91
+ createMissingPartIssue,
92
+ } from "../../io/opc/corrupt-package.ts";
93
+ import {
94
+ parseRevisionsFromDocumentXml,
95
+ parseRevisionsFromStoryXml,
96
+ type ParsedRevisionsResult,
97
+ } from "../../io/ooxml/parse-revisions.ts";
98
+ import { parseCommentsFromOoxml } from "../../io/ooxml/parse-comments.ts";
99
+ import { parseNumberingXml } from "../../io/ooxml/parse-numbering.ts";
100
+ import {
101
+ parseHeaderFooterReferences,
102
+ parseHeaderXml,
103
+ parseFooterXml,
104
+ } from "../../io/ooxml/parse-headers-footers.ts";
105
+ import {
106
+ parseFootnotesXml,
107
+ parseEndnotesXml,
108
+ } from "../../io/ooxml/parse-footnotes.ts";
109
+ import {
110
+ materializeCanonicalTheme,
111
+ parseThemeXml,
112
+ resolveTheme,
113
+ } from "../../io/ooxml/parse-theme.ts";
114
+ import { parseSettingsXml } from "../../io/ooxml/parse-settings.ts";
115
+ import { parseStylesXml } from "../../io/ooxml/parse-styles.ts";
116
+ import { parseFontTable } from "../../io/ooxml/parse-font-table.ts";
117
+ import { createPersistedSourcePackage } from "../../io/source-package-provenance.ts";
118
+ import { validatePersistedEditorSnapshot } from "../../model/snapshot.ts";
119
+ import { buildCompatibilityReport } from "../../validation/compatibility-engine.ts";
120
+ import {
121
+ createPackageImportDiagnostics,
122
+ createValidationImportDiagnostics,
123
+ } from "../../validation/import-diagnostics.ts";
124
+ import {
125
+ extractDocumentProtection,
126
+ extractProtectionRanges,
127
+ buildProtectionSnapshot,
128
+ } from "../shared/protection.ts";
129
+ import {
130
+ createEmptyNumberingCatalog,
131
+ decodeUtf8,
132
+ extractDocumentRootAttributes,
133
+ serializeCanonicalDocumentForExport,
134
+ toUint8Array,
135
+ } from "../shared/session-utils.ts";
136
+ import {
137
+ createImportDiagnosticsFromError,
138
+ createStageEmitter,
139
+ } from "./parse-orchestration.ts";
140
+ import { mergeSecondaryStoryMediaCatalog } from "./styles-consolidation.ts";
141
+ import {
142
+ createImportedCanonicalDocument,
143
+ createImportedSnapshot,
144
+ } from "./canonical-assembly.ts";
145
+ import {
146
+ normalizeImportedCommentThreads,
147
+ normalizeImportedRevisionRecords,
148
+ toRuntimeCommentRecords,
149
+ toRuntimeRevisionRecords,
150
+ } from "./review-import.ts";
151
+ import { translateClmCommentsToWorkflow } from "./workflow-scope-import.ts";
152
+ import {
153
+ mapCommentDiagnosticsToWarnings,
154
+ mapRevisionDiagnosticsToWarnings,
155
+ mapStoryRevisionDiagnosticsToWarnings,
156
+ } from "./import-diagnostics.ts";
157
+ import { buildImportPreservation } from "./preservation-index.ts";
158
+ import {
159
+ hydrateOffloadEntriesFromPayload,
160
+ processEmbeddedOffload,
161
+ type EmbeddingOffloadEntry,
162
+ } from "./embedded-offload.ts";
163
+ import {
164
+ COMMENTS_PART_PATH,
165
+ COMMENTS_RELATIONSHIP_TYPE,
166
+ MAIN_DOCUMENT_PATH,
167
+ resolveCommentsPartPath,
168
+ resolveDocumentRelatedPartPath,
169
+ resolveMainDocumentPartPath,
170
+ } from "./part-discovery.ts";
171
+ import {
172
+ collectBrokenInternalRelationshipIssues,
173
+ createBrokenRelationshipWarnings,
174
+ summarizeBrokenRelationshipIssues,
175
+ } from "./package-read.ts";
176
+ import {
177
+ createSubPartOpaqueImportState,
178
+ normalizeFootnoteCollectionOpaqueBlocks,
179
+ normalizeSubPartOpaqueBlocks,
180
+ } from "./normalize.ts";
181
+ import {
182
+ collectInlineMediaParts,
183
+ } from "./package-parts.ts";
184
+ import { createDiagnosticsSession } from "./diagnostics-session.ts";
185
+ import { collectEmbeddedDocuments } from "./embedded-discovery.ts";
186
+ import type { CommentThread } from "../../model/review/comment-types.ts";
187
+ import type { RevisionRecord as ReviewRevisionRecord } from "../../model/review/revision-types.ts";
188
+ import {
189
+ BLOCKING_COMMENT_DIAGNOSTIC_CODES,
190
+ PROGRESSIVE_VIEWPORT_BLOCKS,
191
+ type ImportedDocxState,
192
+ type LoadDocxEditorSessionAsyncOptions,
193
+ type LoadDocxEditorSessionOptions,
194
+ type LoadedDocxEditorSession,
195
+ } from "./loader-types.ts";
196
+ import {
197
+ COMMENTS_EXTENDED_PART_PATH,
198
+ COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
199
+ COMMENTS_IDS_PART_PATH,
200
+ COMMENTS_IDS_RELATIONSHIP_TYPE,
201
+ ENDNOTES_PART_PATH,
202
+ ENDNOTES_RELATIONSHIP_TYPE,
203
+ FONT_TABLE_PART_PATH,
204
+ FONT_TABLE_RELATIONSHIP_TYPE,
205
+ FOOTNOTES_PART_PATH,
206
+ FOOTNOTES_RELATIONSHIP_TYPE,
207
+ MAIN_DOCUMENT_CONTENT_TYPE,
208
+ NUMBERING_PART_PATH,
209
+ NUMBERING_RELATIONSHIP_TYPE,
210
+ PEOPLE_PART_PATH,
211
+ PEOPLE_RELATIONSHIP_TYPE,
212
+ SETTINGS_PART_PATH,
213
+ SETTINGS_RELATIONSHIP_TYPE,
214
+ STYLES_PART_PATH,
215
+ STYLES_RELATIONSHIP_TYPE,
216
+ } from "./part-constants.ts";
217
+
218
+ /**
219
+ * Structural subset of `CacheEnvelope` (from
220
+ * `src/runtime/prerender/cache-envelope.ts`) that the loader reads.
221
+ *
222
+ * Defined locally so this module stays §P6-clean — the concrete
223
+ * `CacheEnvelope` type is owned by the runtime/prerender layer and may
224
+ * not be imported here. Callers cast to this subset at the single
225
+ * consumption site (`options.laycacheEnvelope as LaycacheEnvelopeSubset
226
+ * | undefined`); the envelope carries additional fields (graph,
227
+ * surface, hashes) that this module does not consume.
228
+ */
229
+ interface LaycacheEnvelopeSubset {
230
+ readonly canonicalDocument: CanonicalDocument;
231
+ /**
232
+ * Concrete type at the boundary is the `Internal*` flavor of
233
+ * `CompatibilityReport` (from `src/core/state/editor-state.ts`) that
234
+ * the runtime-layer `CacheEnvelope` carries. We keep it as `unknown`
235
+ * here so this module does not transit `src/core/**` (§P6); the one
236
+ * consumption site narrows via `Parameters<typeof
237
+ * toPublicCompatibilityReport>[0]` at call time.
238
+ */
239
+ readonly compatibilityReport?: unknown;
240
+ }
241
+
242
+ /**
243
+ * Narrow type for the caller-supplied telemetry bus. The concrete
244
+ * `TelemetryBus` type lives at `src/runtime/debug/telemetry-bus.ts`
245
+ * (runtime-layer, forbidden here by §P6). We reach the parser's own
246
+ * parameter type via `Parameters<typeof setActiveParseTelemetryBus>[0]`
247
+ * so the single bracketing call site passes a strongly-typed value
248
+ * without this module transiting runtime internals.
249
+ */
250
+ type ParseTelemetryBusParam = Parameters<typeof setActiveParseTelemetryBus>[0];
251
+
252
+ /**
253
+ * Session-layer owner of the async load path. Body is byte-identical to
254
+ * the pre-5e-7.e.1 `loadDocxEditorSessionAsync` in
255
+ * `src/io/docx-session.ts`; only the imports were renamed to point at
256
+ * the already-extracted session-layer modules and the two runtime-type
257
+ * casts were replaced with the local `LaycacheEnvelopeSubset` + the
258
+ * `ParseTelemetryBusParam` narrow.
259
+ *
260
+ * Slice 5e-7.f removed the `deps` parameter — the stateful export
261
+ * pipeline now lives in-session at
262
+ * `src/session/export/stateful-export.ts` and is imported directly.
263
+ */
264
+ export async function loadDocxSessionAsync(
265
+ options: LoadDocxEditorSessionAsyncOptions,
266
+ ): Promise<LoadedDocxEditorSession> {
267
+ const { scheduler } = options;
268
+ const editorBuild =
269
+ typeof options.editorBuild === "string" && options.editorBuild.length > 0
270
+ ? options.editorBuild
271
+ : "dev";
272
+ const sourceBytes = toUint8Array(options.bytes);
273
+
274
+ const stages = createStageEmitter(options.onLoadStage);
275
+
276
+ let sourcePackage: OpcPackage;
277
+
278
+ try {
279
+ sourcePackage = readOpcPackage(sourceBytes);
280
+ } catch (error) {
281
+ return createDiagnosticsSession(
282
+ options,
283
+ createPackageImportDiagnostics({
284
+ issue: classifyCorruptPackageError(error),
285
+ }),
286
+ );
287
+ }
288
+ stages.emit("opc");
289
+ await scheduler.yield();
290
+ const embeddedWorkflowPayload = parseWorkflowPayloadEnvelopeFromPackage(sourcePackage);
291
+ const embeddedWorkflowMetadata = embeddedWorkflowPayload?.workflowMetadata;
292
+ const embeddedWorkflowOverlay = embeddedWorkflowPayload?.workflowOverlay;
293
+
294
+ const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
295
+ const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
296
+ sourcePackage,
297
+ mainDocumentPath,
298
+ );
299
+ if (brokenRelationshipIssues.length > 0) {
300
+ return createDiagnosticsSession(
301
+ options,
302
+ createPackageImportDiagnostics({
303
+ issue: {
304
+ ...brokenRelationshipIssues[0],
305
+ message: summarizeBrokenRelationshipIssues(brokenRelationshipIssues),
306
+ details: {
307
+ issueCount: brokenRelationshipIssues.length,
308
+ targets: brokenRelationshipIssues.map((issue) => issue.targetPartPath).filter(Boolean),
309
+ },
310
+ },
311
+ }),
312
+ );
313
+ }
314
+
315
+ if (!mainDocumentPath) {
316
+ return createDiagnosticsSession(
317
+ options,
318
+ createPackageImportDiagnostics({
319
+ issue: createMissingPartIssue(MAIN_DOCUMENT_PATH),
320
+ }),
321
+ );
322
+ }
323
+
324
+ const documentPart = sourcePackage.parts.get(mainDocumentPath);
325
+ if (!documentPart) {
326
+ return createDiagnosticsSession(
327
+ options,
328
+ createPackageImportDiagnostics({
329
+ issue: createMissingPartIssue(mainDocumentPath),
330
+ }),
331
+ );
332
+ }
333
+ if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
334
+ return createDiagnosticsSession(
335
+ options,
336
+ createValidationImportDiagnostics({
337
+ message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
338
+ }),
339
+ );
340
+ }
341
+
342
+ try {
343
+ // L7 Phase 2.5 Plan B B.6b — loader short-circuit. Hand
344
+ // `envelope.canonicalDocument` through reference-equal and skip the
345
+ // five expensive parse stages. The four `onLoadStage` callbacks still
346
+ // fire in order — `body` and `styles-numbering-comments` emit with
347
+ // near-zero duration — so host progress bars are unaffected.
348
+ const laycacheEnvelope = options.laycacheEnvelope as
349
+ | LaycacheEnvelopeSubset
350
+ | undefined;
351
+ if (laycacheEnvelope) {
352
+ stages.emit("body");
353
+ stages.emit("styles-numbering-comments");
354
+
355
+ const canonicalDocument = laycacheEnvelope.canonicalDocument;
356
+
357
+ // `extractProtectionRanges` needs `parsedDocument.blocks` (which we
358
+ // are skipping), so the short-circuit uses an empty ranges list;
359
+ // document-level `editType` / `enforcement` still come from
360
+ // settings.xml so read-only docs stay read-only.
361
+ const settingsPartPath = resolveDocumentRelatedPartPath(
362
+ sourcePackage,
363
+ mainDocumentPath,
364
+ documentPart.relationships,
365
+ SETTINGS_RELATIONSHIP_TYPE,
366
+ SETTINGS_PART_PATH,
367
+ );
368
+ const settingsXml =
369
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
370
+ ? decodeUtf8(
371
+ sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array(),
372
+ )
373
+ : "";
374
+ const documentProtection = extractDocumentProtection(settingsXml);
375
+ const protectionSnapshot = buildProtectionSnapshot(documentProtection, []);
376
+
377
+ // Chart previews (`previewMediaId` is host-dependent) aren't cached
378
+ // in the envelope. C3b: when onChartPreviewsReady is provided, defer
379
+ // resolution out of the critical path. Otherwise block (legacy behavior).
380
+ let documentWithChartPreviews: CanonicalDocument;
381
+ if (options.onChartPreviewsReady) {
382
+ scheduleChartPreviewResolution(
383
+ canonicalDocument,
384
+ sourcePackage,
385
+ options.hostAdapter,
386
+ options.onChartPreviewsReady,
387
+ );
388
+ documentWithChartPreviews = canonicalDocument as CanonicalDocument;
389
+ } else {
390
+ documentWithChartPreviews = (await resolveChartPreviewsForDocument(
391
+ canonicalDocument,
392
+ sourcePackage,
393
+ options.hostAdapter,
394
+ )) as CanonicalDocument;
395
+ }
396
+
397
+ const timestamp = new Date().toISOString();
398
+ // Phase 2 Finale C3: skip `buildCompatibilityReport` (60–100 ms on
399
+ // extra-large) when the envelope carries a pre-computed report.
400
+ // Pure-function determinism of the report is enforced by
401
+ // `canonicalDocumentHash` (5th input to `deriveCacheKey`): any
402
+ // change to the canonical doc flips the hash and rejects the
403
+ // envelope on load.
404
+ //
405
+ // The cached report's `generatedAt` is a fixed sentinel
406
+ // (`CACHE_NORMALIZED_GENERATED_AT`) for envelope byte-identity.
407
+ // Swap it for the live ISO8601 timestamp here because downstream
408
+ // `validatePersistedEditorSnapshot` requires
409
+ // `$.compatibility.generatedAt` to be ISO 8601.
410
+ const cachedReport = laycacheEnvelope?.compatibilityReport as
411
+ | CompatibilityReport
412
+ | undefined;
413
+ const compatibility = cachedReport
414
+ ? { ...cachedReport, generatedAt: timestamp }
415
+ : buildCompatibilityReport({
416
+ document: documentWithChartPreviews,
417
+ generatedAt: timestamp,
418
+ });
419
+ await scheduler.yield();
420
+
421
+ const snapshot = createImportedSnapshot({
422
+ documentId: options.documentId,
423
+ editorBuild,
424
+ timestamp,
425
+ document: documentWithChartPreviews,
426
+ compatibility,
427
+ protectionSnapshot,
428
+ sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
429
+ workflowOverlay: embeddedWorkflowOverlay,
430
+ workflowMetadata: embeddedWorkflowMetadata,
431
+ visibilityPolicies: embeddedWorkflowPayload?.visibilityPolicies,
432
+ markupModePolicy: embeddedWorkflowPayload?.markupModePolicy ?? undefined,
433
+ });
434
+ const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
435
+ if (snapshotIssues.length > 0) {
436
+ const firstIssue = snapshotIssues[0];
437
+ return createDiagnosticsSession(
438
+ options,
439
+ createValidationImportDiagnostics({
440
+ message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
441
+ source: "import",
442
+ details: {
443
+ issueCount: snapshotIssues.length,
444
+ firstIssuePath: firstIssue?.path,
445
+ },
446
+ }),
447
+ );
448
+ }
449
+
450
+ // Build `initialSessionState` inline — bypassing
451
+ // `editorSessionStateFromPersistedSnapshot`'s structuredClone so
452
+ // `session.initialSessionState.canonicalDocument` is reference-equal
453
+ // to `envelope.canonicalDocument` (cloning a large canonical document
454
+ // defeats part of the cache gain).
455
+ const sessionState: EditorSessionState = {
456
+ sessionVersion: "editor-session-state/1",
457
+ schemaVersion: snapshot.schemaVersion,
458
+ documentId: snapshot.documentId,
459
+ docId: snapshot.docId,
460
+ createdAt: snapshot.createdAt,
461
+ updatedAt: snapshot.updatedAt,
462
+ editorBuild: snapshot.editorBuild,
463
+ canonicalDocument: snapshot.canonicalDocument,
464
+ compatibility: snapshot.compatibility,
465
+ warningLog: snapshot.warningLog,
466
+ protectionSnapshot: snapshot.protectionSnapshot,
467
+ sourcePackage: snapshot.sourcePackage,
468
+ workflowOverlay: snapshot.workflowOverlay,
469
+ workflowMetadata: snapshot.workflowMetadata,
470
+ };
471
+
472
+ // The short-circuit path does not carry an `ImportedDocxState`, so
473
+ // `exportDocx` lazily re-runs the cold path on first invocation and
474
+ // memoizes. Keeps the warm-load fast while preserving byte-exact
475
+ // export correctness.
476
+ let lazyColdExport: LoadedDocxEditorSession["exportDocx"] | undefined;
477
+ const exportDocx: LoadedDocxEditorSession["exportDocx"] = async (
478
+ nextSessionStateOrSnapshot,
479
+ exportOptions,
480
+ ) => {
481
+ if (!lazyColdExport) {
482
+ const { laycacheEnvelope: _unused, ...coldOptions } = options;
483
+ void _unused;
484
+ const coldSession = await loadDocxSessionAsync(coldOptions);
485
+ if (coldSession.fatalError) {
486
+ throw new Error(
487
+ `DOCX export via short-circuit fallback failed cold load: ${coldSession.fatalError.message ?? "fatal error"}`,
488
+ );
489
+ }
490
+ lazyColdExport = coldSession.exportDocx;
491
+ }
492
+ return lazyColdExport(nextSessionStateOrSnapshot, exportOptions);
493
+ };
494
+
495
+ stages.emit("skeleton-ready");
496
+ return {
497
+ initialSessionState: sessionState,
498
+ initialSnapshot: snapshot,
499
+ readOnly: false,
500
+ protectionSnapshot,
501
+ exportDocx,
502
+ ...(embeddedWorkflowPayload?.editorState
503
+ ? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
504
+ : {}),
505
+ };
506
+ }
507
+
508
+ const sourceDocumentXml = decodeUtf8(documentPart.bytes);
509
+ const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
510
+ const numberingPartPath = resolveDocumentRelatedPartPath(
511
+ sourcePackage,
512
+ mainDocumentPath,
513
+ documentPart.relationships,
514
+ NUMBERING_RELATIONSHIP_TYPE,
515
+ NUMBERING_PART_PATH,
516
+ );
517
+ const parsedNumbering = numberingPartPath
518
+ ? parseNumberingXml(
519
+ decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
520
+ {
521
+ relationships: sourcePackage.parts.get(numberingPartPath)?.relationships,
522
+ partPath: numberingPartPath,
523
+ },
524
+ )
525
+ : createEmptyNumberingCatalog();
526
+ const mediaParts = collectInlineMediaParts(sourcePackage);
527
+ const chartPartLookup = createChartPartLookup(
528
+ sourcePackage,
529
+ mainDocumentPath,
530
+ documentPart.relationships,
531
+ );
532
+ if (options.telemetryBus) {
533
+ setActiveParseTelemetryBus(options.telemetryBus as ParseTelemetryBusParam);
534
+ }
535
+ let parsedDocument;
536
+ try {
537
+ parsedDocument = parseMainDocumentXml(
538
+ sourceDocumentXml,
539
+ documentPart.relationships,
540
+ mediaParts,
541
+ mainDocumentPath,
542
+ chartPartLookup,
543
+ );
544
+ } finally {
545
+ if (options.telemetryBus) setActiveParseTelemetryBus(undefined);
546
+ }
547
+ await scheduler.yield();
548
+ const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
549
+ const normalizedDocument = await normalizeParsedTextDocumentAsync(
550
+ parsedDocument,
551
+ mainDocumentPath,
552
+ scheduler,
553
+ );
554
+ stages.emit("body");
555
+ await scheduler.yield();
556
+
557
+ // L7 Phase 2 Finale C2 — progressive initial mount.
558
+ //
559
+ // Fire `onProgressiveSnapshot` exactly once, after the body stage and
560
+ // its post-yield. At this point `normalizedDocument.content` carries
561
+ // the full block tree with per-block runProperties already resolved
562
+ // during `normalizeParsedTextDocumentAsync`. We synthesize a
563
+ // throw-away `CanonicalDocument` using the normalized content
564
+ // + empty style/review/preservation catalogs, then project a
565
+ // viewport-windowed `EditorSurfaceSnapshot` (first `PROGRESSIVE_VIEWPORT_BLOCKS`
566
+ // blocks real, rest as culled placeholders via the Phase 2.9 flag).
567
+ //
568
+ // The bench's measured signal: time from `loadDocxEditorSessionAsync`
569
+ // entry to this callback's fire is `firstViewportCommitMs` — the
570
+ // metric C2 gates on.
571
+ //
572
+ // Skipped on the Plan B short-circuit: `laycacheEnvelope !== undefined`
573
+ // already completes ~376 ms faster than cold — adding a progressive
574
+ // synthesis on top costs more than it saves. The short-circuit path
575
+ // returns the real snapshot fast enough.
576
+ if (
577
+ options.onProgressiveSnapshot !== undefined &&
578
+ options.laycacheEnvelope === undefined
579
+ ) {
580
+ const blocksTotal = normalizedDocument.content.children.length;
581
+ const blocksRealized = Math.min(
582
+ PROGRESSIVE_VIEWPORT_BLOCKS,
583
+ blocksTotal,
584
+ );
585
+ const progressiveSurface = options.surfaceProjector
586
+ ? options.surfaceProjector({
587
+ rootContent: normalizedDocument.content,
588
+ documentId: options.documentId,
589
+ blocksRealized,
590
+ blocksTotal,
591
+ })
592
+ : undefined;
593
+ try {
594
+ options.onProgressiveSnapshot({
595
+ ...(progressiveSurface !== undefined
596
+ ? { surface: progressiveSurface }
597
+ : {}),
598
+ phase: "viewport",
599
+ blocksRealized,
600
+ blocksTotal,
601
+ });
602
+ } catch {
603
+ // A throwing consumer must not abort the load. Progressive is
604
+ // a best-effort optimization; errors on the callback side
605
+ // silently fall through to the normal full-commit path.
606
+ }
607
+ }
608
+
609
+ const commentsPartPath = resolveCommentsPartPath(
610
+ sourcePackage,
611
+ mainDocumentPath,
612
+ documentPart.relationships,
613
+ );
614
+ const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
615
+ sourcePackage,
616
+ mainDocumentPath,
617
+ documentPart.relationships,
618
+ COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
619
+ COMMENTS_EXTENDED_PART_PATH,
620
+ );
621
+ const commentsIdsPartPath = resolveDocumentRelatedPartPath(
622
+ sourcePackage,
623
+ mainDocumentPath,
624
+ documentPart.relationships,
625
+ COMMENTS_IDS_RELATIONSHIP_TYPE,
626
+ COMMENTS_IDS_PART_PATH,
627
+ );
628
+ const peoplePartPath = resolveDocumentRelatedPartPath(
629
+ sourcePackage,
630
+ mainDocumentPath,
631
+ documentPart.relationships,
632
+ PEOPLE_RELATIONSHIP_TYPE,
633
+ PEOPLE_PART_PATH,
634
+ );
635
+ const parsedComments = commentsPartPath
636
+ ? parseCommentsFromOoxml(
637
+ sourceDocumentXml,
638
+ {
639
+ commentsXml: decodeUtf8(sourcePackage.parts.get(commentsPartPath)?.bytes ?? new Uint8Array()),
640
+ commentsExtendedXml: decodeUtf8(
641
+ sourcePackage.parts.get(commentsExtendedPartPath ?? "")?.bytes ?? new Uint8Array(),
642
+ ),
643
+ commentsIdsXml: decodeUtf8(
644
+ sourcePackage.parts.get(commentsIdsPartPath ?? "")?.bytes ?? new Uint8Array(),
645
+ ),
646
+ peopleXml: decodeUtf8(
647
+ sourcePackage.parts.get(peoplePartPath ?? "")?.bytes ?? new Uint8Array(),
648
+ ),
649
+ },
650
+ )
651
+ : {
652
+ threads: [] as CommentThread[],
653
+ diagnostics: [] as CommentImportDiagnostic[],
654
+ definitions: [] as ImportedCommentDefinition[],
655
+ sourceRootTag: undefined,
656
+ sourceExtendedRootTag: undefined,
657
+ sourceIdsRootTag: undefined,
658
+ sourcePeopleRootTag: undefined,
659
+ peopleAuthors: [] as string[],
660
+ };
661
+ const normalizedRevisions = normalizeImportedRevisionRecords(
662
+ importedRevisions,
663
+ normalizedDocument.content,
664
+ normalizedDocument.preservation.opaqueFragments,
665
+ );
666
+ const normalizedComments = normalizeImportedCommentThreads(
667
+ parsedComments,
668
+ normalizedDocument.preservation.opaqueFragments,
669
+ normalizedRevisions.revisions,
670
+ );
671
+ stages.emit("styles-numbering-comments");
672
+ const importedStoryRevisions: ReviewRevisionRecord[] = [];
673
+ const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
674
+ const subPartOpaqueState = createSubPartOpaqueImportState(
675
+ normalizedDocument.preservation.opaqueFragments,
676
+ normalizedDocument.diagnostics.warnings,
677
+ );
678
+ // ---- Parse sub-parts: headers, footers, footnotes, endnotes, theme ----
679
+ const headerFooterRefs = parseHeaderFooterReferences(sourceDocumentXml);
680
+ const parsedHeaders: HeaderDocument[] = [];
681
+ const parsedFooters: FooterDocument[] = [];
682
+ const sourceHeaderPaths: Array<{ partPath: string; relationshipId: string }> = [];
683
+ const sourceFooterPaths: Array<{ partPath: string; relationshipId: string }> = [];
684
+ const seenSubPartKeys = new Set<string>();
685
+
686
+ for (const ref of headerFooterRefs) {
687
+ const dedupeKey = `${ref.kind}:${ref.variant}:${ref.relationshipId}`;
688
+ if (seenSubPartKeys.has(dedupeKey)) {
689
+ continue;
690
+ }
691
+ seenSubPartKeys.add(dedupeKey);
692
+
693
+ const relationship = documentPart.relationships.find(
694
+ (r) => r.id === ref.relationshipId && r.targetMode === "internal",
695
+ );
696
+ if (!relationship) {
697
+ continue;
698
+ }
699
+
700
+ const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
701
+ const part = sourcePackage.parts.get(partPath);
702
+ const partBytes = part?.bytes;
703
+ if (!partBytes) {
704
+ continue;
705
+ }
706
+
707
+ await scheduler.yield();
708
+ const xml = decodeUtf8(partBytes);
709
+ const subPartRelationships = part?.relationships ?? [];
710
+ const subPartChartPartLookup = createChartPartLookup(
711
+ sourcePackage,
712
+ partPath,
713
+ subPartRelationships,
714
+ );
715
+ if (ref.kind === "header") {
716
+ const parsedHeaderRevisions = parseRevisionsFromStoryXml(xml);
717
+ const parsed = parseHeaderXml(xml, {
718
+ relationships: subPartRelationships,
719
+ mediaParts,
720
+ sourcePartPath: partPath,
721
+ chartPartLookup: subPartChartPartLookup,
722
+ });
723
+ parsedHeaders.push({
724
+ variant: ref.variant,
725
+ partPath,
726
+ relationshipId: ref.relationshipId,
727
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
728
+ blocks: normalizeSubPartOpaqueBlocks(
729
+ parsed.blocks,
730
+ normalizedDocument.preservation.opaqueFragments,
731
+ normalizedDocument.diagnostics.warnings,
732
+ partPath,
733
+ subPartOpaqueState,
734
+ ),
735
+ });
736
+ importedStoryRevisions.push(
737
+ ...parsedHeaderRevisions.revisions.map((revision): ReviewRevisionRecord => ({
738
+ ...revision,
739
+ metadata: {
740
+ ...revision.metadata,
741
+ storyTarget: {
742
+ kind: "header" as const,
743
+ relationshipId: ref.relationshipId,
744
+ variant: ref.variant,
745
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
746
+ },
747
+ },
748
+ })),
749
+ );
750
+ importedStoryRevisionDiagnostics.push(...parsedHeaderRevisions.diagnostics);
751
+ sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
752
+ } else {
753
+ const parsedFooterRevisions = parseRevisionsFromStoryXml(xml);
754
+ const parsed = parseFooterXml(xml, {
755
+ relationships: subPartRelationships,
756
+ mediaParts,
757
+ sourcePartPath: partPath,
758
+ chartPartLookup: subPartChartPartLookup,
759
+ });
760
+ parsedFooters.push({
761
+ variant: ref.variant,
762
+ partPath,
763
+ relationshipId: ref.relationshipId,
764
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
765
+ blocks: normalizeSubPartOpaqueBlocks(
766
+ parsed.blocks,
767
+ normalizedDocument.preservation.opaqueFragments,
768
+ normalizedDocument.diagnostics.warnings,
769
+ partPath,
770
+ subPartOpaqueState,
771
+ ),
772
+ });
773
+ importedStoryRevisions.push(
774
+ ...parsedFooterRevisions.revisions.map((revision): ReviewRevisionRecord => ({
775
+ ...revision,
776
+ metadata: {
777
+ ...revision.metadata,
778
+ storyTarget: {
779
+ kind: "footer" as const,
780
+ relationshipId: ref.relationshipId,
781
+ variant: ref.variant,
782
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
783
+ },
784
+ },
785
+ })),
786
+ );
787
+ importedStoryRevisionDiagnostics.push(...parsedFooterRevisions.diagnostics);
788
+ sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
789
+ }
790
+ }
791
+
792
+ const footnotesPartPath = resolveDocumentRelatedPartPath(
793
+ sourcePackage,
794
+ mainDocumentPath,
795
+ documentPart.relationships,
796
+ FOOTNOTES_RELATIONSHIP_TYPE,
797
+ FOOTNOTES_PART_PATH,
798
+ );
799
+ const footnotesRelationshipId = documentPart.relationships.find(
800
+ (r) => r.type === FOOTNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
801
+ )?.id;
802
+ const endnotesPartPath = resolveDocumentRelatedPartPath(
803
+ sourcePackage,
804
+ mainDocumentPath,
805
+ documentPart.relationships,
806
+ ENDNOTES_RELATIONSHIP_TYPE,
807
+ ENDNOTES_PART_PATH,
808
+ );
809
+ const endnotesRelationshipId = documentPart.relationships.find(
810
+ (r) => r.type === ENDNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
811
+ )?.id;
812
+
813
+ let footnoteCollection: FootnoteCollection | undefined;
814
+ if (footnotesPartPath) {
815
+ footnoteCollection = parseFootnotesXml(
816
+ decodeUtf8(sourcePackage.parts.get(footnotesPartPath)?.bytes ?? new Uint8Array()),
817
+ );
818
+ normalizeFootnoteCollectionOpaqueBlocks(
819
+ footnoteCollection,
820
+ "footnote",
821
+ normalizedDocument.preservation.opaqueFragments,
822
+ normalizedDocument.diagnostics.warnings,
823
+ footnotesPartPath,
824
+ subPartOpaqueState,
825
+ );
826
+ }
827
+ if (endnotesPartPath) {
828
+ footnoteCollection = parseEndnotesXml(
829
+ decodeUtf8(sourcePackage.parts.get(endnotesPartPath)?.bytes ?? new Uint8Array()),
830
+ footnoteCollection,
831
+ );
832
+ normalizeFootnoteCollectionOpaqueBlocks(
833
+ footnoteCollection,
834
+ "endnote",
835
+ normalizedDocument.preservation.opaqueFragments,
836
+ normalizedDocument.diagnostics.warnings,
837
+ endnotesPartPath,
838
+ subPartOpaqueState,
839
+ );
840
+ }
841
+ await scheduler.yield();
842
+
843
+ const themeRelationship = documentPart.relationships.find(
844
+ (r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" &&
845
+ r.targetMode === "internal",
846
+ );
847
+ const themePartPath = themeRelationship
848
+ ? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
849
+ : undefined;
850
+ const parsedTheme =
851
+ themePartPath && sourcePackage.parts.has(themePartPath)
852
+ ? parseThemeXml(
853
+ decodeUtf8(sourcePackage.parts.get(themePartPath)?.bytes ?? new Uint8Array()),
854
+ )
855
+ : undefined;
856
+ const resolvedTheme = parsedTheme ? resolveTheme(parsedTheme) : undefined;
857
+ const settingsPartPath = resolveDocumentRelatedPartPath(
858
+ sourcePackage,
859
+ mainDocumentPath,
860
+ documentPart.relationships,
861
+ SETTINGS_RELATIONSHIP_TYPE,
862
+ SETTINGS_PART_PATH,
863
+ );
864
+ const parsedSettings =
865
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
866
+ ? parseSettingsXml(
867
+ decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
868
+ )
869
+ : undefined;
870
+ const canonicalTheme =
871
+ parsedTheme !== undefined
872
+ ? materializeCanonicalTheme(
873
+ parsedTheme,
874
+ parsedSettings?.clrSchemeMapping ?? {},
875
+ )
876
+ : undefined;
877
+ const settingsXmlForProtection =
878
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
879
+ ? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
880
+ : "";
881
+ const documentProtection = extractDocumentProtection(settingsXmlForProtection);
882
+ const importedProtectionSnapshot = buildProtectionSnapshot(documentProtection, protectionRanges);
883
+
884
+ // ---- Parse styles.xml for canonical style catalog ----
885
+ const stylesPartPath = resolveDocumentRelatedPartPath(
886
+ sourcePackage,
887
+ mainDocumentPath,
888
+ documentPart.relationships,
889
+ STYLES_RELATIONSHIP_TYPE,
890
+ STYLES_PART_PATH,
891
+ );
892
+ const parsedStyles =
893
+ stylesPartPath && sourcePackage.parts.has(stylesPartPath)
894
+ ? parseStylesXml(
895
+ decodeUtf8(sourcePackage.parts.get(stylesPartPath)?.bytes ?? new Uint8Array()),
896
+ )
897
+ : parseStylesXml("");
898
+ await scheduler.yield();
899
+
900
+ // ---- Parse fontTable.xml for canonical font catalog ----
901
+ const fontTablePartPath = resolveDocumentRelatedPartPath(
902
+ sourcePackage,
903
+ mainDocumentPath,
904
+ documentPart.relationships,
905
+ FONT_TABLE_RELATIONSHIP_TYPE,
906
+ FONT_TABLE_PART_PATH,
907
+ );
908
+ const parsedFontTable =
909
+ fontTablePartPath && sourcePackage.parts.has(fontTablePartPath)
910
+ ? parseFontTable(
911
+ decodeUtf8(sourcePackage.parts.get(fontTablePartPath)?.bytes ?? new Uint8Array()),
912
+ )
913
+ : undefined;
914
+
915
+ const mergedMedia = mergeSecondaryStoryMediaCatalog(normalizedDocument.media, {
916
+ headers: parsedHeaders,
917
+ footers: parsedFooters,
918
+ footnoteCollection,
919
+ mediaParts,
920
+ });
921
+
922
+ const subParts: SubPartsCatalog | undefined =
923
+ parsedHeaders.length > 0 ||
924
+ parsedFooters.length > 0 ||
925
+ footnoteCollection !== undefined ||
926
+ parsedTheme !== undefined ||
927
+ normalizedDocument.finalSectionProperties !== undefined ||
928
+ resolvedTheme !== undefined ||
929
+ canonicalTheme !== undefined ||
930
+ parsedSettings !== undefined
931
+ ? {
932
+ headers: parsedHeaders,
933
+ footers: parsedFooters,
934
+ ...(footnoteCollection !== undefined ? { footnoteCollection } : {}),
935
+ ...(parsedTheme !== undefined ? { theme: parsedTheme } : {}),
936
+ ...(normalizedDocument.finalSectionProperties !== undefined
937
+ ? { finalSectionProperties: normalizedDocument.finalSectionProperties }
938
+ : {}),
939
+ ...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
940
+ ...(canonicalTheme !== undefined ? { canonicalTheme } : {}),
941
+ ...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
942
+ }
943
+ : undefined;
944
+
945
+ const timestamp = new Date().toISOString();
946
+ const translatedWorkflowState = translateClmCommentsToWorkflow({
947
+ comments: normalizedComments.threads,
948
+ workflowOverlay: embeddedWorkflowOverlay,
949
+ workflowMetadata: embeddedWorkflowMetadata,
950
+ timestamp,
951
+ });
952
+ // P8 Step 7 — embedded-document offload. First, check whether the
953
+ // source package already carries a `bw:embeddings` customXml
954
+ // namespace from a prior offload (reopen path). If so, skip the
955
+ // fresh `storeEmbeddedDocument` pass and reuse the hydrated
956
+ // entries. Otherwise run discovery + offload; entries are empty
957
+ // when no adapter is present, when zero offloadable manifests
958
+ // were found, or when every adapter call failed (fall-through to
959
+ // store-only semantics). See architecture §P8 and
960
+ // `src/session/import/embedded-offload.ts`.
961
+ const embeddedDocumentManifestsEarly = collectEmbeddedDocuments(
962
+ sourcePackage,
963
+ mainDocumentPath,
964
+ ).manifests;
965
+ const rehydratedOffload = hydrateOffloadEntriesFromPayload(
966
+ embeddedWorkflowPayload?.editorState,
967
+ );
968
+ let offloadEntries: readonly EmbeddingOffloadEntry[];
969
+ let offloadOwnedPartPaths: readonly string[];
970
+ if (rehydratedOffload.entries.length > 0) {
971
+ // REOPEN path — entries already exist in customXml; no fresh
972
+ // adapter call, no sha / base64 work on the critical path.
973
+ offloadEntries = rehydratedOffload.entries;
974
+ offloadOwnedPartPaths = rehydratedOffload.ownedPartPaths;
975
+ } else if (options.offloadEmbeddedDocuments === true) {
976
+ // Fresh offload opt-in. Hosts that want offload pay the cold-
977
+ // open cost only when they explicitly set the flag. Supplying
978
+ // `hostAdapter` alone (for chart-preview / save-session /
979
+ // log-event etc.) does NOT trigger offload — offload is a
980
+ // separate decision from adapter presence per refactor/04's
981
+ // cross-lane perf finding (cross-layer-coord-04 §1.13): sha256
982
+ // + base64 of large offloadable embeddings costs ~300 ms per
983
+ // MB on the critical path.
984
+ const offloadResult = await processEmbeddedOffload({
985
+ parentDocumentId: options.documentId,
986
+ manifests: embeddedDocumentManifestsEarly,
987
+ hostAdapter: options.hostAdapter,
988
+ resolvePartPath: (rId) => {
989
+ const rel = documentPart.relationships.find(
990
+ (r) => r.id === rId && r.targetMode === "internal",
991
+ );
992
+ return rel ? resolveRelationshipTarget(mainDocumentPath, rel) : undefined;
993
+ },
994
+ resolveContentType: (path) =>
995
+ sourcePackage.parts.get(path)?.contentType ?? undefined,
996
+ });
997
+ offloadEntries = offloadResult.entries;
998
+ offloadOwnedPartPaths = offloadResult.ownedPartPaths;
999
+ } else {
1000
+ // Default path — no offload. Offloadable manifests fall
1001
+ // through to the preservation store (same as store-only
1002
+ // behavior). Zero-cost; hosts that never opt in never pay.
1003
+ offloadEntries = [];
1004
+ offloadOwnedPartPaths = [];
1005
+ }
1006
+ const importedDocument = createImportedCanonicalDocument({
1007
+ documentId: options.documentId,
1008
+ timestamp,
1009
+ numbering: parsedNumbering,
1010
+ media: mergedMedia,
1011
+ content: normalizedDocument.content,
1012
+ subParts,
1013
+ parsedStyles,
1014
+ fontTable: parsedFontTable,
1015
+ preservation: buildImportPreservation(normalizedDocument.preservation, sourcePackage, [
1016
+ mainDocumentPath,
1017
+ numberingPartPath,
1018
+ commentsPartPath,
1019
+ commentsExtendedPartPath,
1020
+ commentsIdsPartPath,
1021
+ peoplePartPath,
1022
+ ...offloadOwnedPartPaths,
1023
+ ]),
1024
+ diagnostics: {
1025
+ warnings: [
1026
+ ...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
1027
+ ...normalizedDocument.diagnostics.warnings,
1028
+ ...mapRevisionDiagnosticsToWarnings(normalizedRevisions.diagnostics),
1029
+ ...mapStoryRevisionDiagnosticsToWarnings(importedStoryRevisionDiagnostics),
1030
+ ...mapCommentDiagnosticsToWarnings(normalizedComments.diagnostics),
1031
+ ],
1032
+ errors: [],
1033
+ },
1034
+ review: {
1035
+ comments: toRuntimeCommentRecords(translatedWorkflowState.comments),
1036
+ revisions: toRuntimeRevisionRecords([
1037
+ ...normalizedRevisions.revisions,
1038
+ ...importedStoryRevisions,
1039
+ ]),
1040
+ },
1041
+ });
1042
+ // Stage 0B.1: if the host implements `renderChartPreview`, resolve
1043
+ // chart_preview nodes inline so the first snapshot already carries the
1044
+ // synthesized `previewMediaId`. Fallback-safe: returning null or throwing
1045
+ // is per-chart — the typed badge renders as if the adapter weren't set.
1046
+ // C3b: when onChartPreviewsReady is provided, defer resolution out of
1047
+ // the critical path (same pattern as the short-circuit branch above).
1048
+ let document: CanonicalDocument;
1049
+ if (options.onChartPreviewsReady) {
1050
+ scheduleChartPreviewResolution(
1051
+ importedDocument,
1052
+ sourcePackage,
1053
+ options.hostAdapter,
1054
+ options.onChartPreviewsReady,
1055
+ );
1056
+ document = importedDocument as CanonicalDocument;
1057
+ } else {
1058
+ document = (await resolveChartPreviewsForDocument(
1059
+ importedDocument,
1060
+ sourcePackage,
1061
+ options.hostAdapter,
1062
+ )) as CanonicalDocument;
1063
+ }
1064
+ const compatibility = buildCompatibilityReport({
1065
+ document,
1066
+ generatedAt: timestamp,
1067
+ });
1068
+ await scheduler.yield();
1069
+ const snapshot = createImportedSnapshot({
1070
+ documentId: options.documentId,
1071
+ editorBuild,
1072
+ timestamp,
1073
+ document,
1074
+ compatibility,
1075
+ protectionSnapshot: importedProtectionSnapshot,
1076
+ sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
1077
+ workflowOverlay: translatedWorkflowState.workflowOverlay,
1078
+ workflowMetadata: translatedWorkflowState.workflowMetadata,
1079
+ visibilityPolicies: embeddedWorkflowPayload?.visibilityPolicies,
1080
+ markupModePolicy: embeddedWorkflowPayload?.markupModePolicy ?? undefined,
1081
+ });
1082
+ const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
1083
+ if (snapshotIssues.length > 0) {
1084
+ const firstIssue = snapshotIssues[0];
1085
+ return createDiagnosticsSession(
1086
+ options,
1087
+ createValidationImportDiagnostics({
1088
+ message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
1089
+ source: "import",
1090
+ details: {
1091
+ issueCount: snapshotIssues.length,
1092
+ firstIssuePath: firstIssue?.path,
1093
+ },
1094
+ }),
1095
+ );
1096
+ }
1097
+ const initialSessionState = editorSessionStateFromPersistedSnapshot(snapshot);
1098
+ const importedState: ImportedDocxState = {
1099
+ sourceBytes: new Uint8Array(sourceBytes),
1100
+ sourcePackage,
1101
+ sourceDocumentXml,
1102
+ sourceDocumentPartPath: mainDocumentPath,
1103
+ sourceDocumentRelationships: documentPart.relationships,
1104
+ sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
1105
+ sourceNumberingPartPath: numberingPartPath,
1106
+ sourceNumberingRelationshipId: documentPart.relationships.find(
1107
+ (relationship) =>
1108
+ relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
1109
+ relationship.targetMode === "internal",
1110
+ )?.id,
1111
+ sourceSettingsPartPath: settingsPartPath,
1112
+ sourceSettingsXml:
1113
+ settingsXmlForProtection.length > 0 ? settingsXmlForProtection : undefined,
1114
+ sourceCommentsPartPath: commentsPartPath,
1115
+ sourceCommentsRelationshipId: documentPart.relationships.find(
1116
+ (relationship) =>
1117
+ relationship.type === COMMENTS_RELATIONSHIP_TYPE &&
1118
+ relationship.targetMode === "internal",
1119
+ )?.id,
1120
+ sourceCommentsRootTag: normalizedComments.sourceRootTag,
1121
+ sourceCommentsExtendedPartPath: commentsExtendedPartPath,
1122
+ sourceCommentsExtendedRelationshipId: documentPart.relationships.find(
1123
+ (relationship) =>
1124
+ relationship.type === COMMENTS_EXTENDED_RELATIONSHIP_TYPE &&
1125
+ relationship.targetMode === "internal",
1126
+ )?.id,
1127
+ sourceCommentsExtendedRootTag: normalizedComments.sourceExtendedRootTag,
1128
+ sourceCommentsIdsPartPath: commentsIdsPartPath,
1129
+ sourceCommentsIdsRelationshipId: documentPart.relationships.find(
1130
+ (relationship) =>
1131
+ relationship.type === COMMENTS_IDS_RELATIONSHIP_TYPE &&
1132
+ relationship.targetMode === "internal",
1133
+ )?.id,
1134
+ sourceCommentsIdsRootTag: normalizedComments.sourceIdsRootTag,
1135
+ sourcePeoplePartPath: peoplePartPath,
1136
+ sourcePeopleRelationshipId: documentPart.relationships.find(
1137
+ (relationship) =>
1138
+ relationship.type === PEOPLE_RELATIONSHIP_TYPE &&
1139
+ relationship.targetMode === "internal",
1140
+ )?.id,
1141
+ sourcePeopleRootTag: normalizedComments.sourcePeopleRootTag,
1142
+ sourcePeopleAuthors: normalizedComments.peopleAuthors,
1143
+ protectionSnapshot: buildProtectionSnapshot(documentProtection, protectionRanges),
1144
+ preservedCommentDefinitions: normalizedComments.preservedDefinitions,
1145
+ blockingCommentDiagnostics: normalizedComments.diagnostics.filter((diagnostic) =>
1146
+ BLOCKING_COMMENT_DIAGNOSTIC_CODES.has(diagnostic.code),
1147
+ ),
1148
+ initialCanonicalSignature: serializeCanonicalDocumentForExport(document),
1149
+ sourceSubPartPaths: {
1150
+ headers: sourceHeaderPaths,
1151
+ footers: sourceFooterPaths,
1152
+ footnotesPartPath,
1153
+ footnotesRelationshipId,
1154
+ endnotesPartPath,
1155
+ endnotesRelationshipId,
1156
+ themePartPath,
1157
+ themeRelationshipId: themeRelationship?.id,
1158
+ },
1159
+ embeddingOffloadEntries: offloadEntries,
1160
+ ...(options.hostAdapter !== undefined
1161
+ ? { hostAdapter: options.hostAdapter }
1162
+ : {}),
1163
+ };
1164
+
1165
+ stages.emit("skeleton-ready");
1166
+ return {
1167
+ initialSessionState,
1168
+ initialSnapshot: snapshot,
1169
+ readOnly: false,
1170
+ protectionSnapshot: importedProtectionSnapshot,
1171
+ exportDocx: async (nextSessionState, exportOptions) =>
1172
+ runStatefulExport(importedState, nextSessionState, exportOptions),
1173
+ embeddedDocumentManifests: embeddedDocumentManifestsEarly,
1174
+ ...(embeddedWorkflowPayload?.editorState
1175
+ ? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
1176
+ : {}),
1177
+ };
1178
+ } catch (error) {
1179
+ return createDiagnosticsSession(
1180
+ options,
1181
+ createImportDiagnosticsFromError(error),
1182
+ );
1183
+ }
1184
+ }
1185
+
1186
+ /**
1187
+ * Session-layer owner of the sync load path.
1188
+ *
1189
+ * Slice 5e-7.e.2 relocates the body of `loadDocxEditorSession` from
1190
+ * `src/io/docx-session.ts` into this module. Body mirrors
1191
+ * `loadDocxSessionAsync` minus the async-only orchestration shell:
1192
+ * - No `scheduler.yield()` calls (sync execution).
1193
+ * - No laycache short-circuit branch (`laycacheEnvelope` is not on
1194
+ * `LoadDocxEditorSessionOptions`).
1195
+ * - No progressive-snapshot branch (`onProgressiveSnapshot` /
1196
+ * `surfaceProjector` not on `LoadDocxEditorSessionOptions`).
1197
+ * - Uses `normalizeParsedTextDocument` (sync).
1198
+ *
1199
+ * Byte-equivalence with the async path is asserted by
1200
+ * `test/io/fastload-parity.test.ts` on every F*.docx fixture. Drift
1201
+ * between the two sync/async bodies will fail that test.
1202
+ */
1203
+ export function loadDocxSessionSync(
1204
+ options: LoadDocxEditorSessionOptions,
1205
+ ): LoadedDocxEditorSession {
1206
+ const editorBuild =
1207
+ typeof options.editorBuild === "string" && options.editorBuild.length > 0
1208
+ ? options.editorBuild
1209
+ : "dev";
1210
+ const sourceBytes = toUint8Array(options.bytes);
1211
+
1212
+ const stages = createStageEmitter(options.onLoadStage);
1213
+
1214
+ let sourcePackage: OpcPackage;
1215
+
1216
+ try {
1217
+ sourcePackage = readOpcPackage(sourceBytes);
1218
+ } catch (error) {
1219
+ return createDiagnosticsSession(
1220
+ options,
1221
+ createPackageImportDiagnostics({
1222
+ issue: classifyCorruptPackageError(error),
1223
+ }),
1224
+ );
1225
+ }
1226
+ stages.emit("opc");
1227
+ const embeddedWorkflowPayload = parseWorkflowPayloadEnvelopeFromPackage(sourcePackage);
1228
+ const embeddedWorkflowMetadata = embeddedWorkflowPayload?.workflowMetadata;
1229
+ const embeddedWorkflowOverlay = embeddedWorkflowPayload?.workflowOverlay;
1230
+
1231
+ const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
1232
+ const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
1233
+ sourcePackage,
1234
+ mainDocumentPath,
1235
+ );
1236
+ if (brokenRelationshipIssues.length > 0) {
1237
+ return createDiagnosticsSession(
1238
+ options,
1239
+ createPackageImportDiagnostics({
1240
+ issue: {
1241
+ ...brokenRelationshipIssues[0],
1242
+ message: summarizeBrokenRelationshipIssues(brokenRelationshipIssues),
1243
+ details: {
1244
+ issueCount: brokenRelationshipIssues.length,
1245
+ targets: brokenRelationshipIssues.map((issue) => issue.targetPartPath).filter(Boolean),
1246
+ },
1247
+ },
1248
+ }),
1249
+ );
1250
+ }
1251
+
1252
+ if (!mainDocumentPath) {
1253
+ return createDiagnosticsSession(
1254
+ options,
1255
+ createPackageImportDiagnostics({
1256
+ issue: createMissingPartIssue(MAIN_DOCUMENT_PATH),
1257
+ }),
1258
+ );
1259
+ }
1260
+
1261
+ const documentPart = sourcePackage.parts.get(mainDocumentPath);
1262
+ if (!documentPart) {
1263
+ return createDiagnosticsSession(
1264
+ options,
1265
+ createPackageImportDiagnostics({
1266
+ issue: createMissingPartIssue(mainDocumentPath),
1267
+ }),
1268
+ );
1269
+ }
1270
+ if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
1271
+ return createDiagnosticsSession(
1272
+ options,
1273
+ createValidationImportDiagnostics({
1274
+ message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
1275
+ }),
1276
+ );
1277
+ }
1278
+
1279
+ try {
1280
+ const sourceDocumentXml = decodeUtf8(documentPart.bytes);
1281
+ const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
1282
+ const numberingPartPath = resolveDocumentRelatedPartPath(
1283
+ sourcePackage,
1284
+ mainDocumentPath,
1285
+ documentPart.relationships,
1286
+ NUMBERING_RELATIONSHIP_TYPE,
1287
+ NUMBERING_PART_PATH,
1288
+ );
1289
+ const parsedNumbering = numberingPartPath
1290
+ ? parseNumberingXml(
1291
+ decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
1292
+ {
1293
+ relationships: sourcePackage.parts.get(numberingPartPath)?.relationships,
1294
+ partPath: numberingPartPath,
1295
+ },
1296
+ )
1297
+ : createEmptyNumberingCatalog();
1298
+ const mediaParts = collectInlineMediaParts(sourcePackage);
1299
+ const chartPartLookup = createChartPartLookup(
1300
+ sourcePackage,
1301
+ mainDocumentPath,
1302
+ documentPart.relationships,
1303
+ );
1304
+ if (options.telemetryBus) {
1305
+ setActiveParseTelemetryBus(options.telemetryBus as ParseTelemetryBusParam);
1306
+ }
1307
+ let parsedDocument;
1308
+ try {
1309
+ parsedDocument = parseMainDocumentXml(
1310
+ sourceDocumentXml,
1311
+ documentPart.relationships,
1312
+ mediaParts,
1313
+ mainDocumentPath,
1314
+ chartPartLookup,
1315
+ );
1316
+ } finally {
1317
+ if (options.telemetryBus) setActiveParseTelemetryBus(undefined);
1318
+ }
1319
+ const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
1320
+ const normalizedDocument = normalizeParsedTextDocument(
1321
+ parsedDocument,
1322
+ mainDocumentPath,
1323
+ );
1324
+ stages.emit("body");
1325
+ const commentsPartPath = resolveCommentsPartPath(
1326
+ sourcePackage,
1327
+ mainDocumentPath,
1328
+ documentPart.relationships,
1329
+ );
1330
+ const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
1331
+ sourcePackage,
1332
+ mainDocumentPath,
1333
+ documentPart.relationships,
1334
+ COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
1335
+ COMMENTS_EXTENDED_PART_PATH,
1336
+ );
1337
+ const commentsIdsPartPath = resolveDocumentRelatedPartPath(
1338
+ sourcePackage,
1339
+ mainDocumentPath,
1340
+ documentPart.relationships,
1341
+ COMMENTS_IDS_RELATIONSHIP_TYPE,
1342
+ COMMENTS_IDS_PART_PATH,
1343
+ );
1344
+ const peoplePartPath = resolveDocumentRelatedPartPath(
1345
+ sourcePackage,
1346
+ mainDocumentPath,
1347
+ documentPart.relationships,
1348
+ PEOPLE_RELATIONSHIP_TYPE,
1349
+ PEOPLE_PART_PATH,
1350
+ );
1351
+ const parsedComments = commentsPartPath
1352
+ ? parseCommentsFromOoxml(
1353
+ sourceDocumentXml,
1354
+ {
1355
+ commentsXml: decodeUtf8(sourcePackage.parts.get(commentsPartPath)?.bytes ?? new Uint8Array()),
1356
+ commentsExtendedXml: decodeUtf8(
1357
+ sourcePackage.parts.get(commentsExtendedPartPath ?? "")?.bytes ?? new Uint8Array(),
1358
+ ),
1359
+ commentsIdsXml: decodeUtf8(
1360
+ sourcePackage.parts.get(commentsIdsPartPath ?? "")?.bytes ?? new Uint8Array(),
1361
+ ),
1362
+ peopleXml: decodeUtf8(
1363
+ sourcePackage.parts.get(peoplePartPath ?? "")?.bytes ?? new Uint8Array(),
1364
+ ),
1365
+ },
1366
+ )
1367
+ : {
1368
+ threads: [] as CommentThread[],
1369
+ diagnostics: [] as CommentImportDiagnostic[],
1370
+ definitions: [] as ImportedCommentDefinition[],
1371
+ sourceRootTag: undefined,
1372
+ sourceExtendedRootTag: undefined,
1373
+ sourceIdsRootTag: undefined,
1374
+ sourcePeopleRootTag: undefined,
1375
+ peopleAuthors: [] as string[],
1376
+ };
1377
+ const normalizedRevisions = normalizeImportedRevisionRecords(
1378
+ importedRevisions,
1379
+ normalizedDocument.content,
1380
+ normalizedDocument.preservation.opaqueFragments,
1381
+ );
1382
+ const normalizedComments = normalizeImportedCommentThreads(
1383
+ parsedComments,
1384
+ normalizedDocument.preservation.opaqueFragments,
1385
+ normalizedRevisions.revisions,
1386
+ );
1387
+ stages.emit("styles-numbering-comments");
1388
+ const importedStoryRevisions: ReviewRevisionRecord[] = [];
1389
+ const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
1390
+ const subPartOpaqueState = createSubPartOpaqueImportState(
1391
+ normalizedDocument.preservation.opaqueFragments,
1392
+ normalizedDocument.diagnostics.warnings,
1393
+ );
1394
+ // ---- Parse sub-parts: headers, footers, footnotes, endnotes, theme ----
1395
+ const headerFooterRefs = parseHeaderFooterReferences(sourceDocumentXml);
1396
+ const parsedHeaders: HeaderDocument[] = [];
1397
+ const parsedFooters: FooterDocument[] = [];
1398
+ const sourceHeaderPaths: Array<{ partPath: string; relationshipId: string }> = [];
1399
+ const sourceFooterPaths: Array<{ partPath: string; relationshipId: string }> = [];
1400
+ const seenSubPartKeys = new Set<string>();
1401
+
1402
+ for (const ref of headerFooterRefs) {
1403
+ const dedupeKey = `${ref.kind}:${ref.variant}:${ref.relationshipId}`;
1404
+ if (seenSubPartKeys.has(dedupeKey)) {
1405
+ continue;
1406
+ }
1407
+ seenSubPartKeys.add(dedupeKey);
1408
+
1409
+ const relationship = documentPart.relationships.find(
1410
+ (r) => r.id === ref.relationshipId && r.targetMode === "internal",
1411
+ );
1412
+ if (!relationship) {
1413
+ continue;
1414
+ }
1415
+
1416
+ const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
1417
+ const part = sourcePackage.parts.get(partPath);
1418
+ const partBytes = part?.bytes;
1419
+ if (!partBytes) {
1420
+ continue;
1421
+ }
1422
+
1423
+ const xml = decodeUtf8(partBytes);
1424
+ const subPartRelationships = part?.relationships ?? [];
1425
+ const subPartChartPartLookup = createChartPartLookup(
1426
+ sourcePackage,
1427
+ partPath,
1428
+ subPartRelationships,
1429
+ );
1430
+ if (ref.kind === "header") {
1431
+ const parsedHeaderRevisions = parseRevisionsFromStoryXml(xml);
1432
+ const parsed = parseHeaderXml(xml, {
1433
+ relationships: subPartRelationships,
1434
+ mediaParts,
1435
+ sourcePartPath: partPath,
1436
+ chartPartLookup: subPartChartPartLookup,
1437
+ });
1438
+ parsedHeaders.push({
1439
+ variant: ref.variant,
1440
+ partPath,
1441
+ relationshipId: ref.relationshipId,
1442
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1443
+ blocks: normalizeSubPartOpaqueBlocks(
1444
+ parsed.blocks,
1445
+ normalizedDocument.preservation.opaqueFragments,
1446
+ normalizedDocument.diagnostics.warnings,
1447
+ partPath,
1448
+ subPartOpaqueState,
1449
+ ),
1450
+ });
1451
+ importedStoryRevisions.push(
1452
+ ...parsedHeaderRevisions.revisions.map((revision): ReviewRevisionRecord => ({
1453
+ ...revision,
1454
+ metadata: {
1455
+ ...revision.metadata,
1456
+ storyTarget: {
1457
+ kind: "header" as const,
1458
+ relationshipId: ref.relationshipId,
1459
+ variant: ref.variant,
1460
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1461
+ },
1462
+ },
1463
+ })),
1464
+ );
1465
+ importedStoryRevisionDiagnostics.push(...parsedHeaderRevisions.diagnostics);
1466
+ sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
1467
+ } else {
1468
+ const parsedFooterRevisions = parseRevisionsFromStoryXml(xml);
1469
+ const parsed = parseFooterXml(xml, {
1470
+ relationships: subPartRelationships,
1471
+ mediaParts,
1472
+ sourcePartPath: partPath,
1473
+ chartPartLookup: subPartChartPartLookup,
1474
+ });
1475
+ parsedFooters.push({
1476
+ variant: ref.variant,
1477
+ partPath,
1478
+ relationshipId: ref.relationshipId,
1479
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1480
+ blocks: normalizeSubPartOpaqueBlocks(
1481
+ parsed.blocks,
1482
+ normalizedDocument.preservation.opaqueFragments,
1483
+ normalizedDocument.diagnostics.warnings,
1484
+ partPath,
1485
+ subPartOpaqueState,
1486
+ ),
1487
+ });
1488
+ importedStoryRevisions.push(
1489
+ ...parsedFooterRevisions.revisions.map((revision): ReviewRevisionRecord => ({
1490
+ ...revision,
1491
+ metadata: {
1492
+ ...revision.metadata,
1493
+ storyTarget: {
1494
+ kind: "footer" as const,
1495
+ relationshipId: ref.relationshipId,
1496
+ variant: ref.variant,
1497
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1498
+ },
1499
+ },
1500
+ })),
1501
+ );
1502
+ importedStoryRevisionDiagnostics.push(...parsedFooterRevisions.diagnostics);
1503
+ sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
1504
+ }
1505
+ }
1506
+
1507
+ const footnotesPartPath = resolveDocumentRelatedPartPath(
1508
+ sourcePackage,
1509
+ mainDocumentPath,
1510
+ documentPart.relationships,
1511
+ FOOTNOTES_RELATIONSHIP_TYPE,
1512
+ FOOTNOTES_PART_PATH,
1513
+ );
1514
+ const footnotesRelationshipId = documentPart.relationships.find(
1515
+ (r) => r.type === FOOTNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
1516
+ )?.id;
1517
+ const endnotesPartPath = resolveDocumentRelatedPartPath(
1518
+ sourcePackage,
1519
+ mainDocumentPath,
1520
+ documentPart.relationships,
1521
+ ENDNOTES_RELATIONSHIP_TYPE,
1522
+ ENDNOTES_PART_PATH,
1523
+ );
1524
+ const endnotesRelationshipId = documentPart.relationships.find(
1525
+ (r) => r.type === ENDNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
1526
+ )?.id;
1527
+
1528
+ let footnoteCollection: FootnoteCollection | undefined;
1529
+ if (footnotesPartPath) {
1530
+ footnoteCollection = parseFootnotesXml(
1531
+ decodeUtf8(sourcePackage.parts.get(footnotesPartPath)?.bytes ?? new Uint8Array()),
1532
+ );
1533
+ normalizeFootnoteCollectionOpaqueBlocks(
1534
+ footnoteCollection,
1535
+ "footnote",
1536
+ normalizedDocument.preservation.opaqueFragments,
1537
+ normalizedDocument.diagnostics.warnings,
1538
+ footnotesPartPath,
1539
+ subPartOpaqueState,
1540
+ );
1541
+ }
1542
+ if (endnotesPartPath) {
1543
+ footnoteCollection = parseEndnotesXml(
1544
+ decodeUtf8(sourcePackage.parts.get(endnotesPartPath)?.bytes ?? new Uint8Array()),
1545
+ footnoteCollection,
1546
+ );
1547
+ normalizeFootnoteCollectionOpaqueBlocks(
1548
+ footnoteCollection,
1549
+ "endnote",
1550
+ normalizedDocument.preservation.opaqueFragments,
1551
+ normalizedDocument.diagnostics.warnings,
1552
+ endnotesPartPath,
1553
+ subPartOpaqueState,
1554
+ );
1555
+ }
1556
+
1557
+ const themeRelationship = documentPart.relationships.find(
1558
+ (r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" &&
1559
+ r.targetMode === "internal",
1560
+ );
1561
+ const themePartPath = themeRelationship
1562
+ ? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
1563
+ : undefined;
1564
+ const parsedTheme =
1565
+ themePartPath && sourcePackage.parts.has(themePartPath)
1566
+ ? parseThemeXml(
1567
+ decodeUtf8(sourcePackage.parts.get(themePartPath)?.bytes ?? new Uint8Array()),
1568
+ )
1569
+ : undefined;
1570
+ const resolvedTheme = parsedTheme ? resolveTheme(parsedTheme) : undefined;
1571
+ const settingsPartPath = resolveDocumentRelatedPartPath(
1572
+ sourcePackage,
1573
+ mainDocumentPath,
1574
+ documentPart.relationships,
1575
+ SETTINGS_RELATIONSHIP_TYPE,
1576
+ SETTINGS_PART_PATH,
1577
+ );
1578
+ const parsedSettings =
1579
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
1580
+ ? parseSettingsXml(
1581
+ decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
1582
+ )
1583
+ : undefined;
1584
+ const canonicalTheme =
1585
+ parsedTheme !== undefined
1586
+ ? materializeCanonicalTheme(
1587
+ parsedTheme,
1588
+ parsedSettings?.clrSchemeMapping ?? {},
1589
+ )
1590
+ : undefined;
1591
+ const settingsXmlForProtection =
1592
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
1593
+ ? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
1594
+ : "";
1595
+ const documentProtection = extractDocumentProtection(settingsXmlForProtection);
1596
+ const importedProtectionSnapshot = buildProtectionSnapshot(documentProtection, protectionRanges);
1597
+
1598
+ // ---- Parse styles.xml for canonical style catalog ----
1599
+ const stylesPartPath = resolveDocumentRelatedPartPath(
1600
+ sourcePackage,
1601
+ mainDocumentPath,
1602
+ documentPart.relationships,
1603
+ STYLES_RELATIONSHIP_TYPE,
1604
+ STYLES_PART_PATH,
1605
+ );
1606
+ const parsedStyles =
1607
+ stylesPartPath && sourcePackage.parts.has(stylesPartPath)
1608
+ ? parseStylesXml(
1609
+ decodeUtf8(sourcePackage.parts.get(stylesPartPath)?.bytes ?? new Uint8Array()),
1610
+ )
1611
+ : parseStylesXml("");
1612
+
1613
+ // ---- Parse fontTable.xml for canonical font catalog ----
1614
+ const fontTablePartPath = resolveDocumentRelatedPartPath(
1615
+ sourcePackage,
1616
+ mainDocumentPath,
1617
+ documentPart.relationships,
1618
+ FONT_TABLE_RELATIONSHIP_TYPE,
1619
+ FONT_TABLE_PART_PATH,
1620
+ );
1621
+ const parsedFontTable =
1622
+ fontTablePartPath && sourcePackage.parts.has(fontTablePartPath)
1623
+ ? parseFontTable(
1624
+ decodeUtf8(sourcePackage.parts.get(fontTablePartPath)?.bytes ?? new Uint8Array()),
1625
+ )
1626
+ : undefined;
1627
+
1628
+ const mergedMedia = mergeSecondaryStoryMediaCatalog(normalizedDocument.media, {
1629
+ headers: parsedHeaders,
1630
+ footers: parsedFooters,
1631
+ footnoteCollection,
1632
+ mediaParts,
1633
+ });
1634
+
1635
+ const subParts: SubPartsCatalog | undefined =
1636
+ parsedHeaders.length > 0 ||
1637
+ parsedFooters.length > 0 ||
1638
+ footnoteCollection !== undefined ||
1639
+ parsedTheme !== undefined ||
1640
+ normalizedDocument.finalSectionProperties !== undefined ||
1641
+ resolvedTheme !== undefined ||
1642
+ canonicalTheme !== undefined ||
1643
+ parsedSettings !== undefined
1644
+ ? {
1645
+ headers: parsedHeaders,
1646
+ footers: parsedFooters,
1647
+ ...(footnoteCollection !== undefined ? { footnoteCollection } : {}),
1648
+ ...(parsedTheme !== undefined ? { theme: parsedTheme } : {}),
1649
+ ...(normalizedDocument.finalSectionProperties !== undefined
1650
+ ? { finalSectionProperties: normalizedDocument.finalSectionProperties }
1651
+ : {}),
1652
+ ...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
1653
+ ...(canonicalTheme !== undefined ? { canonicalTheme } : {}),
1654
+ ...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
1655
+ }
1656
+ : undefined;
1657
+
1658
+ const timestamp = new Date().toISOString();
1659
+ const translatedWorkflowState = translateClmCommentsToWorkflow({
1660
+ comments: normalizedComments.threads,
1661
+ workflowOverlay: embeddedWorkflowOverlay,
1662
+ workflowMetadata: embeddedWorkflowMetadata,
1663
+ timestamp,
1664
+ });
1665
+ // P8 Step 7 — sync loader path never calls `storeEmbeddedDocument`
1666
+ // (the host adapter is async, and this entire loader is sync by
1667
+ // contract — it's the SSR / Node fallback used by
1668
+ // `editor-runtime-boundary`'s `createRuntime`). The REOPEN path
1669
+ // is still supported: if the package already carries a
1670
+ // `bw:embeddings` customXml namespace from a prior offload, we
1671
+ // hydrate entries so the export reconstitute path runs correctly.
1672
+ const rehydratedOffloadSync = hydrateOffloadEntriesFromPayload(
1673
+ embeddedWorkflowPayload?.editorState,
1674
+ );
1675
+ const embeddedDocumentManifestsSync = collectEmbeddedDocuments(
1676
+ sourcePackage,
1677
+ mainDocumentPath,
1678
+ ).manifests;
1679
+ const document = createImportedCanonicalDocument({
1680
+ documentId: options.documentId,
1681
+ timestamp,
1682
+ numbering: parsedNumbering,
1683
+ media: mergedMedia,
1684
+ content: normalizedDocument.content,
1685
+ subParts,
1686
+ parsedStyles,
1687
+ fontTable: parsedFontTable,
1688
+ preservation: buildImportPreservation(normalizedDocument.preservation, sourcePackage, [
1689
+ mainDocumentPath,
1690
+ numberingPartPath,
1691
+ commentsPartPath,
1692
+ commentsExtendedPartPath,
1693
+ commentsIdsPartPath,
1694
+ peoplePartPath,
1695
+ ...rehydratedOffloadSync.ownedPartPaths,
1696
+ ]),
1697
+ diagnostics: {
1698
+ warnings: [
1699
+ ...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
1700
+ ...normalizedDocument.diagnostics.warnings,
1701
+ ...mapRevisionDiagnosticsToWarnings(normalizedRevisions.diagnostics),
1702
+ ...mapStoryRevisionDiagnosticsToWarnings(importedStoryRevisionDiagnostics),
1703
+ ...mapCommentDiagnosticsToWarnings(normalizedComments.diagnostics),
1704
+ ],
1705
+ errors: [],
1706
+ },
1707
+ review: {
1708
+ comments: toRuntimeCommentRecords(translatedWorkflowState.comments),
1709
+ revisions: toRuntimeRevisionRecords([
1710
+ ...normalizedRevisions.revisions,
1711
+ ...importedStoryRevisions,
1712
+ ]),
1713
+ },
1714
+ });
1715
+ const compatibility = buildCompatibilityReport({
1716
+ document,
1717
+ generatedAt: timestamp,
1718
+ });
1719
+ const snapshot = createImportedSnapshot({
1720
+ documentId: options.documentId,
1721
+ editorBuild,
1722
+ timestamp,
1723
+ document,
1724
+ compatibility,
1725
+ protectionSnapshot: importedProtectionSnapshot,
1726
+ sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
1727
+ workflowOverlay: translatedWorkflowState.workflowOverlay,
1728
+ workflowMetadata: translatedWorkflowState.workflowMetadata,
1729
+ visibilityPolicies: embeddedWorkflowPayload?.visibilityPolicies,
1730
+ markupModePolicy: embeddedWorkflowPayload?.markupModePolicy ?? undefined,
1731
+ });
1732
+ const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
1733
+ if (snapshotIssues.length > 0) {
1734
+ const firstIssue = snapshotIssues[0];
1735
+ return createDiagnosticsSession(
1736
+ options,
1737
+ createValidationImportDiagnostics({
1738
+ message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
1739
+ source: "import",
1740
+ details: {
1741
+ issueCount: snapshotIssues.length,
1742
+ firstIssuePath: firstIssue?.path,
1743
+ },
1744
+ }),
1745
+ );
1746
+ }
1747
+ const initialSessionState = editorSessionStateFromPersistedSnapshot(snapshot);
1748
+ const importedState: ImportedDocxState = {
1749
+ sourceBytes: new Uint8Array(sourceBytes),
1750
+ sourcePackage,
1751
+ sourceDocumentXml,
1752
+ sourceDocumentPartPath: mainDocumentPath,
1753
+ sourceDocumentRelationships: documentPart.relationships,
1754
+ sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
1755
+ sourceNumberingPartPath: numberingPartPath,
1756
+ sourceNumberingRelationshipId: documentPart.relationships.find(
1757
+ (relationship) =>
1758
+ relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
1759
+ relationship.targetMode === "internal",
1760
+ )?.id,
1761
+ sourceSettingsPartPath: settingsPartPath,
1762
+ sourceSettingsXml:
1763
+ settingsXmlForProtection.length > 0 ? settingsXmlForProtection : undefined,
1764
+ sourceCommentsPartPath: commentsPartPath,
1765
+ sourceCommentsRelationshipId: documentPart.relationships.find(
1766
+ (relationship) =>
1767
+ relationship.type === COMMENTS_RELATIONSHIP_TYPE &&
1768
+ relationship.targetMode === "internal",
1769
+ )?.id,
1770
+ sourceCommentsRootTag: normalizedComments.sourceRootTag,
1771
+ sourceCommentsExtendedPartPath: commentsExtendedPartPath,
1772
+ sourceCommentsExtendedRelationshipId: documentPart.relationships.find(
1773
+ (relationship) =>
1774
+ relationship.type === COMMENTS_EXTENDED_RELATIONSHIP_TYPE &&
1775
+ relationship.targetMode === "internal",
1776
+ )?.id,
1777
+ sourceCommentsExtendedRootTag: normalizedComments.sourceExtendedRootTag,
1778
+ sourceCommentsIdsPartPath: commentsIdsPartPath,
1779
+ sourceCommentsIdsRelationshipId: documentPart.relationships.find(
1780
+ (relationship) =>
1781
+ relationship.type === COMMENTS_IDS_RELATIONSHIP_TYPE &&
1782
+ relationship.targetMode === "internal",
1783
+ )?.id,
1784
+ sourceCommentsIdsRootTag: normalizedComments.sourceIdsRootTag,
1785
+ sourcePeoplePartPath: peoplePartPath,
1786
+ sourcePeopleRelationshipId: documentPart.relationships.find(
1787
+ (relationship) =>
1788
+ relationship.type === PEOPLE_RELATIONSHIP_TYPE &&
1789
+ relationship.targetMode === "internal",
1790
+ )?.id,
1791
+ sourcePeopleRootTag: normalizedComments.sourcePeopleRootTag,
1792
+ sourcePeopleAuthors: normalizedComments.peopleAuthors,
1793
+ protectionSnapshot: buildProtectionSnapshot(documentProtection, protectionRanges),
1794
+ preservedCommentDefinitions: normalizedComments.preservedDefinitions,
1795
+ blockingCommentDiagnostics: normalizedComments.diagnostics.filter((diagnostic) =>
1796
+ BLOCKING_COMMENT_DIAGNOSTIC_CODES.has(diagnostic.code),
1797
+ ),
1798
+ initialCanonicalSignature: serializeCanonicalDocumentForExport(document),
1799
+ sourceSubPartPaths: {
1800
+ headers: sourceHeaderPaths,
1801
+ footers: sourceFooterPaths,
1802
+ footnotesPartPath,
1803
+ footnotesRelationshipId,
1804
+ endnotesPartPath,
1805
+ endnotesRelationshipId,
1806
+ themePartPath,
1807
+ themeRelationshipId: themeRelationship?.id,
1808
+ },
1809
+ embeddingOffloadEntries: rehydratedOffloadSync.entries,
1810
+ ...(options.hostAdapter !== undefined
1811
+ ? { hostAdapter: options.hostAdapter }
1812
+ : {}),
1813
+ };
1814
+
1815
+ stages.emit("skeleton-ready");
1816
+ return {
1817
+ initialSessionState,
1818
+ initialSnapshot: snapshot,
1819
+ readOnly: false,
1820
+ protectionSnapshot: importedProtectionSnapshot,
1821
+ exportDocx: async (nextSessionState, exportOptions) =>
1822
+ runStatefulExport(importedState, nextSessionState, exportOptions),
1823
+ embeddedDocumentManifests: embeddedDocumentManifestsSync,
1824
+ ...(embeddedWorkflowPayload?.editorState
1825
+ ? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
1826
+ : {}),
1827
+ };
1828
+ } catch (error) {
1829
+ return createDiagnosticsSession(
1830
+ options,
1831
+ createImportDiagnosticsFromError(error),
1832
+ );
1833
+ }
1834
+ }