@beyondwork/docx-react-component 1.0.67 → 1.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -932
- 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 -4797
- 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,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coord-06 §13c — resolve a *derived* paragraph-like scopeId to an
|
|
3
|
+
* `EditorAnchorProjection` (range anchor).
|
|
4
|
+
*
|
|
5
|
+
* Derived scopeIds are the paragraph / heading / list-item identifiers
|
|
6
|
+
* minted by `src/runtime/scopes/enumerate-scopes.ts` for blocks that
|
|
7
|
+
* aren't backed by an overlay marker. They don't live on the workflow
|
|
8
|
+
* overlay, so `overlayStore.scopes.find(...)` misses them — but the
|
|
9
|
+
* block itself exists in the canonical document and has a well-defined
|
|
10
|
+
* range. This resolver walks the canonical document, classifies each
|
|
11
|
+
* paragraph the same way the enumerator does, mints the candidate
|
|
12
|
+
* scopeId, and emits the range anchor on match.
|
|
13
|
+
*
|
|
14
|
+
* Shape rules:
|
|
15
|
+
* - `para:<paraId>` / `para:<blockIndex>`
|
|
16
|
+
* - `heading:<paraId>` / `heading:<level>:<blockIndex>`
|
|
17
|
+
* - `li:<paraId>` / `li:<instance>:<level>:<blockIndex>`
|
|
18
|
+
*
|
|
19
|
+
* Default `assoc` is `{ start: 1, end: -1 }` (greedy — same shipped
|
|
20
|
+
* shape as the pre-§13d scope-writer). Derived scopes don't carry a
|
|
21
|
+
* caller-authored `assoc` today; if a future slice exposes one on
|
|
22
|
+
* `attachExplanation` / `createIssue` inputs, this resolver accepts
|
|
23
|
+
* the override via the second argument.
|
|
24
|
+
*
|
|
25
|
+
* Non-paragraph kinds (table / table-row / table-cell / field /
|
|
26
|
+
* comment-thread / revision) are not in scope for §13c — CLM hot-path
|
|
27
|
+
* dominates paragraph / heading / list-item. Table-kind resolution is
|
|
28
|
+
* straightforward to add later (the scopeId grammar is `table:<index>`)
|
|
29
|
+
* but no consumer asks for it yet.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
BlockNode,
|
|
34
|
+
DocumentRootNode,
|
|
35
|
+
InlineNode,
|
|
36
|
+
ParagraphNode,
|
|
37
|
+
} from "../../model/canonical-document.ts";
|
|
38
|
+
import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
|
|
39
|
+
import type { EditorAnchorProjection } from "../../api/public-types.ts";
|
|
40
|
+
import {
|
|
41
|
+
buildParagraphScopeId,
|
|
42
|
+
detectParagraphKind,
|
|
43
|
+
} from "../../model/paragraph-scope-ids.ts";
|
|
44
|
+
|
|
45
|
+
function inlineLength(node: InlineNode): number {
|
|
46
|
+
switch (node.type) {
|
|
47
|
+
case "text":
|
|
48
|
+
return Array.from(node.text).length;
|
|
49
|
+
case "hyperlink":
|
|
50
|
+
case "field":
|
|
51
|
+
return node.children.reduce(
|
|
52
|
+
(total, child) => total + inlineLength(child as InlineNode),
|
|
53
|
+
0,
|
|
54
|
+
);
|
|
55
|
+
case "bookmark_start":
|
|
56
|
+
case "bookmark_end":
|
|
57
|
+
case "scope_marker_start":
|
|
58
|
+
case "scope_marker_end":
|
|
59
|
+
return 0;
|
|
60
|
+
default:
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function paragraphLength(paragraph: ParagraphNode): number {
|
|
66
|
+
return paragraph.children.reduce(
|
|
67
|
+
(total, child) => total + inlineLength(child as InlineNode),
|
|
68
|
+
0,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Walk the canonical document and resolve a derived paragraph-like
|
|
74
|
+
* scopeId to its range anchor. Returns `null` when no paragraph /
|
|
75
|
+
* heading / list-item mints this scopeId.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveDerivedScopeAnchorFromCanonical(
|
|
78
|
+
document: CanonicalDocumentEnvelope,
|
|
79
|
+
scopeId: string,
|
|
80
|
+
assoc: { readonly start: -1 | 1; readonly end: -1 | 1 } = { start: 1, end: -1 },
|
|
81
|
+
): EditorAnchorProjection | null {
|
|
82
|
+
// Fast-reject: derived paragraph-like scopeIds are always
|
|
83
|
+
// `para:…` / `heading:…` / `li:…`. Anything else (`table:…`,
|
|
84
|
+
// `field:…`, comment / revision ids, arbitrary host strings) can't
|
|
85
|
+
// match in this pass.
|
|
86
|
+
if (
|
|
87
|
+
!scopeId.startsWith("para:") &&
|
|
88
|
+
!scopeId.startsWith("heading:") &&
|
|
89
|
+
!scopeId.startsWith("li:")
|
|
90
|
+
) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const root = document.content as DocumentRootNode;
|
|
95
|
+
let cursor = 0;
|
|
96
|
+
const blocks = root.children;
|
|
97
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
98
|
+
const block: BlockNode = blocks[index]!;
|
|
99
|
+
let thisBlockLength: number;
|
|
100
|
+
let matchedScopeId: string | null = null;
|
|
101
|
+
|
|
102
|
+
if (block.type === "paragraph") {
|
|
103
|
+
thisBlockLength = paragraphLength(block);
|
|
104
|
+
const kind = detectParagraphKind(block);
|
|
105
|
+
matchedScopeId = buildParagraphScopeId(kind, index, block);
|
|
106
|
+
} else if (block.type === "table" || block.type === "sdt") {
|
|
107
|
+
thisBlockLength = 1;
|
|
108
|
+
} else {
|
|
109
|
+
thisBlockLength = 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (matchedScopeId !== null && matchedScopeId === scopeId) {
|
|
113
|
+
return {
|
|
114
|
+
kind: "range",
|
|
115
|
+
from: cursor,
|
|
116
|
+
to: cursor + thisBlockLength,
|
|
117
|
+
assoc,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cursor += thisBlockLength;
|
|
122
|
+
if (index < blocks.length - 1) cursor += 1;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow layer barrel.
|
|
3
|
+
*
|
|
4
|
+
* Layer 06 (workflow/review) — consolidated home for workflow scopes,
|
|
5
|
+
* scope markers, workflow markup, AI action policy, tamper gate, and the
|
|
6
|
+
* scope-rail composer. See `docs/architecture/06-workflow-review.md` for
|
|
7
|
+
* the canonical target and contracts W1–W9.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from "./query-scopes.ts";
|
|
11
|
+
export * from "./scope-resolver.ts";
|
|
12
|
+
export * from "./scope-tag-registry.ts";
|
|
13
|
+
export * from "./markup.ts";
|
|
14
|
+
export * from "./ai-action-policy.ts";
|
|
15
|
+
export * from "./tamper-gate.ts";
|
|
16
|
+
export * from "./rail/types.ts";
|
|
17
|
+
export * from "./rail/compose.ts";
|
|
18
|
+
export * from "./scope-writer.ts";
|
|
19
|
+
export * from "./metadata-writer.ts";
|
|
20
|
+
export * from "./projector.ts";
|
|
21
|
+
export * from "./overlay-store.ts";
|
|
22
|
+
export * from "./coordinator.ts";
|
|
23
|
+
export * from "./visibility-policy.ts";
|
|
24
|
+
export * from "./markup-mode-policy.ts";
|
|
25
|
+
export * from "./scope-rail-composer.ts";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X5 · Workflow markup-mode policy (class-A state) — runtime helpers.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the `visibility-policy.ts` shape for a single-value policy.
|
|
5
|
+
* Per `docs/plans/refactor/state-classes-cross-cutting.md` §X5:
|
|
6
|
+
*
|
|
7
|
+
* - L06 publishes `workflow.getMarkupModePolicy()` — class A.
|
|
8
|
+
* - L10 composes with class-C local preference in
|
|
9
|
+
* `ui.viewport.getEffectiveMarkupMode()`.
|
|
10
|
+
* - L03's `applyRevisionDisplay(run, revision, markupMode)` is
|
|
11
|
+
* unchanged — the composed value flows through.
|
|
12
|
+
*
|
|
13
|
+
* Invariants:
|
|
14
|
+
* - One policy record per document (not per-kind). Exposed via the
|
|
15
|
+
* `getMarkupModePolicy()` / `setMarkupModePolicy(policy)` pair.
|
|
16
|
+
* - `enforcement: "always"` hard-locks the mode; `"authored-default"`
|
|
17
|
+
* cedes to the local preference.
|
|
18
|
+
* - Mutations flow through the coordinator so they persist via
|
|
19
|
+
* `customXml/item1.xml` and broadcast via the Yjs document share.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
OverlayVisibilityAuthorship,
|
|
24
|
+
WorkflowMarkupMode,
|
|
25
|
+
WorkflowMarkupModeEnforcement,
|
|
26
|
+
WorkflowMarkupModePolicy,
|
|
27
|
+
} from "../../api/public-types.ts";
|
|
28
|
+
|
|
29
|
+
export type {
|
|
30
|
+
WorkflowMarkupMode,
|
|
31
|
+
WorkflowMarkupModeEnforcement,
|
|
32
|
+
WorkflowMarkupModePolicy,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const MARKUP_MODE_SET: ReadonlySet<WorkflowMarkupMode> = new Set([
|
|
36
|
+
"clean",
|
|
37
|
+
"simple",
|
|
38
|
+
"all",
|
|
39
|
+
]);
|
|
40
|
+
const ENFORCEMENT_SET: ReadonlySet<WorkflowMarkupModeEnforcement> = new Set([
|
|
41
|
+
"authored-default",
|
|
42
|
+
"always",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
export function isWorkflowMarkupMode(value: unknown): value is WorkflowMarkupMode {
|
|
46
|
+
return (
|
|
47
|
+
typeof value === "string" &&
|
|
48
|
+
MARKUP_MODE_SET.has(value as WorkflowMarkupMode)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isWorkflowMarkupModeEnforcement(
|
|
53
|
+
value: unknown,
|
|
54
|
+
): value is WorkflowMarkupModeEnforcement {
|
|
55
|
+
return (
|
|
56
|
+
typeof value === "string" &&
|
|
57
|
+
ENFORCEMENT_SET.has(value as WorkflowMarkupModeEnforcement)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isWorkflowMarkupModePolicy(
|
|
62
|
+
value: unknown,
|
|
63
|
+
): value is WorkflowMarkupModePolicy {
|
|
64
|
+
if (value === null || typeof value !== "object") return false;
|
|
65
|
+
const c = value as { mode?: unknown; enforcement?: unknown };
|
|
66
|
+
return isWorkflowMarkupMode(c.mode) && isWorkflowMarkupModeEnforcement(c.enforcement);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createWorkflowMarkupModePolicy(input: {
|
|
70
|
+
mode: WorkflowMarkupMode;
|
|
71
|
+
enforcement: WorkflowMarkupModeEnforcement;
|
|
72
|
+
authoredBy?: OverlayVisibilityAuthorship;
|
|
73
|
+
}): WorkflowMarkupModePolicy {
|
|
74
|
+
return {
|
|
75
|
+
mode: input.mode,
|
|
76
|
+
enforcement: input.enforcement,
|
|
77
|
+
...(input.authoredBy ? { authoredBy: { ...input.authoredBy } } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Structural equality — used by the store to short-circuit no-op sets
|
|
83
|
+
* (no spurious subscriber wakeups).
|
|
84
|
+
*/
|
|
85
|
+
export function workflowMarkupModePoliciesEqual(
|
|
86
|
+
left: WorkflowMarkupModePolicy | null | undefined,
|
|
87
|
+
right: WorkflowMarkupModePolicy | null | undefined,
|
|
88
|
+
): boolean {
|
|
89
|
+
if (left === right) return true;
|
|
90
|
+
if (!left || !right) return false;
|
|
91
|
+
if (left.mode !== right.mode) return false;
|
|
92
|
+
if (left.enforcement !== right.enforcement) return false;
|
|
93
|
+
const la = left.authoredBy;
|
|
94
|
+
const ra = right.authoredBy;
|
|
95
|
+
if (!la && !ra) return true;
|
|
96
|
+
if (!la || !ra) return false;
|
|
97
|
+
return la.actorId === ra.actorId && la.at === ra.at;
|
|
98
|
+
}
|
|
@@ -20,15 +20,15 @@ import type {
|
|
|
20
20
|
WorkflowProtectedRangeMarkup,
|
|
21
21
|
WorkflowRevisionMarkup,
|
|
22
22
|
WorkflowCommentMarkup,
|
|
23
|
-
} from "
|
|
24
|
-
import { MAIN_STORY_TARGET } from "
|
|
25
|
-
import { createPublicRangeAnchor } from "
|
|
23
|
+
} from "../../api/public-types";
|
|
24
|
+
import { MAIN_STORY_TARGET } from "../../core/selection/mapping.ts";
|
|
25
|
+
import { createPublicRangeAnchor } from "../../core/selection/anchor-conversion.ts";
|
|
26
26
|
import {
|
|
27
27
|
projectSurfaceText,
|
|
28
28
|
searchProjectedSurfaceText,
|
|
29
|
-
} from "
|
|
30
|
-
import { describeOpaqueFragment, isBlockedImportFeatureKey } from "
|
|
31
|
-
import type { CanonicalDocumentEnvelope } from "
|
|
29
|
+
} from "../../core/search/search-text.ts";
|
|
30
|
+
import { describeOpaqueFragment, isBlockedImportFeatureKey } from "../../preservation/store.ts";
|
|
31
|
+
import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Surface-derived markup (highlights + block-level opaque fragments).
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 06 — Workflow metadata persistence + conflict resolution.
|
|
3
|
+
*
|
|
4
|
+
* Owns the semantics of converting scope metadata between the three
|
|
5
|
+
* persistence modes (overlay default / scope-level / entry-level
|
|
6
|
+
* cascading to "internal" | "external"), the version-mismatch
|
|
7
|
+
* detection that emits `metadata_conflict_detected`, and the
|
|
8
|
+
* internal ↔ external conversion routines that talk to a host-
|
|
9
|
+
* supplied `ScopeMetadataResolver`.
|
|
10
|
+
*
|
|
11
|
+
* Slice 4 rehousing (2026-04-22): this logic lived directly in
|
|
12
|
+
* `src/ui/WordReviewEditor.tsx` (lines 280-477) before the
|
|
13
|
+
* adversarial closure pass. It had no UI dependencies — it
|
|
14
|
+
* operated on the runtime's workflow metadata snapshot and the
|
|
15
|
+
* resolver — so it was a strong layering violation. Moving it
|
|
16
|
+
* behind a Layer-06 seam means:
|
|
17
|
+
*
|
|
18
|
+
* - The semantics are now inspectable + testable without booting
|
|
19
|
+
* the React tree.
|
|
20
|
+
* - A pending `WorkflowHostHandle` type can consume this module
|
|
21
|
+
* directly; v3 callers get the same conflict-resolution policy
|
|
22
|
+
* as the UI layer without the UI having to hand it to them.
|
|
23
|
+
* - The "conflict emission + deduplication by conflict key" rule
|
|
24
|
+
* is versioned in one place.
|
|
25
|
+
*
|
|
26
|
+
* W9 purity: this module imports only from `src/api/public-types`
|
|
27
|
+
* and the scope-metadata-resolver type file. Both are type-only
|
|
28
|
+
* module references allowed by
|
|
29
|
+
* `scripts/ci-check-workflow-layer-purity.mjs`.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
MetadataPersistenceMode,
|
|
34
|
+
ScopeMetadataPersistence,
|
|
35
|
+
WorkflowMetadataEntry,
|
|
36
|
+
WorkflowMetadataSnapshot,
|
|
37
|
+
WorkflowOverlay,
|
|
38
|
+
WordReviewEditorEvent,
|
|
39
|
+
} from "../../api/public-types.ts";
|
|
40
|
+
import {
|
|
41
|
+
MetadataResolverMissingError,
|
|
42
|
+
} from "../../api/public-types.ts";
|
|
43
|
+
import type { ScopeMetadataResolver } from "../../api/scope-metadata-resolver-types.ts";
|
|
44
|
+
|
|
45
|
+
/* -------------------------------------------------------------------- *\
|
|
46
|
+
* Conflict keying — stable string identity per (scope, entry, field).
|
|
47
|
+
*
|
|
48
|
+
* The deduplication rule on `registerAndEmitConflict` depends on this
|
|
49
|
+
* key being stable, so the function is exported for host callers
|
|
50
|
+
* that want to find a previously-emitted conflict in their queue.
|
|
51
|
+
\* -------------------------------------------------------------------- */
|
|
52
|
+
|
|
53
|
+
export function conflictKey(input: {
|
|
54
|
+
scopeId?: string;
|
|
55
|
+
entryId?: string;
|
|
56
|
+
fieldKey?: string;
|
|
57
|
+
}): string {
|
|
58
|
+
return `${input.scopeId ?? ""}|${input.entryId ?? ""}|${input.fieldKey ?? ""}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* §W5 — cascade for effective persistence mode. Field-level wins over
|
|
63
|
+
* scope-level wins over overlay-default; unset falls back to "internal".
|
|
64
|
+
* Pure function of the three inputs.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveEffectivePersistence(input: {
|
|
67
|
+
overlay?: MetadataPersistenceMode;
|
|
68
|
+
scope?: ScopeMetadataPersistence;
|
|
69
|
+
field?: ScopeMetadataPersistence;
|
|
70
|
+
}): "internal" | "external" {
|
|
71
|
+
if (input.field === "internal" || input.field === "external") return input.field;
|
|
72
|
+
if (input.scope === "internal" || input.scope === "external") return input.scope;
|
|
73
|
+
return input.overlay ?? "internal";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A pending conflict record held by the host while it decides how to
|
|
78
|
+
* resolve. Keyed by `conflictKey(...)`.
|
|
79
|
+
*/
|
|
80
|
+
export interface PendingConflict {
|
|
81
|
+
scopeId?: string;
|
|
82
|
+
entryId?: string;
|
|
83
|
+
fieldKey?: string;
|
|
84
|
+
embedded: { value?: Record<string, unknown>; version?: number } | null;
|
|
85
|
+
external: { value?: Record<string, unknown>; version?: number } | null;
|
|
86
|
+
defaultPolicy: "prefer-latest";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Emit a single `metadata_conflict_detected` event and register the
|
|
91
|
+
* pending conflict, unless a conflict with the same key is already
|
|
92
|
+
* registered (deduplication). Pure side effect on the `pendingConflicts`
|
|
93
|
+
* map + the `onEvent` callback.
|
|
94
|
+
*/
|
|
95
|
+
export function registerAndEmitConflict(args: {
|
|
96
|
+
onEvent: ((event: WordReviewEditorEvent) => void) | undefined;
|
|
97
|
+
documentId: string;
|
|
98
|
+
conflict: PendingConflict;
|
|
99
|
+
pendingConflicts: Map<string, PendingConflict>;
|
|
100
|
+
}): void {
|
|
101
|
+
const key = conflictKey(args.conflict);
|
|
102
|
+
// Guard: do not emit duplicate events for the same key in a single pass.
|
|
103
|
+
if (args.pendingConflicts.has(key)) return;
|
|
104
|
+
args.pendingConflicts.set(key, args.conflict);
|
|
105
|
+
args.onEvent?.({
|
|
106
|
+
type: "metadata_conflict_detected",
|
|
107
|
+
documentId: args.documentId,
|
|
108
|
+
scopeId: args.conflict.scopeId,
|
|
109
|
+
entryId: args.conflict.entryId,
|
|
110
|
+
fieldKey: args.conflict.fieldKey,
|
|
111
|
+
embedded: args.conflict.embedded,
|
|
112
|
+
external: args.conflict.external,
|
|
113
|
+
defaultPolicy: args.conflict.defaultPolicy,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* -------------------------------------------------------------------- *\
|
|
118
|
+
* Narrow runtime interface — everything the conversion routines need
|
|
119
|
+
* from the runtime, specified as a structural type so callers can
|
|
120
|
+
* pass `DocumentRuntime` (or `WorkflowCoordinator`) verbatim without
|
|
121
|
+
* Layer-06 reaching up into `src/runtime/document-runtime.ts` or
|
|
122
|
+
* `src/api/v3/**`.
|
|
123
|
+
\* -------------------------------------------------------------------- */
|
|
124
|
+
|
|
125
|
+
export interface MetadataPersistenceRuntime {
|
|
126
|
+
getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot;
|
|
127
|
+
getWorkflowOverlay(): WorkflowOverlay | null;
|
|
128
|
+
setWorkflowMetadataEntries(entries: readonly WorkflowMetadataEntry[]): void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* -------------------------------------------------------------------- *\
|
|
132
|
+
* Conversion: scopes → internal (pull external payloads in-doc)
|
|
133
|
+
\* -------------------------------------------------------------------- */
|
|
134
|
+
|
|
135
|
+
export interface ConvertScopesToInternalInput {
|
|
136
|
+
runtime: MetadataPersistenceRuntime;
|
|
137
|
+
scopeIds: string[];
|
|
138
|
+
resolver: ScopeMetadataResolver | null;
|
|
139
|
+
/** When provided, version mismatches emit `metadata_conflict_detected`. */
|
|
140
|
+
documentId?: string;
|
|
141
|
+
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
142
|
+
pendingConflicts?: Map<string, PendingConflict>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Convert the given scopes' metadata entries to internal persistence
|
|
147
|
+
* (inline value in the customXml payload). For each entry currently
|
|
148
|
+
* in external mode, resolves the value via the host-supplied
|
|
149
|
+
* resolver, detects version mismatches (optional), and rewrites the
|
|
150
|
+
* entry with `metadataPersistence: "internal"` and the resolved
|
|
151
|
+
* value. Throws `MetadataResolverMissingError` when no resolver is
|
|
152
|
+
* supplied but conversion is attempted.
|
|
153
|
+
*/
|
|
154
|
+
export async function convertScopesToInternal(
|
|
155
|
+
args: ConvertScopesToInternalInput,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
if (!args.resolver) throw new MetadataResolverMissingError();
|
|
158
|
+
const snapshot = args.runtime.getWorkflowMetadataSnapshot();
|
|
159
|
+
const overlay = args.runtime.getWorkflowOverlay();
|
|
160
|
+
|
|
161
|
+
const nextEntries = await Promise.all(
|
|
162
|
+
snapshot.entries.map(async (entry) => {
|
|
163
|
+
if (!entry.scopeId || !args.scopeIds.includes(entry.scopeId)) return entry;
|
|
164
|
+
const scope = overlay?.scopes.find((s) => s.scopeId === entry.scopeId);
|
|
165
|
+
const effective = resolveEffectivePersistence({
|
|
166
|
+
overlay: overlay?.metadataPersistence,
|
|
167
|
+
scope: scope?.metadataPersistence,
|
|
168
|
+
field: entry.metadataPersistence,
|
|
169
|
+
});
|
|
170
|
+
if (effective !== "external" || !entry.storageRef) return entry;
|
|
171
|
+
const resolved = await args.resolver!.resolve(entry.storageRef);
|
|
172
|
+
if (!resolved) return entry;
|
|
173
|
+
|
|
174
|
+
// Conflict detection: compare embedded metadataVersion vs resolver version.
|
|
175
|
+
if (
|
|
176
|
+
args.pendingConflicts &&
|
|
177
|
+
args.documentId !== undefined &&
|
|
178
|
+
entry.metadataVersion !== undefined &&
|
|
179
|
+
resolved.version !== undefined &&
|
|
180
|
+
entry.metadataVersion !== resolved.version
|
|
181
|
+
) {
|
|
182
|
+
registerAndEmitConflict({
|
|
183
|
+
onEvent: args.onEvent,
|
|
184
|
+
documentId: args.documentId,
|
|
185
|
+
pendingConflicts: args.pendingConflicts,
|
|
186
|
+
conflict: {
|
|
187
|
+
scopeId: entry.scopeId,
|
|
188
|
+
entryId: entry.entryId,
|
|
189
|
+
// External entries have no inline value; embedded side carries version only.
|
|
190
|
+
embedded: { version: entry.metadataVersion },
|
|
191
|
+
external: { value: resolved.value, version: resolved.version },
|
|
192
|
+
defaultPolicy: "prefer-latest",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Default behavior: always apply the resolved value (host can override via resolveMetadataConflict).
|
|
198
|
+
return {
|
|
199
|
+
...entry,
|
|
200
|
+
value: resolved.value,
|
|
201
|
+
metadataPersistence: "internal" as const,
|
|
202
|
+
storageRef: undefined,
|
|
203
|
+
metadataVersion: resolved.version ?? entry.metadataVersion,
|
|
204
|
+
};
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
args.runtime.setWorkflowMetadataEntries(nextEntries);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* -------------------------------------------------------------------- *\
|
|
212
|
+
* Conversion: scopes → external (publish payloads out of the doc)
|
|
213
|
+
\* -------------------------------------------------------------------- */
|
|
214
|
+
|
|
215
|
+
export interface ConvertScopesToExternalInput {
|
|
216
|
+
runtime: MetadataPersistenceRuntime;
|
|
217
|
+
scopeIds: string[];
|
|
218
|
+
resolver: ScopeMetadataResolver | null;
|
|
219
|
+
/** When provided, version-conflict publish errors emit `metadata_conflict_detected`. */
|
|
220
|
+
documentId?: string;
|
|
221
|
+
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
222
|
+
pendingConflicts?: Map<string, PendingConflict>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Convert the given scopes' metadata entries to external persistence
|
|
227
|
+
* (value lives in the host store; customXml carries only a ref +
|
|
228
|
+
* version). For each entry currently in internal mode, publishes
|
|
229
|
+
* the value via the host-supplied resolver, duck-types version-
|
|
230
|
+
* conflict errors into `metadata_conflict_detected`, and rewrites
|
|
231
|
+
* the entry with `metadataPersistence: "external"` and the new
|
|
232
|
+
* storage ref. Throws `MetadataResolverMissingError` when no
|
|
233
|
+
* resolver is supplied.
|
|
234
|
+
*/
|
|
235
|
+
export async function convertScopesToExternal(
|
|
236
|
+
args: ConvertScopesToExternalInput,
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
if (!args.resolver) throw new MetadataResolverMissingError();
|
|
239
|
+
const snapshot = args.runtime.getWorkflowMetadataSnapshot();
|
|
240
|
+
const overlay = args.runtime.getWorkflowOverlay();
|
|
241
|
+
|
|
242
|
+
const nextEntries = await Promise.all(
|
|
243
|
+
snapshot.entries.map(async (entry) => {
|
|
244
|
+
if (!entry.scopeId || !args.scopeIds.includes(entry.scopeId)) return entry;
|
|
245
|
+
const scope = overlay?.scopes.find((s) => s.scopeId === entry.scopeId);
|
|
246
|
+
const effective = resolveEffectivePersistence({
|
|
247
|
+
overlay: overlay?.metadataPersistence,
|
|
248
|
+
scope: scope?.metadataPersistence,
|
|
249
|
+
field: entry.metadataPersistence,
|
|
250
|
+
});
|
|
251
|
+
if (effective === "external") return entry;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const { ref, version } = await args.resolver!.publish({
|
|
255
|
+
scopeId: entry.scopeId,
|
|
256
|
+
metadataId: entry.metadataId,
|
|
257
|
+
entryId: entry.entryId,
|
|
258
|
+
value: entry.value ?? {},
|
|
259
|
+
expectedVersion: entry.metadataVersion,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
...entry,
|
|
263
|
+
value: undefined,
|
|
264
|
+
metadataPersistence: "external" as const,
|
|
265
|
+
storageRef: ref,
|
|
266
|
+
metadataVersion: version,
|
|
267
|
+
};
|
|
268
|
+
} catch (err: unknown) {
|
|
269
|
+
// Duck-type version-conflict errors (HarnessVersionConflictError or compatible shapes).
|
|
270
|
+
if (
|
|
271
|
+
args.pendingConflicts &&
|
|
272
|
+
args.documentId !== undefined &&
|
|
273
|
+
err instanceof Error &&
|
|
274
|
+
(err.name === "HarnessVersionConflictError" ||
|
|
275
|
+
("ref" in err && "expected" in err && "actual" in err))
|
|
276
|
+
) {
|
|
277
|
+
const conflictErr = err as Error & {
|
|
278
|
+
ref?: string;
|
|
279
|
+
expected?: number;
|
|
280
|
+
actual?: number;
|
|
281
|
+
};
|
|
282
|
+
registerAndEmitConflict({
|
|
283
|
+
onEvent: args.onEvent,
|
|
284
|
+
documentId: args.documentId,
|
|
285
|
+
pendingConflicts: args.pendingConflicts,
|
|
286
|
+
conflict: {
|
|
287
|
+
scopeId: entry.scopeId,
|
|
288
|
+
entryId: entry.entryId,
|
|
289
|
+
// Embedded side: the entry's current inline value and metadataVersion.
|
|
290
|
+
embedded: { value: entry.value, version: entry.metadataVersion },
|
|
291
|
+
// External side: only the rowstore's actual version (no value available from error).
|
|
292
|
+
external: { version: conflictErr.actual },
|
|
293
|
+
defaultPolicy: "prefer-latest",
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
// Skip this entry — do not publish; leave it unchanged.
|
|
297
|
+
return entry;
|
|
298
|
+
}
|
|
299
|
+
// Non-conflict errors propagate normally.
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
args.runtime.setWorkflowMetadataEntries(nextEntries);
|
|
306
|
+
}
|