@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,195 @@
1
+ /**
2
+ * Sub-part opaque-fragment normalization helpers for the load path.
3
+ *
4
+ * Slice 2c of refactor/01 package-session. Extracted from
5
+ * src/io/docx-session.ts (legacy lines ~2590-2720 pre-extraction).
6
+ *
7
+ * "Sub-parts" = the ancillary OOXML parts attached to the main
8
+ * document (headers, footers, footnotes, endnotes). When their block
9
+ * content includes regions we don't fully model, the parser emits
10
+ * `opaque_block` nodes carrying the raw XML. These helpers assign
11
+ * stable fragment/warning/diagnostic IDs, push entries into the
12
+ * canonical opaque-fragment store, and append matching preservation
13
+ * warnings — so the load path surfaces every preserved region without
14
+ * losing the source XML.
15
+ *
16
+ * Contract P6 clean. Depends on `src/model/canonical-document.ts`
17
+ * (allowed) and `./part-discovery.ts` boundary types only.
18
+ */
19
+
20
+ import type {
21
+ BlockNode,
22
+ CanonicalDocument,
23
+ FootnoteCollection,
24
+ OpaqueFragmentRecord,
25
+ } from "../../model/canonical-document.ts";
26
+
27
+ /** Warning shape carried on `CanonicalDocument.diagnostics.warnings`. */
28
+ type CanonicalWarning = CanonicalDocument["diagnostics"]["warnings"][number];
29
+
30
+ /**
31
+ * Mutable bookkeeping so a single load-path run assigns collision-free
32
+ * fragment/warning/diagnostic IDs + stable range cursors across every
33
+ * sub-part it normalizes. Seeded from the already-imported state (the
34
+ * main document may have contributed opaque fragments before sub-part
35
+ * normalization runs).
36
+ */
37
+ export interface SubPartOpaqueImportState {
38
+ nextFragmentIndex: number;
39
+ nextWarningIndex: number;
40
+ nextDiagnosticIndex: number;
41
+ cursor: number;
42
+ }
43
+
44
+ /**
45
+ * Seed a fresh `SubPartOpaqueImportState` by scanning the already-
46
+ * imported opaque fragments + warnings for the highest existing
47
+ * `fragment:import-N` / `warning:import-N` / `diagnostic:import-N`
48
+ * indices, and the maximum `lastKnownRange.to` value. This guarantees
49
+ * the sub-part normalization pass never collides with IDs the main
50
+ * document already emitted.
51
+ */
52
+ export function createSubPartOpaqueImportState(
53
+ opaqueFragments: Record<string, OpaqueFragmentRecord>,
54
+ warnings: CanonicalWarning[],
55
+ ): SubPartOpaqueImportState {
56
+ const maxByPrefix = (values: Iterable<string>, prefix: string): number => {
57
+ let max = 0;
58
+ for (const value of values) {
59
+ const match = new RegExp(`^${prefix}(\\d+)$`).exec(value);
60
+ if (!match) continue;
61
+ const parsed = Number.parseInt(match[1] ?? "0", 10);
62
+ if (Number.isFinite(parsed) && parsed > max) max = parsed;
63
+ }
64
+ return max;
65
+ };
66
+
67
+ const maxCursor = Object.values(opaqueFragments).reduce(
68
+ (currentMax, fragment) => Math.max(currentMax, fragment.lastKnownRange?.to ?? 0),
69
+ 0,
70
+ );
71
+
72
+ return {
73
+ nextFragmentIndex:
74
+ maxByPrefix(Object.keys(opaqueFragments), "fragment:import-") + 1,
75
+ nextWarningIndex:
76
+ maxByPrefix(
77
+ [
78
+ ...Object.values(opaqueFragments).map((fragment) => fragment.warningId),
79
+ ...warnings.map((warning) => warning.warningId),
80
+ ],
81
+ "warning:import-",
82
+ ) + 1,
83
+ nextDiagnosticIndex:
84
+ maxByPrefix(warnings.map((warning) => warning.diagnosticId), "diagnostic:import-") + 1,
85
+ cursor: maxCursor,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Walk a sub-part's block list. For each `opaque_block` carrying raw
91
+ * XML, register a fresh fragment + warning + diagnostic and replace
92
+ * the block's ids with the registered ones. Non-opaque blocks pass
93
+ * through unchanged.
94
+ */
95
+ export function normalizeSubPartOpaqueBlocks(
96
+ blocks: BlockNode[],
97
+ opaqueFragments: Record<string, OpaqueFragmentRecord>,
98
+ warnings: CanonicalWarning[],
99
+ packagePartName: string,
100
+ state: SubPartOpaqueImportState,
101
+ ): BlockNode[] {
102
+ return blocks.map((block) => {
103
+ if (block.type !== "opaque_block" || typeof block.rawXml !== "string") {
104
+ return block;
105
+ }
106
+ return recordImportedOpaqueBlock(
107
+ block.rawXml,
108
+ opaqueFragments,
109
+ warnings,
110
+ packagePartName,
111
+ state,
112
+ );
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Apply `normalizeSubPartOpaqueBlocks` to every footnote (or endnote,
118
+ * when `kind === "endnote"`) definition's block list inside a shared
119
+ * footnote/endnote collection. Mutates the collection in place so
120
+ * the load path's subsequent passes see the normalized state.
121
+ */
122
+ export function normalizeFootnoteCollectionOpaqueBlocks(
123
+ collection: FootnoteCollection | undefined,
124
+ kind: "footnote" | "endnote",
125
+ opaqueFragments: Record<string, OpaqueFragmentRecord>,
126
+ warnings: CanonicalWarning[],
127
+ packagePartName: string,
128
+ state: SubPartOpaqueImportState,
129
+ ): void {
130
+ if (!collection) return;
131
+ const notes = kind === "footnote" ? collection.footnotes : collection.endnotes;
132
+ for (const definition of Object.values(notes)) {
133
+ definition.blocks = normalizeSubPartOpaqueBlocks(
134
+ definition.blocks,
135
+ opaqueFragments,
136
+ warnings,
137
+ packagePartName,
138
+ state,
139
+ );
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Register a single opaque block against the imported state:
145
+ * - allocate `fragment:import-N` / `warning:import-N` /
146
+ * `diagnostic:import-N` ids.
147
+ * - push a new `OpaqueFragmentRecord` into `opaqueFragments`.
148
+ * - push a matching preservation warning into `warnings`.
149
+ * - advance `state.cursor` by 1 (reserving a [from, to) range).
150
+ * Returns the rebuilt `opaque_block` node carrying the fresh ids.
151
+ */
152
+ export function recordImportedOpaqueBlock(
153
+ rawXml: string,
154
+ opaqueFragments: Record<string, OpaqueFragmentRecord>,
155
+ warnings: CanonicalWarning[],
156
+ packagePartName: string,
157
+ state: SubPartOpaqueImportState,
158
+ ): BlockNode {
159
+ const fragmentId = `fragment:import-${state.nextFragmentIndex}`;
160
+ state.nextFragmentIndex += 1;
161
+ const warningId = `warning:import-${state.nextWarningIndex}`;
162
+ state.nextWarningIndex += 1;
163
+ const diagnosticId = `diagnostic:import-${state.nextDiagnosticIndex}`;
164
+ state.nextDiagnosticIndex += 1;
165
+
166
+ const rangeStart = state.cursor;
167
+ const rangeEnd = state.cursor + 1;
168
+ state.cursor = rangeEnd;
169
+
170
+ opaqueFragments[fragmentId] = {
171
+ fragmentId,
172
+ payloadKind: "xml-subtree",
173
+ payloadReference: rawXml,
174
+ featureClass: "preserve-only",
175
+ lastKnownRange: {
176
+ from: rangeStart,
177
+ to: rangeEnd,
178
+ },
179
+ warningId,
180
+ packagePartName,
181
+ };
182
+ warnings.push({
183
+ diagnosticId,
184
+ warningId,
185
+ source: "import",
186
+ message: "Unsupported sub-part OOXML was preserved as an opaque placeholder.",
187
+ });
188
+
189
+ return {
190
+ type: "opaque_block",
191
+ fragmentId,
192
+ warningId,
193
+ rawXml,
194
+ };
195
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Package-part preservation + relationship utilities.
3
+ *
4
+ * Slice 2d of refactor/01 package-session. Extracted from
5
+ * src/io/docx-session.ts (legacy lines ~3718-3920 pre-extraction).
6
+ * Groups the pure helpers that deal with OPC package-part accounting
7
+ * (which parts to preserve verbatim on round-trip, which relationship
8
+ * ids target a given part, inline-media enumeration, etc.).
9
+ *
10
+ * Contract P6 clean. All dependencies live under allowed prefixes
11
+ * (src/io, src/preservation).
12
+ */
13
+
14
+ import type { OpcPackage } from "../../io/opc/package-reader.ts";
15
+ import {
16
+ normalizePartPath,
17
+ resolveRelationshipTarget,
18
+ type OpcRelationship,
19
+ type OpcSurfaceKind,
20
+ } from "../../io/ooxml/part-manifest.ts";
21
+ import type { PreservedPackagePart } from "../../model/canonical-document.ts";
22
+
23
+ /**
24
+ * Core parts that never carry preservation state: they're either
25
+ * regenerated on export (app / core props, numbering) or their
26
+ * ownership is tracked elsewhere.
27
+ */
28
+ export const CORE_NON_PRESERVED_PART_PATHS: ReadonlySet<string> = new Set([
29
+ "/docProps/app.xml",
30
+ "/docProps/core.xml",
31
+ "/word/numbering.xml",
32
+ ]);
33
+
34
+ /**
35
+ * Decide whether a given package part should round-trip as a
36
+ * preserved (byte-stable) package part. True for content-kind parts
37
+ * that aren't owned by the runtime, aren't inline media, and aren't
38
+ * regenerated on export.
39
+ */
40
+ export function shouldPreservePackagePart(
41
+ partPath: string,
42
+ surfaceKind: OpcSurfaceKind,
43
+ ownedPartPaths: ReadonlySet<string>,
44
+ ): boolean {
45
+ if (surfaceKind !== "content") return false;
46
+ if (
47
+ ownedPartPaths.has(partPath) ||
48
+ partPath.startsWith("/word/media/") ||
49
+ CORE_NON_PRESERVED_PART_PATHS.has(partPath)
50
+ ) {
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * Enumerate every internal relationship id that points at the given
58
+ * target part. Searches both the package-level relationships and every
59
+ * part's relationships. Returns a deterministically-sorted list.
60
+ */
61
+ export function findRelationshipIdsTargetingPart(
62
+ sourcePackage: OpcPackage,
63
+ targetPartPath: string,
64
+ ): string[] {
65
+ const ids = new Set<string>();
66
+
67
+ for (const relationship of sourcePackage.manifest.packageRelationships) {
68
+ if (
69
+ relationship.targetMode === "internal" &&
70
+ resolveRelationshipTarget(null, relationship) === targetPartPath
71
+ ) {
72
+ ids.add(relationship.id);
73
+ }
74
+ }
75
+
76
+ for (const part of sourcePackage.parts.values()) {
77
+ for (const relationship of part.relationships) {
78
+ if (
79
+ relationship.targetMode === "internal" &&
80
+ resolveRelationshipTarget(part.path, relationship) === targetPartPath
81
+ ) {
82
+ ids.add(relationship.id);
83
+ }
84
+ }
85
+ }
86
+
87
+ return [...ids].sort();
88
+ }
89
+
90
+ /**
91
+ * Build the `PreservedPackagePart` map keyed by part path. Skips the
92
+ * parts this load pass explicitly owns (they'll be re-serialized) and
93
+ * the core non-preserved set.
94
+ */
95
+ export function collectPreservedPackageParts(
96
+ sourcePackage: OpcPackage,
97
+ ownedPartPaths: ReadonlyArray<string | undefined>,
98
+ ): Record<string, PreservedPackagePart> {
99
+ const normalizedOwnedPaths = new Set(
100
+ ownedPartPaths
101
+ .filter((value): value is string => typeof value === "string")
102
+ .map((path) => normalizePartPath(path)),
103
+ );
104
+ return Object.fromEntries(
105
+ [...sourcePackage.parts.values()]
106
+ .filter((part) =>
107
+ shouldPreservePackagePart(part.path, part.surfaceKind, normalizedOwnedPaths),
108
+ )
109
+ .map((part) => [
110
+ part.path,
111
+ {
112
+ packagePartName: part.path,
113
+ contentType: part.contentType ?? "application/octet-stream",
114
+ relationshipIds: findRelationshipIdsTargetingPart(sourcePackage, part.path),
115
+ } satisfies PreservedPackagePart,
116
+ ]),
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Enumerate every `/word/media/` part as a simple path + content-type
122
+ * pair. Used by the secondary-story media catalog merge to thread
123
+ * inline media through the canonical document.
124
+ */
125
+ export function collectInlineMediaParts(
126
+ sourcePackage: OpcPackage,
127
+ ): ReadonlyMap<string, { path: string; contentType: string }> {
128
+ return new Map(
129
+ [...sourcePackage.parts.values()]
130
+ .filter(
131
+ (part) =>
132
+ part.path.startsWith("/word/media/") && typeof part.contentType === "string",
133
+ )
134
+ .map((part) => [
135
+ part.path,
136
+ {
137
+ path: part.path,
138
+ contentType: part.contentType ?? "application/octet-stream",
139
+ },
140
+ ]),
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Allocate a fresh `rIdCommentsN` relationship id that doesn't collide
146
+ * with any existing relationship in the supplied list. Used by the
147
+ * export path when re-attaching the comments sub-part.
148
+ */
149
+ export function createCommentsRelationshipId(
150
+ relationships: readonly OpcRelationship[],
151
+ ): string {
152
+ let nextIndex = 1;
153
+ while (
154
+ relationships.some(
155
+ (relationship) => relationship.id === `rIdComments${nextIndex}`,
156
+ )
157
+ ) {
158
+ nextIndex += 1;
159
+ }
160
+ return `rIdComments${nextIndex}`;
161
+ }
162
+
163
+ /**
164
+ * Normalize a part path to a document-relative target string. The
165
+ * normalized form strips the leading slash + `word/` prefix so the
166
+ * value is suitable as `<Relationship Target="..."/>` content.
167
+ */
168
+ export function toDocumentRelativeTarget(partPath: string): string {
169
+ const normalized = normalizePartPath(partPath);
170
+ return normalized.startsWith("/word/")
171
+ ? normalized.slice("/word/".length)
172
+ : normalized.slice(1);
173
+ }
174
+
175
+ /** Shallow copy of an OPC relationship. */
176
+ export function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
177
+ return { ...relationship };
178
+ }
179
+
180
+ /**
181
+ * Rebuild a main-document relationship list after toggling whether
182
+ * each document-related part (comments, header, footer, footnotes,
183
+ * endnotes, …) is present. Callers pass `include: true` to keep the
184
+ * relationship, `false` to drop it. Relationships of types listed in
185
+ * `relatedParts` are always filtered out first (so an incoming list
186
+ * with include=false effectively removes that relationship type);
187
+ * every other relationship passes through unchanged via
188
+ * `cloneRelationship`.
189
+ */
190
+ export function withDocumentRelatedParts(
191
+ relationships: readonly OpcRelationship[],
192
+ relatedParts: ReadonlyArray<{
193
+ relationshipType: string;
194
+ partPath: string;
195
+ existingRelationshipId?: string;
196
+ include: boolean;
197
+ }>,
198
+ ): OpcRelationship[] {
199
+ const filteredTypes = new Set(relatedParts.map((part) => part.relationshipType));
200
+ const nextRelationships = relationships
201
+ .filter((relationship) => !filteredTypes.has(relationship.type))
202
+ .map(cloneRelationship);
203
+
204
+ for (const part of relatedParts) {
205
+ if (!part.include) continue;
206
+ nextRelationships.push({
207
+ id:
208
+ part.existingRelationshipId ??
209
+ createCommentsRelationshipId(nextRelationships),
210
+ type: part.relationshipType,
211
+ target: toDocumentRelativeTarget(part.partPath),
212
+ targetMode: "internal",
213
+ });
214
+ }
215
+
216
+ return nextRelationships;
217
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * OPC-package-level validation helpers for the load path.
3
+ *
4
+ * Slice 2b of refactor/01 package-session. Extracted from
5
+ * src/io/docx-session.ts (legacy lines ~3920-4070 pre-extraction).
6
+ * Pure functions over an `OpcPackage`; contract P6 clean (no runtime /
7
+ * ui / core / review imports). Depends on:
8
+ * - src/io/opc/corrupt-package.ts (`createBrokenRelationshipIssue`)
9
+ * - src/io/ooxml/part-manifest.ts (`resolveRelationshipTarget`)
10
+ * - src/session/import/part-discovery.ts (`OFFICE_DOCUMENT_RELATIONSHIP_TYPE`)
11
+ *
12
+ * These helpers detect broken internal relationships in a loaded OPC
13
+ * package — some fatal (main-document relationship points at a missing
14
+ * part) and some not (ancillary part relationships outside the editor-
15
+ * owned graph, surfaced as preservation warnings rather than fatal
16
+ * diagnostics).
17
+ */
18
+
19
+ import type { CanonicalDocument } from "../../model/canonical-document.ts";
20
+ import type { OpcPackage } from "../../io/opc/package-reader.ts";
21
+ import { createBrokenRelationshipIssue } from "../../io/opc/corrupt-package.ts";
22
+ import {
23
+ resolveRelationshipTarget,
24
+ type OpcRelationship,
25
+ } from "../../io/ooxml/part-manifest.ts";
26
+ import { OFFICE_DOCUMENT_RELATIONSHIP_TYPE } from "./part-discovery.ts";
27
+
28
+ /** A broken-relationship issue as produced by `createBrokenRelationshipIssue`. */
29
+ export type BrokenRelationshipIssue = ReturnType<typeof createBrokenRelationshipIssue>;
30
+
31
+ /** Warning shape carried on `CanonicalDocument.diagnostics.warnings`. */
32
+ type CanonicalWarning = CanonicalDocument["diagnostics"]["warnings"][number];
33
+
34
+ /**
35
+ * Enumerate every internal relationship in the package whose target is
36
+ * absent AND whose absence is fatal. Returns a deterministically-sorted
37
+ * list. Used by the loader to decide whether to emit a fatal import
38
+ * diagnostic (main-document missing) vs a preservation warning
39
+ * (ancillary parts).
40
+ */
41
+ export function collectBrokenInternalRelationshipIssues(
42
+ sourcePackage: OpcPackage,
43
+ mainDocumentPath?: string,
44
+ ): BrokenRelationshipIssue[] {
45
+ const brokenTargets = new Map<string, BrokenRelationshipIssue>();
46
+
47
+ for (const relationship of sourcePackage.manifest.packageRelationships) {
48
+ if (relationship.targetMode !== "internal") continue;
49
+
50
+ const target = resolveRelationshipTarget(null, relationship);
51
+ if (
52
+ !sourcePackage.parts.has(target) &&
53
+ isFatalBrokenRelationship({
54
+ relationshipSourcePath: null,
55
+ relationship,
56
+ targetPartPath: target,
57
+ mainDocumentPath,
58
+ })
59
+ ) {
60
+ brokenTargets.set(
61
+ `package:${relationship.id}:${target}`,
62
+ createBrokenRelationshipIssue({
63
+ relationshipSourcePath: null,
64
+ relationshipId: relationship.id,
65
+ targetPartPath: target,
66
+ }),
67
+ );
68
+ }
69
+ }
70
+
71
+ for (const part of sourcePackage.parts.values()) {
72
+ for (const relationship of part.relationships) {
73
+ if (relationship.targetMode !== "internal") continue;
74
+
75
+ const target = resolveRelationshipTarget(part.path, relationship);
76
+ if (
77
+ !sourcePackage.parts.has(target) &&
78
+ isFatalBrokenRelationship({
79
+ relationshipSourcePath: part.path,
80
+ relationship,
81
+ targetPartPath: target,
82
+ mainDocumentPath,
83
+ })
84
+ ) {
85
+ brokenTargets.set(
86
+ `${part.path}:${relationship.id}:${target}`,
87
+ createBrokenRelationshipIssue({
88
+ relationshipSourcePath: part.path,
89
+ relationshipId: relationship.id,
90
+ targetPartPath: target,
91
+ }),
92
+ );
93
+ }
94
+ }
95
+ }
96
+
97
+ return [...brokenTargets.values()].sort((left, right) =>
98
+ `${left.relationshipSourcePath ?? ""}:${left.relationshipId}:${left.targetPartPath}`.localeCompare(
99
+ `${right.relationshipSourcePath ?? ""}:${right.relationshipId}:${right.targetPartPath}`,
100
+ ),
101
+ );
102
+ }
103
+
104
+ /** Build the one-line summary shown on a fatal broken-relationship diagnostic. */
105
+ export function summarizeBrokenRelationshipIssues(
106
+ issues: readonly BrokenRelationshipIssue[],
107
+ ): string {
108
+ return `DOCX package has unresolved internal relationships: ${issues
109
+ .map((issue) => issue.targetPartPath ?? issue.message)
110
+ .slice(0, 3)
111
+ .join(", ")}${issues.length > 3 ? ", ..." : ""}.`;
112
+ }
113
+
114
+ /**
115
+ * Build preservation-warning entries for the subset of broken
116
+ * relationships that are NOT fatal — ancillary parts whose targets
117
+ * sit outside the editor-owned graph. These surface as warnings on
118
+ * the canonical document rather than blocking the load.
119
+ */
120
+ export function createBrokenRelationshipWarnings(
121
+ sourcePackage: OpcPackage,
122
+ mainDocumentPath?: string,
123
+ ): CanonicalWarning[] {
124
+ const warnings = new Map<string, CanonicalWarning>();
125
+
126
+ for (const relationship of sourcePackage.manifest.packageRelationships) {
127
+ if (relationship.targetMode !== "internal") continue;
128
+
129
+ const target = resolveRelationshipTarget(null, relationship);
130
+ if (
131
+ sourcePackage.parts.has(target) ||
132
+ isFatalBrokenRelationship({
133
+ relationshipSourcePath: null,
134
+ relationship,
135
+ targetPartPath: target,
136
+ mainDocumentPath,
137
+ })
138
+ ) {
139
+ continue;
140
+ }
141
+
142
+ warnings.set(`package:${relationship.id}:${target}`, {
143
+ diagnosticId: `diagnostic:broken-relationship-package-${relationship.id}`,
144
+ warningId: `warning:broken-relationship:${relationship.id}`,
145
+ source: "preservation",
146
+ message: `DOCX package has unresolved internal relationships outside the editor-owned graph: ${target}.`,
147
+ });
148
+ }
149
+
150
+ for (const part of sourcePackage.parts.values()) {
151
+ for (const relationship of part.relationships) {
152
+ if (relationship.targetMode !== "internal") continue;
153
+
154
+ const target = resolveRelationshipTarget(part.path, relationship);
155
+ if (
156
+ sourcePackage.parts.has(target) ||
157
+ isFatalBrokenRelationship({
158
+ relationshipSourcePath: part.path,
159
+ relationship,
160
+ targetPartPath: target,
161
+ mainDocumentPath,
162
+ })
163
+ ) {
164
+ continue;
165
+ }
166
+
167
+ warnings.set(`${part.path}:${relationship.id}:${target}`, {
168
+ diagnosticId: `diagnostic:broken-relationship-${relationship.id}`,
169
+ warningId: `warning:broken-relationship:${relationship.id}`,
170
+ source: "preservation",
171
+ message: `DOCX package has unresolved internal relationships outside the editor-owned graph: ${target}.`,
172
+ });
173
+ }
174
+ }
175
+
176
+ return [...warnings.values()];
177
+ }
178
+
179
+ /**
180
+ * Classifier: is this broken relationship a fatal load error? Only the
181
+ * package-level officeDocument relationship when its target matches
182
+ * `mainDocumentPath`. Everything else is a preservation warning.
183
+ */
184
+ export function isFatalBrokenRelationship(input: {
185
+ relationshipSourcePath: string | null;
186
+ relationship: OpcRelationship;
187
+ targetPartPath: string;
188
+ mainDocumentPath?: string;
189
+ }): boolean {
190
+ return (
191
+ input.relationshipSourcePath === null &&
192
+ input.relationship.type === OFFICE_DOCUMENT_RELATIONSHIP_TYPE &&
193
+ input.targetPartPath === input.mainDocumentPath
194
+ );
195
+ }