@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.
- package/README.md +75 -931
- package/package.json +26 -27
- package/src/api/anchor-conversion.ts +43 -0
- package/src/api/editor-state-types.ts +2 -1
- package/src/api/public-types.ts +504 -101
- package/src/api/session-state.ts +4 -0
- package/src/api/v3/README.md +91 -0
- package/src/api/v3/_create.ts +146 -0
- package/src/api/v3/_layer-metadata.ts +362 -0
- package/src/api/v3/_mocks.ts +84 -0
- package/src/api/v3/_runtime-handle.ts +162 -0
- package/src/api/v3/_ux-response.ts +73 -0
- package/src/api/v3/ai/_metadata-audit.ts +225 -0
- package/src/api/v3/ai/attach.ts +235 -0
- package/src/api/v3/ai/bundle.ts +132 -0
- package/src/api/v3/ai/explain.ts +144 -0
- package/src/api/v3/ai/export.ts +54 -0
- package/src/api/v3/ai/inspect.ts +118 -0
- package/src/api/v3/ai/policy.ts +77 -0
- package/src/api/v3/ai/replacement.ts +341 -0
- package/src/api/v3/ai/resolve.ts +133 -0
- package/src/api/v3/index.ts +79 -0
- package/src/api/v3/runtime/chart.ts +310 -0
- package/src/api/v3/runtime/clipboard.ts +81 -0
- package/src/api/v3/runtime/collab.ts +331 -0
- package/src/api/v3/runtime/content.ts +236 -0
- package/src/api/v3/runtime/document.ts +282 -0
- package/src/api/v3/runtime/formatting.ts +186 -0
- package/src/api/v3/runtime/geometry.ts +349 -0
- package/src/api/v3/runtime/layout.ts +108 -0
- package/src/api/v3/runtime/review.ts +129 -0
- package/src/api/v3/runtime/search.ts +74 -0
- package/src/api/v3/runtime/table.ts +63 -0
- package/src/api/v3/runtime/workflow.ts +434 -0
- package/src/api/v3/ui/_context.ts +86 -0
- package/src/api/v3/ui/_create.ts +65 -0
- package/src/api/v3/ui/_types.ts +520 -0
- package/src/api/v3/ui/chrome-composition.ts +342 -0
- package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
- package/src/api/v3/ui/chrome.ts +476 -0
- package/src/api/v3/ui/debug.ts +124 -0
- package/src/api/v3/ui/index.ts +64 -0
- package/src/api/v3/ui/overlays-visibility.ts +170 -0
- package/src/api/v3/ui/overlays.ts +427 -0
- package/src/api/v3/ui/scope.ts +71 -0
- package/src/api/v3/ui/session.ts +100 -0
- package/src/api/v3/ui/surface.ts +170 -0
- package/src/api/v3/ui/viewport.ts +303 -0
- package/src/core/commands/index.ts +28 -6
- package/src/core/commands/list-commands.ts +3 -2
- package/src/core/commands/section-layout-commands.ts +9 -8
- package/src/core/schema/text-schema.ts +16 -0
- package/src/core/selection/mapping.ts +33 -72
- package/src/core/state/editor-state.ts +96 -189
- package/src/index.ts +23 -4
- package/src/io/chart-preview-resolver.ts +1 -1
- package/src/io/docx-session.ts +36 -4795
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +1 -1
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +45 -0
- package/src/io/export/serialize-run-formatting.ts +17 -2
- package/src/io/export/twip.ts +1 -1
- package/src/io/normalize/normalize-text.ts +27 -20
- package/src/io/ooxml/chart/parse-series.ts +1 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +1 -1
- package/src/io/ooxml/classify-embedding.ts +83 -33
- package/src/io/ooxml/parse-fill.ts +1 -1
- package/src/io/ooxml/parse-main-document.ts +71 -1
- package/src/io/ooxml/parse-object.ts +14 -10
- package/src/io/ooxml/parse-run-formatting.ts +47 -1
- package/src/io/ooxml/property-grab-bag.ts +2 -2
- package/src/io/ooxml/units.ts +11 -0
- package/src/io/ooxml/workflow-payload.ts +282 -7
- package/src/model/anchor.ts +85 -0
- package/src/model/canonical-document.ts +351 -15
- package/src/model/chart-types.ts +1 -1
- package/src/model/layout/index.ts +83 -0
- package/src/model/layout/page-graph-types.ts +181 -0
- package/src/model/layout/page-layout-snapshot.ts +105 -0
- package/src/model/layout/resolved-layout-types.ts +47 -0
- package/src/model/layout/runtime-page-graph-types.ts +102 -0
- package/src/model/paragraph-scope-ids.ts +72 -0
- package/src/model/review/comment-types.ts +112 -0
- package/src/model/review/index.ts +2 -0
- package/src/model/review/revision-types.ts +215 -0
- package/src/model/snapshot.ts +32 -0
- package/src/review/store/comment-store.ts +21 -47
- package/src/review/store/revision-types.ts +40 -198
- package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
- package/src/runtime/collab/runtime-collab-sync.ts +13 -3
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
- package/src/runtime/debug/event-ring-buffer.ts +64 -0
- package/src/runtime/debug/probability-sampler.ts +18 -0
- package/src/runtime/debug/runtime-debug-facet.ts +67 -0
- package/src/runtime/debug/stage-tokens.ts +31 -0
- package/src/runtime/debug/telemetry-bus.ts +271 -0
- package/src/runtime/debug/types.ts +275 -0
- package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
- package/src/runtime/document-layout.ts +8 -6
- package/src/runtime/document-runtime.ts +843 -1141
- package/src/runtime/document-search.ts +1 -1
- package/src/runtime/edit-ops/index.ts +1 -1
- package/src/runtime/external-send-runtime.ts +1 -1
- package/src/runtime/formatting/document-lookup.ts +235 -0
- package/src/runtime/formatting/field/registry.ts +41 -0
- package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
- package/src/runtime/formatting/font-resolution.ts +83 -0
- package/src/runtime/formatting/formatting-context.ts +903 -0
- package/src/runtime/formatting/formatting-types.ts +157 -0
- package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
- package/src/runtime/formatting/index.ts +125 -0
- package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
- package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
- package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
- package/src/runtime/formatting/projector.ts +75 -0
- package/src/runtime/formatting/resolve-effective.ts +407 -0
- package/src/runtime/formatting/revision-display.ts +105 -0
- package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
- package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
- package/src/runtime/formatting/telemetry-bridge.ts +106 -0
- package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
- package/src/runtime/geometry/caret-geometry.ts +164 -0
- package/src/runtime/geometry/geometry-facet.ts +364 -0
- package/src/runtime/geometry/geometry-types.ts +256 -0
- package/src/runtime/geometry/hit-test.ts +125 -0
- package/src/runtime/geometry/index.ts +71 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
- package/src/runtime/geometry/invalidation.ts +35 -0
- package/src/runtime/geometry/object-handles.ts +77 -0
- package/src/runtime/geometry/overlay-rects.ts +85 -0
- package/src/runtime/geometry/project-anchors.ts +100 -0
- package/src/runtime/geometry/project-fragments.ts +216 -0
- package/src/runtime/geometry/projector.ts +129 -0
- package/src/runtime/geometry/replacement-envelope.ts +130 -0
- package/src/runtime/geometry/viewport.ts +218 -0
- package/src/runtime/layout/compat-input-ledger.ts +211 -0
- package/src/runtime/layout/index.ts +6 -1
- package/src/runtime/layout/inert-layout-facet.ts +12 -7
- package/src/runtime/layout/layout-engine-instance.ts +189 -11
- package/src/runtime/layout/layout-engine-version.ts +450 -1
- package/src/runtime/layout/layout-facet-types.ts +60 -0
- package/src/runtime/layout/layout-measurement-provider.ts +13 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
- package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
- package/src/runtime/layout/page-graph.ts +62 -209
- package/src/runtime/layout/page-story-resolver.ts +7 -12
- package/src/runtime/layout/paginated-layout-engine.ts +186 -11
- package/src/runtime/layout/project-block-fragments.ts +11 -0
- package/src/runtime/layout/projector.ts +90 -0
- package/src/runtime/layout/public-facet.ts +187 -442
- package/src/runtime/layout/resolved-formatting-state.ts +158 -26
- package/src/runtime/layout/table-render-plan.ts +1 -1
- package/src/runtime/prerender/cache-envelope.ts +6 -1
- package/src/runtime/prerender/prerender-document.ts +18 -23
- package/src/runtime/render/decoration-resolver.ts +1 -1
- package/src/runtime/render/render-frame-types.ts +20 -0
- package/src/runtime/render/render-kernel.ts +94 -25
- package/src/runtime/scopes/_formatting-seam.ts +262 -0
- package/src/runtime/scopes/_scope-dependencies.ts +49 -0
- package/src/runtime/scopes/action-validation.ts +356 -0
- package/src/runtime/scopes/attach-explanation.ts +102 -0
- package/src/runtime/scopes/audit-bundle.ts +71 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
- package/src/runtime/scopes/compile-scope.ts +262 -0
- package/src/runtime/scopes/compiler-service.ts +431 -0
- package/src/runtime/scopes/create-issue.ts +107 -0
- package/src/runtime/scopes/enumerate-scopes.ts +543 -0
- package/src/runtime/scopes/evidence.ts +233 -0
- package/src/runtime/scopes/index.ts +150 -0
- package/src/runtime/scopes/position-map.ts +214 -0
- package/src/runtime/scopes/preservation-boundary.ts +91 -0
- package/src/runtime/scopes/projector.ts +49 -0
- package/src/runtime/scopes/replaceability.ts +87 -0
- package/src/runtime/scopes/replacement/apply.ts +228 -0
- package/src/runtime/scopes/replacement/compile.ts +59 -0
- package/src/runtime/scopes/replacement/propose.ts +42 -0
- package/src/runtime/scopes/resolve-reference.ts +347 -0
- package/src/runtime/scopes/review-bundle.ts +141 -0
- package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
- package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
- package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
- package/src/runtime/scopes/scope-kinds/field.ts +65 -0
- package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
- package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
- package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
- package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
- package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
- package/src/runtime/scopes/scope-kinds/table.ts +55 -0
- package/src/runtime/scopes/scope-range.ts +208 -0
- package/src/runtime/scopes/semantic-scope-types.ts +454 -0
- package/src/runtime/scopes/workflow-overlap.ts +92 -0
- package/src/runtime/selection/index.ts +1 -1
- package/src/runtime/structure-ops/fragment-insert.ts +1 -1
- package/src/runtime/structure-ops/index.ts +1 -1
- package/src/runtime/surface-projection.ts +232 -262
- package/src/runtime/units.ts +4 -2
- package/src/runtime/workflow/coordinator.ts +1348 -0
- package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
- package/src/runtime/workflow/index.ts +25 -0
- package/src/runtime/workflow/markup-mode-policy.ts +98 -0
- package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
- package/src/runtime/workflow/metadata-persistence.ts +306 -0
- package/src/runtime/workflow/metadata-writer.ts +123 -0
- package/src/runtime/workflow/overlay-store.ts +690 -0
- package/src/runtime/workflow/projector.ts +127 -0
- package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
- package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
- package/src/runtime/workflow/rail/types.ts +198 -0
- package/src/runtime/workflow/scope-rail-composer.ts +39 -0
- package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
- package/src/runtime/workflow/scope-writer.ts +188 -0
- package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
- package/src/runtime/workflow/visibility-policy.ts +129 -0
- package/src/session/_sync-legacy.ts +66 -0
- package/src/session/export/embedded-reconstitute.ts +104 -0
- package/src/session/export/export-diagnostics.ts +85 -0
- package/src/session/export/export-validation.ts +110 -0
- package/src/session/export/index.ts +34 -0
- package/src/session/export/preservation-reattach.ts +30 -0
- package/src/session/export/serialize-dispatch.ts +165 -0
- package/src/session/export/stateful-export-pipeline.ts +432 -0
- package/src/session/export/stateful-export.ts +684 -0
- package/src/session/import/canonical-assembly.ts +227 -0
- package/src/session/import/diagnostics-session.ts +54 -0
- package/src/session/import/embedded-discovery.ts +225 -0
- package/src/session/import/embedded-offload.ts +337 -0
- package/src/session/import/import-diagnostics.ts +69 -0
- package/src/session/import/loader-types.ts +313 -0
- package/src/session/import/loader.ts +1834 -0
- package/src/session/import/normalize.ts +195 -0
- package/src/session/import/package-parts.ts +217 -0
- package/src/session/import/package-read.ts +195 -0
- package/src/session/import/parse-orchestration.ts +105 -0
- package/src/session/import/part-constants.ts +70 -0
- package/src/session/import/part-discovery.ts +94 -0
- package/src/session/import/preservation-index.ts +46 -0
- package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
- package/src/session/import/review-import.ts +508 -0
- package/src/session/import/styles-consolidation.ts +281 -0
- package/src/session/import/workflow-scope-import.ts +256 -0
- package/src/session/index.ts +37 -0
- package/src/session/session-state.ts +69 -0
- package/src/session/session.ts +532 -0
- package/src/session/shared/protection.ts +228 -0
- package/src/session/shared/session-utils.ts +82 -0
- package/src/session/types.ts +499 -0
- package/src/shell/chart-snapshots.ts +96 -0
- package/src/shell/media-previews.ts +85 -0
- package/src/shell/overlay-anchor-bridge.ts +53 -0
- package/src/shell/paste-adapter.ts +23 -0
- package/src/shell/ref-commands.ts +1697 -0
- package/src/shell/ref-utilities.ts +48 -0
- package/src/shell/search.ts +51 -0
- package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
- package/src/shell/ui-subscriber-channels.ts +81 -0
- package/src/shell/use-collab-sync.ts +116 -0
- package/src/ui/WordReviewEditor.tsx +496 -2051
- package/src/ui/editor-shell-view.tsx +30 -1
- package/src/ui/editor-surface-controller.tsx +49 -1
- package/src/ui/headless/revision-decoration-model.ts +83 -0
- package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
- package/src/ui/headless/scoped-chrome-policy.ts +2 -2
- package/src/ui/headless/selection-tool-context.ts +1 -1
- package/src/ui/headless/selection-tool-resolver.ts +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +46 -1
- package/src/ui/ui-controller-factory.ts +221 -0
- package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
- package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
- package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
- package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
- package/src/ui-tailwind/chart/render/area.tsx +3 -3
- package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
- package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
- package/src/ui-tailwind/chart/render/combo.tsx +2 -2
- package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
- package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
- package/src/ui-tailwind/chart/render/line.tsx +3 -3
- package/src/ui-tailwind/chart/render/pie.tsx +6 -6
- package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
- package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
- package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
- package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
- package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
- package/src/ui-tailwind/debug/README.md +57 -0
- package/src/ui-tailwind/debug/index.ts +3 -0
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
- package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
- package/src/ui-tailwind/index.ts +0 -5
- package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
- package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
- package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
- package/src/ui-tailwind/review-workspace/types.ts +408 -0
- package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
- package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
- package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
- package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
- package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
- package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
- package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
- package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
- package/src/ui-tailwind/ui-api-context.tsx +43 -0
- package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
- package/src/validation/compatibility-engine.ts +6 -6
- package/src/runtime/styles-cascade.ts +0 -33
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
- /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
- /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
- /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
|
+
}
|