@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,1348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 06 — Workflow coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Owns the workflow/review orchestration that used to live inline in
|
|
5
|
+
* `document-runtime.ts`. The overlay store (`overlay-store.ts`) holds
|
|
6
|
+
* state + pure helpers; the coordinator plumbs that state through
|
|
7
|
+
* runtime-scoped dependencies (selection, revision token, render
|
|
8
|
+
* snapshot, page graph, dispatch, event emission) and exposes the
|
|
9
|
+
* full workflow public API plus the dispatch-branch handler.
|
|
10
|
+
*
|
|
11
|
+
* ## Contracts
|
|
12
|
+
*
|
|
13
|
+
* - W1 (marker-backed scopes) — scope marker pairs remain canonical
|
|
14
|
+
* truth for scope location. This coordinator does not own markers;
|
|
15
|
+
* it reads them through `collectScopeLocations` (via overlay-store
|
|
16
|
+
* + query-scopes) and resolves anchors through `resolveScope`.
|
|
17
|
+
*
|
|
18
|
+
* - W3 (single interaction-guard verdict) — `getInteractionGuardSnapshot`
|
|
19
|
+
* is the sole authority for effective mode + blocked reasons. The
|
|
20
|
+
* snapshot caches against (revisionToken, activeStory, selection,
|
|
21
|
+
* readOnly, documentMode, protectionSnapshot, overlay reference,
|
|
22
|
+
* sharedWorkflowState).
|
|
23
|
+
*
|
|
24
|
+
* - W4 (AI action policy orthogonal to guard) — this coordinator does
|
|
25
|
+
* not re-implement AI policy (see `ai-action-policy.ts`); guard
|
|
26
|
+
* evaluation is input to AI policy, not vice versa.
|
|
27
|
+
*
|
|
28
|
+
* - W7 (rail is page-anchored) — `getRailSegments` / `getAllRailSegments`
|
|
29
|
+
* / `getAllScopeCardModels` delegate to `collectScopeRailSegments`
|
|
30
|
+
* over the coordinator's page-graph accessor. Layout is a
|
|
31
|
+
* page-graph input source, not the owner.
|
|
32
|
+
*
|
|
33
|
+
* - W8 (tamper gate blocks on failure) — the coordinator reads
|
|
34
|
+
* `sharedWorkflowState.lockedMode` through the overlay store and
|
|
35
|
+
* short-circuits blocked-reasons evaluation when the round is
|
|
36
|
+
* locked (matches the pre-extraction behavior).
|
|
37
|
+
*
|
|
38
|
+
* - W9 (no upward imports) — this file must not import from
|
|
39
|
+
* `../document-runtime.ts`, `../render`, `../../ui*`, `../../api/v*`,
|
|
40
|
+
* or `../../io`. Enforced by
|
|
41
|
+
* `scripts/ci-check-workflow-layer-purity.mjs`.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import type {
|
|
45
|
+
AddScopeParams,
|
|
46
|
+
AddScopeResult,
|
|
47
|
+
DocumentMode,
|
|
48
|
+
EditorAnchorProjection,
|
|
49
|
+
EditorStoryTarget,
|
|
50
|
+
FieldSnapshot,
|
|
51
|
+
InteractionGuardSnapshot,
|
|
52
|
+
ProtectionSnapshot,
|
|
53
|
+
RuntimeRenderSnapshot,
|
|
54
|
+
ScopeCardModel,
|
|
55
|
+
ScopeChromeVisibilityState,
|
|
56
|
+
ScopeQueryFilter,
|
|
57
|
+
ScopeQueryResult,
|
|
58
|
+
ScopeVisibility,
|
|
59
|
+
WorkflowBlockedCommandReason,
|
|
60
|
+
WorkflowCandidateRange,
|
|
61
|
+
WorkflowMarkupSnapshot,
|
|
62
|
+
WorkflowMetadataDefinition,
|
|
63
|
+
WorkflowMetadataEntry,
|
|
64
|
+
WorkflowMetadataSnapshot,
|
|
65
|
+
WorkflowOverlay,
|
|
66
|
+
WorkflowScope,
|
|
67
|
+
WorkflowScopeMode,
|
|
68
|
+
WorkflowScopeSnapshot,
|
|
69
|
+
} from "../../api/public-types.ts";
|
|
70
|
+
import {
|
|
71
|
+
type CanonicalDocumentEnvelope,
|
|
72
|
+
type EditorState,
|
|
73
|
+
type EditorWarning as InternalEditorWarning,
|
|
74
|
+
} from "../../core/state/editor-state.ts";
|
|
75
|
+
import type { SharedWorkflowState } from "../collab/workflow-shared.ts";
|
|
76
|
+
import type { TelemetryBus } from "../debug/telemetry-bus.ts";
|
|
77
|
+
import {
|
|
78
|
+
MAIN_STORY_TARGET,
|
|
79
|
+
storyTargetsEqual,
|
|
80
|
+
} from "../../core/selection/mapping.ts";
|
|
81
|
+
import { storyTargetKey } from "../story-targeting.ts";
|
|
82
|
+
import { collectWorkflowMarkupSnapshot } from "./markup.ts";
|
|
83
|
+
import {
|
|
84
|
+
collectScopeRailSegments,
|
|
85
|
+
attachScopeCardModel,
|
|
86
|
+
type CollectScopeRailSegmentsInput,
|
|
87
|
+
} from "./rail/compose.ts";
|
|
88
|
+
import type { ScopeRailSegment } from "./rail/types.ts";
|
|
89
|
+
import {
|
|
90
|
+
queryScopes as runQueryScopes,
|
|
91
|
+
type ScopeQueryInputs,
|
|
92
|
+
} from "./query-scopes.ts";
|
|
93
|
+
import { resolveScope } from "./scope-resolver.ts";
|
|
94
|
+
import { insertScopeMarkers, removeScopeMarkers } from "../../core/commands/add-scope.ts";
|
|
95
|
+
import type { OverlayStore, MergeDetachedWarningsResult } from "./overlay-store.ts";
|
|
96
|
+
import {
|
|
97
|
+
type OverlayKind,
|
|
98
|
+
type OverlayVisibilityPolicy,
|
|
99
|
+
} from "./visibility-policy.ts";
|
|
100
|
+
import { type WorkflowMarkupModePolicy } from "./markup-mode-policy.ts";
|
|
101
|
+
|
|
102
|
+
/** Shape of origin metadata attached to commands. The runtime has a
|
|
103
|
+
* richer `CommandOrigin` type; we only pass it through, so we keep an
|
|
104
|
+
* open shape here to avoid coupling. */
|
|
105
|
+
type CoordinatorCommandOrigin = { readonly source: string; readonly at?: string };
|
|
106
|
+
|
|
107
|
+
/** Document-mutation commands the coordinator emits through `deps.dispatch`. */
|
|
108
|
+
type CoordinatorDispatchedCommand =
|
|
109
|
+
| {
|
|
110
|
+
type: "workflow.set-overlay";
|
|
111
|
+
overlay: WorkflowOverlay;
|
|
112
|
+
origin: CoordinatorCommandOrigin;
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
type: "workflow.clear-overlay";
|
|
116
|
+
origin: CoordinatorCommandOrigin;
|
|
117
|
+
}
|
|
118
|
+
| {
|
|
119
|
+
type: "workflow.set-metadata-definitions";
|
|
120
|
+
definitions: readonly WorkflowMetadataDefinition[];
|
|
121
|
+
origin: CoordinatorCommandOrigin;
|
|
122
|
+
}
|
|
123
|
+
| {
|
|
124
|
+
type: "workflow.clear-metadata-definitions";
|
|
125
|
+
origin: CoordinatorCommandOrigin;
|
|
126
|
+
}
|
|
127
|
+
| {
|
|
128
|
+
type: "workflow.set-metadata-entries";
|
|
129
|
+
entries: readonly WorkflowMetadataEntry[];
|
|
130
|
+
origin: CoordinatorCommandOrigin;
|
|
131
|
+
}
|
|
132
|
+
| {
|
|
133
|
+
type: "workflow.clear-metadata-entries";
|
|
134
|
+
origin: CoordinatorCommandOrigin;
|
|
135
|
+
}
|
|
136
|
+
| {
|
|
137
|
+
type: "document.replace";
|
|
138
|
+
document: CanonicalDocumentEnvelope;
|
|
139
|
+
origin: CoordinatorCommandOrigin;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** Editor-state channel — the coordinator records overlay + metadata
|
|
143
|
+
* mutations through this for collab broadcast. Matches the runtime's
|
|
144
|
+
* `editorStateChannel.recordMutation` signature. */
|
|
145
|
+
export interface EditorStateChannel {
|
|
146
|
+
recordMutation(
|
|
147
|
+
namespace: string,
|
|
148
|
+
payload: { namespace: string; schemaVersion: string; data: unknown },
|
|
149
|
+
): void;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Event-emission callback — the coordinator fires
|
|
153
|
+
* `workflow_overlay_changed`, `workflow_metadata_changed`, etc. via
|
|
154
|
+
* this. The runtime owns the event bus; the coordinator does not. */
|
|
155
|
+
export interface WorkflowEmittableEvent {
|
|
156
|
+
readonly type:
|
|
157
|
+
| "workflow_overlay_changed"
|
|
158
|
+
| "workflow_active_work_item_changed"
|
|
159
|
+
| "workflow_metadata_changed";
|
|
160
|
+
readonly documentId: string;
|
|
161
|
+
readonly snapshot?: WorkflowScopeSnapshot | WorkflowMetadataSnapshot | null;
|
|
162
|
+
readonly activeWorkItemId?: string | null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface PerfCounters {
|
|
166
|
+
increment(key: string, n?: number): void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Runtime-scoped dependencies the coordinator needs. These are
|
|
170
|
+
* read-only accessors (pure functions of the runtime's current state)
|
|
171
|
+
* plus callbacks to dispatch commands, emit events, and record
|
|
172
|
+
* mutations — everything required to preserve current behavior. */
|
|
173
|
+
export interface CoordinatorDeps {
|
|
174
|
+
readonly overlayStore: OverlayStore;
|
|
175
|
+
readonly telemetryBus: TelemetryBus;
|
|
176
|
+
readonly clock: () => string;
|
|
177
|
+
/** Current canonical document envelope. */
|
|
178
|
+
readonly getDocument: () => CanonicalDocumentEnvelope;
|
|
179
|
+
/** Current editor state (selection + revisionToken + readOnly + warnings). */
|
|
180
|
+
readonly getState: () => EditorState;
|
|
181
|
+
/** Active story target (main / header / footer / footnote / endnote). */
|
|
182
|
+
readonly getActiveStory: () => EditorStoryTarget;
|
|
183
|
+
/** View state accessor (documentMode used for guard composition). */
|
|
184
|
+
readonly getDocumentMode: () => DocumentMode;
|
|
185
|
+
/** Current protection snapshot (document protection + preserved ranges). */
|
|
186
|
+
readonly getProtectionSnapshot: () => ProtectionSnapshot;
|
|
187
|
+
/** Current render snapshot — used by markup + rail derivations. */
|
|
188
|
+
readonly getRenderSnapshot: () => RuntimeRenderSnapshot;
|
|
189
|
+
/** Field snapshot (used by markup). Runtime owns the cache; we just read. */
|
|
190
|
+
readonly getFieldSnapshot: () => FieldSnapshot;
|
|
191
|
+
/** Suggestions snapshot — used by scope-card composition. */
|
|
192
|
+
readonly getSuggestionsSnapshot: () => unknown;
|
|
193
|
+
/** Render-frame anchor index — used by scope-card composition. */
|
|
194
|
+
readonly getRenderFrameAnchorIndex: () => unknown;
|
|
195
|
+
/** Page graph — used by rail composition. */
|
|
196
|
+
readonly getPageGraph: () => CollectScopeRailSegmentsInput["pageGraph"];
|
|
197
|
+
/** Runtime's opaque-workflow-blocked-reason probe (preservation +
|
|
198
|
+
* blocked-import gating). Owned by the runtime because it reads
|
|
199
|
+
* preservation store and content-range overlap. */
|
|
200
|
+
readonly deriveOpaqueWorkflowBlockedReason: (
|
|
201
|
+
range: { from: number; to: number },
|
|
202
|
+
) => WorkflowBlockedCommandReason | null;
|
|
203
|
+
/** Protection-blocked selection probe. Owned by the runtime. */
|
|
204
|
+
readonly isBlockedByProtection: (
|
|
205
|
+
protection: ProtectionSnapshot,
|
|
206
|
+
selection: EditorState["selection"],
|
|
207
|
+
) => boolean;
|
|
208
|
+
/** Dispatch a runtime command. Used by addScope / removeScope /
|
|
209
|
+
* setWorkflowOverlay / etc. which route through the standard
|
|
210
|
+
* dispatch gate. */
|
|
211
|
+
readonly dispatch: (command: CoordinatorDispatchedCommand) => void;
|
|
212
|
+
/** Emit a workflow_* event to the runtime's event bus. */
|
|
213
|
+
readonly emitEvent: (event: WorkflowEmittableEvent) => void;
|
|
214
|
+
/** Runtime's editor-state channel (collab mutation log). */
|
|
215
|
+
readonly editorStateChannel: EditorStateChannel;
|
|
216
|
+
/** Set of command types that are unsupported in suggesting mode. */
|
|
217
|
+
readonly suggestingUnsupportedCommands: ReadonlySet<string>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The coordinator's public API surface. The runtime forwards its
|
|
221
|
+
* public methods to these 1:1; cache invalidation flows through
|
|
222
|
+
* `invalidateCachesForDocumentMutation` + `invalidateGuardAndScopeCaches`. */
|
|
223
|
+
export interface WorkflowCoordinator {
|
|
224
|
+
/* --- scope lifecycle (mutating, dispatch-backed) --- */
|
|
225
|
+
addScope(params: AddScopeParams): AddScopeResult;
|
|
226
|
+
removeScope(scopeId: string): void;
|
|
227
|
+
addInvisibleScope(params: AddScopeParams): AddScopeResult;
|
|
228
|
+
setScopeVisibility(scopeId: string, visibility: ScopeVisibility): void;
|
|
229
|
+
getScopeVisibility(scopeId: string): ScopeVisibility;
|
|
230
|
+
getScope(scopeId: string): WorkflowScope | null;
|
|
231
|
+
getMarkerBackedScopeIds(): ReadonlySet<string>;
|
|
232
|
+
/* --- scope chrome visibility (local view state) --- */
|
|
233
|
+
setScopeChromeVisibility(state: ScopeChromeVisibilityState): void;
|
|
234
|
+
getScopeChromeVisibility(): ScopeChromeVisibilityState;
|
|
235
|
+
/* --- overlay / metadata (mutating, dispatch-backed) --- */
|
|
236
|
+
setWorkflowOverlay(overlay: WorkflowOverlay): void;
|
|
237
|
+
clearWorkflowOverlay(): void;
|
|
238
|
+
getWorkflowOverlay(): WorkflowOverlay | null;
|
|
239
|
+
setWorkflowMetadataDefinitions(
|
|
240
|
+
definitions: readonly WorkflowMetadataDefinition[],
|
|
241
|
+
): void;
|
|
242
|
+
clearWorkflowMetadataDefinitions(): void;
|
|
243
|
+
setWorkflowMetadataEntries(entries: readonly WorkflowMetadataEntry[]): void;
|
|
244
|
+
clearWorkflowMetadataEntries(): void;
|
|
245
|
+
getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot;
|
|
246
|
+
/* --- W10 overlay-visibility policy (class-A state) --- */
|
|
247
|
+
getVisibilityPolicy(kind: OverlayKind): OverlayVisibilityPolicy | null;
|
|
248
|
+
getVisibilityPolicies(): readonly OverlayVisibilityPolicy[];
|
|
249
|
+
setVisibilityPolicy(policy: OverlayVisibilityPolicy): boolean;
|
|
250
|
+
clearVisibilityPolicy(kind: OverlayKind): boolean;
|
|
251
|
+
/** Bulk replace — used by reload + collab state sync. */
|
|
252
|
+
replaceVisibilityPolicies(
|
|
253
|
+
policies: readonly OverlayVisibilityPolicy[],
|
|
254
|
+
): boolean;
|
|
255
|
+
/** W10 — subscribe to any policy-set change. Consumed by L10 X3's
|
|
256
|
+
* `ui.overlays.subscribeVisibility` so authoring-tool mutations
|
|
257
|
+
* re-fire UI subscribers without a document reload. Returns an
|
|
258
|
+
* unsubscribe function. */
|
|
259
|
+
subscribeVisibilityPolicy(listener: () => void): () => void;
|
|
260
|
+
/* --- X5 · class-A markup-mode policy --- */
|
|
261
|
+
/** X5 — class-A markup-mode policy. `null` means the document carried
|
|
262
|
+
* no authored mode (consumer falls back to class-C local preference). */
|
|
263
|
+
getMarkupModePolicy(): WorkflowMarkupModePolicy | null;
|
|
264
|
+
/** X5 — author (or clear with `null`) the class-A markup-mode policy.
|
|
265
|
+
* Returns `true` when the record actually changed. Persists via
|
|
266
|
+
* customXml on next export + fires subscribers. */
|
|
267
|
+
setMarkupModePolicy(policy: WorkflowMarkupModePolicy | null): boolean;
|
|
268
|
+
/** X5 — subscribe to markup-mode policy changes. Consumed by L10's
|
|
269
|
+
* X5 consumer (`ui.viewport.getEffectiveMarkupMode` + its subscribe
|
|
270
|
+
* seam) so UI surfaces re-fire when policy changes mid-session. */
|
|
271
|
+
subscribeMarkupModePolicy(listener: () => void): () => void;
|
|
272
|
+
/* --- collab shared state --- */
|
|
273
|
+
setSharedWorkflowState(state: SharedWorkflowState | null): void;
|
|
274
|
+
/* --- snapshots (cached) --- */
|
|
275
|
+
getInteractionGuardSnapshot(): InteractionGuardSnapshot;
|
|
276
|
+
getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
|
|
277
|
+
getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
|
|
278
|
+
/* --- scope matching / blocked reasons --- */
|
|
279
|
+
evaluateBlockedReasons(
|
|
280
|
+
selection: EditorState["selection"],
|
|
281
|
+
commandType?: string,
|
|
282
|
+
): WorkflowBlockedCommandReason[];
|
|
283
|
+
getMatchingScope(selection: EditorState["selection"]): WorkflowScope | null;
|
|
284
|
+
getMatchingScopeStack(
|
|
285
|
+
selection: EditorState["selection"],
|
|
286
|
+
): readonly WorkflowScope[];
|
|
287
|
+
getEffectiveDocumentMode(selection: EditorState["selection"]): DocumentMode;
|
|
288
|
+
/* --- queries --- */
|
|
289
|
+
queryScopes(filter?: ScopeQueryFilter): ScopeQueryResult[];
|
|
290
|
+
/* --- rail / card (runtime.workflow facet backing) --- */
|
|
291
|
+
getRailSegments(pageIndex: number): readonly ScopeRailSegment[];
|
|
292
|
+
getAllRailSegments(): readonly ScopeRailSegment[];
|
|
293
|
+
getAllScopeCardModels(): readonly ScopeCardModel[];
|
|
294
|
+
/* --- dispatch branch handler (called from runtime's dispatch loop) --- */
|
|
295
|
+
applyOverlayCommand(
|
|
296
|
+
command: CoordinatorDispatchedCommand,
|
|
297
|
+
onRefreshRender: () => void,
|
|
298
|
+
): MergeDetachedWarningsResult | null;
|
|
299
|
+
/* --- warning synthesis (called after document.replace) --- */
|
|
300
|
+
syncDetachedScopeWarnings(
|
|
301
|
+
existing: readonly InternalEditorWarning[],
|
|
302
|
+
): MergeDetachedWarningsResult;
|
|
303
|
+
/* --- cache invalidation (called from runtime's cache-bust routines) --- */
|
|
304
|
+
invalidateCachesForDocumentMutation(): void;
|
|
305
|
+
invalidateGuardAndScopeCaches(): void;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* ---------------------------------------------------------------- *\
|
|
309
|
+
* Factory
|
|
310
|
+
\* ---------------------------------------------------------------- */
|
|
311
|
+
|
|
312
|
+
const MODE_RESTRICTIVENESS: Record<WorkflowScopeMode, number> = {
|
|
313
|
+
edit: 0,
|
|
314
|
+
suggest: 1,
|
|
315
|
+
comment: 2,
|
|
316
|
+
view: 3,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordinator {
|
|
320
|
+
const { overlayStore, clock } = deps;
|
|
321
|
+
|
|
322
|
+
/* -------- snapshot caches (runtime-scope) -------- */
|
|
323
|
+
|
|
324
|
+
let cachedInteractionGuardSnapshot:
|
|
325
|
+
| {
|
|
326
|
+
revisionToken: string;
|
|
327
|
+
activeStoryKey: string;
|
|
328
|
+
selection: EditorState["selection"];
|
|
329
|
+
readOnly: boolean;
|
|
330
|
+
documentMode: DocumentMode;
|
|
331
|
+
protectionSnapshot: ProtectionSnapshot;
|
|
332
|
+
overlay: WorkflowOverlay | null;
|
|
333
|
+
sharedWorkflowState: SharedWorkflowState | null;
|
|
334
|
+
snapshot: InteractionGuardSnapshot;
|
|
335
|
+
}
|
|
336
|
+
| undefined;
|
|
337
|
+
|
|
338
|
+
let cachedWorkflowScopeSnapshot:
|
|
339
|
+
| {
|
|
340
|
+
overlay: WorkflowOverlay;
|
|
341
|
+
interactionGuardSnapshot: InteractionGuardSnapshot;
|
|
342
|
+
snapshot: WorkflowScopeSnapshot;
|
|
343
|
+
}
|
|
344
|
+
| undefined;
|
|
345
|
+
|
|
346
|
+
let cachedWorkflowMarkupSnapshot:
|
|
347
|
+
| {
|
|
348
|
+
revisionToken: string;
|
|
349
|
+
activeStoryKey: string;
|
|
350
|
+
protectionSnapshot: ProtectionSnapshot;
|
|
351
|
+
preservation: CanonicalDocumentEnvelope["preservation"];
|
|
352
|
+
overlay: WorkflowOverlay | null;
|
|
353
|
+
metadataDefinitions: readonly WorkflowMetadataDefinition[];
|
|
354
|
+
metadataEntries: readonly WorkflowMetadataEntry[];
|
|
355
|
+
snapshot: WorkflowMarkupSnapshot;
|
|
356
|
+
}
|
|
357
|
+
| undefined;
|
|
358
|
+
|
|
359
|
+
/* -------- normalized-overlay accessor (thin shim over store) -------- */
|
|
360
|
+
|
|
361
|
+
function getNormalizedOverlay(): WorkflowOverlay | null {
|
|
362
|
+
return overlayStore.getNormalizedOverlay(deps.getDocument());
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* -------- scope-matching (selection-scoped) -------- */
|
|
366
|
+
|
|
367
|
+
function getEffectiveWorkflowScopes(
|
|
368
|
+
overlay: WorkflowOverlay,
|
|
369
|
+
): readonly WorkflowScope[] {
|
|
370
|
+
const activeStory = deps.getActiveStory();
|
|
371
|
+
const normalized = overlayStore.getNormalizedOverlay(deps.getDocument());
|
|
372
|
+
const source = normalized ?? overlay;
|
|
373
|
+
const activeWorkItemId = source.activeWorkItemId ?? null;
|
|
374
|
+
const activeWorkItemScopeIds =
|
|
375
|
+
activeWorkItemId === null
|
|
376
|
+
? null
|
|
377
|
+
: new Set(
|
|
378
|
+
source.workItems?.find((item) => item.workItemId === activeWorkItemId)
|
|
379
|
+
?.scopeIds ?? [],
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
return source.scopes.filter((scope) => {
|
|
383
|
+
const scopeStoryTarget = scope.storyTarget ?? MAIN_STORY_TARGET;
|
|
384
|
+
if (!storyTargetsEqual(scopeStoryTarget, activeStory)) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
if (activeWorkItemId === null) return true;
|
|
388
|
+
return (
|
|
389
|
+
scope.workItemId === activeWorkItemId ||
|
|
390
|
+
activeWorkItemScopeIds?.has(scope.scopeId) === true
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function expandSelectionRange(range: {
|
|
396
|
+
from: number;
|
|
397
|
+
to: number;
|
|
398
|
+
}): { from: number; to: number } {
|
|
399
|
+
return {
|
|
400
|
+
from: range.from,
|
|
401
|
+
to: range.to > range.from ? range.to : range.from + 1,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function buildMatchingScopeStack(
|
|
406
|
+
selection: EditorState["selection"],
|
|
407
|
+
): WorkflowScope[] {
|
|
408
|
+
const overlay = overlayStore.getOverlay();
|
|
409
|
+
if (!overlay) return [];
|
|
410
|
+
const selectionBounds = {
|
|
411
|
+
from: Math.min(selection.anchor, selection.head),
|
|
412
|
+
to: Math.max(selection.anchor, selection.head),
|
|
413
|
+
};
|
|
414
|
+
const activeScopes = getEffectiveWorkflowScopes(overlay);
|
|
415
|
+
const matching = activeScopes.filter((scope) => {
|
|
416
|
+
// §C8: invisible non-view scopes are transparent to the guard.
|
|
417
|
+
if (scope.visibility === "invisible" && scope.mode !== "view") return false;
|
|
418
|
+
if (scope.anchor.kind === "detached") return false;
|
|
419
|
+
const scopeFrom =
|
|
420
|
+
scope.anchor.kind === "range" ? scope.anchor.from : scope.anchor.at;
|
|
421
|
+
const scopeTo =
|
|
422
|
+
scope.anchor.kind === "range" ? scope.anchor.to : scope.anchor.at;
|
|
423
|
+
return selectionBounds.from >= scopeFrom && selectionBounds.to <= scopeTo;
|
|
424
|
+
});
|
|
425
|
+
// §C6 — outermost first: startPos ASC, endPos DESC, scopeId ASC tiebreak.
|
|
426
|
+
matching.sort((a, b) => {
|
|
427
|
+
const aFrom =
|
|
428
|
+
a.anchor.kind === "range"
|
|
429
|
+
? a.anchor.from
|
|
430
|
+
: (a.anchor as { at: number }).at;
|
|
431
|
+
const bFrom =
|
|
432
|
+
b.anchor.kind === "range"
|
|
433
|
+
? b.anchor.from
|
|
434
|
+
: (b.anchor as { at: number }).at;
|
|
435
|
+
if (aFrom !== bFrom) return aFrom - bFrom;
|
|
436
|
+
const aTo =
|
|
437
|
+
a.anchor.kind === "range" ? a.anchor.to : (a.anchor as { at: number }).at;
|
|
438
|
+
const bTo =
|
|
439
|
+
b.anchor.kind === "range" ? b.anchor.to : (b.anchor as { at: number }).at;
|
|
440
|
+
if (aTo !== bTo) return bTo - aTo;
|
|
441
|
+
return a.scopeId < b.scopeId ? -1 : a.scopeId > b.scopeId ? 1 : 0;
|
|
442
|
+
});
|
|
443
|
+
return matching;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function getMatchingWorkflowScope(
|
|
447
|
+
selection: EditorState["selection"],
|
|
448
|
+
): WorkflowScope | null {
|
|
449
|
+
const stack = buildMatchingScopeStack(selection);
|
|
450
|
+
if (stack.length === 0) return null;
|
|
451
|
+
// §C6 — most-restrictive-wins across overlapping scopes.
|
|
452
|
+
return stack.reduce((best, scope) =>
|
|
453
|
+
(MODE_RESTRICTIVENESS[scope.mode] ?? 0) >
|
|
454
|
+
(MODE_RESTRICTIVENESS[best.mode] ?? 0)
|
|
455
|
+
? scope
|
|
456
|
+
: best,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function getEffectiveDocumentMode(
|
|
461
|
+
selection: EditorState["selection"],
|
|
462
|
+
): DocumentMode {
|
|
463
|
+
const mode = deps.getDocumentMode();
|
|
464
|
+
if (mode === "viewing" || mode === "commenting") return mode;
|
|
465
|
+
const matchingScope = getMatchingWorkflowScope(selection);
|
|
466
|
+
if (matchingScope?.mode === "suggest") return "suggesting";
|
|
467
|
+
return mode;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function evaluateBlockedReasons(
|
|
471
|
+
selection: EditorState["selection"],
|
|
472
|
+
commandType?: string,
|
|
473
|
+
): WorkflowBlockedCommandReason[] {
|
|
474
|
+
const reasons: WorkflowBlockedCommandReason[] = [];
|
|
475
|
+
const sharedWorkflowState = overlayStore.getSharedWorkflowState();
|
|
476
|
+
if (
|
|
477
|
+
sharedWorkflowState?.lockedMode &&
|
|
478
|
+
sharedWorkflowState.lockedMode !== "editing"
|
|
479
|
+
) {
|
|
480
|
+
const lockedMode = sharedWorkflowState.lockedMode;
|
|
481
|
+
const code: WorkflowBlockedCommandReason["code"] =
|
|
482
|
+
lockedMode === "commenting"
|
|
483
|
+
? "workflow_comment_only"
|
|
484
|
+
: lockedMode === "viewing"
|
|
485
|
+
? "workflow_view_only"
|
|
486
|
+
: "workflow_round_locked";
|
|
487
|
+
reasons.push({
|
|
488
|
+
code,
|
|
489
|
+
message: `Round is locked to ${lockedMode} mode.`,
|
|
490
|
+
});
|
|
491
|
+
return reasons;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const selectionBounds = {
|
|
495
|
+
from: Math.min(selection.anchor, selection.head),
|
|
496
|
+
to: Math.max(selection.anchor, selection.head),
|
|
497
|
+
};
|
|
498
|
+
const selectionRange = expandSelectionRange(selectionBounds);
|
|
499
|
+
const opaqueReason = deps.deriveOpaqueWorkflowBlockedReason(selectionRange);
|
|
500
|
+
if (opaqueReason) reasons.push(opaqueReason);
|
|
501
|
+
|
|
502
|
+
const state = deps.getState();
|
|
503
|
+
if (state.readOnly) {
|
|
504
|
+
reasons.push({
|
|
505
|
+
code: "document_read_only",
|
|
506
|
+
message: "Document is in read-only mode.",
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const documentMode = deps.getDocumentMode();
|
|
511
|
+
if (documentMode === "viewing" || documentMode === "commenting") {
|
|
512
|
+
reasons.push({
|
|
513
|
+
code: "document_viewing_mode",
|
|
514
|
+
message: "Document is in viewing mode.",
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (deps.isBlockedByProtection(deps.getProtectionSnapshot(), selection)) {
|
|
519
|
+
reasons.push({
|
|
520
|
+
code: "protected_range",
|
|
521
|
+
message: "Selection falls within a protected range.",
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const effectiveDocumentMode = getEffectiveDocumentMode(selection);
|
|
526
|
+
if (effectiveDocumentMode === "suggesting" && commandType) {
|
|
527
|
+
if (deps.suggestingUnsupportedCommands.has(commandType)) {
|
|
528
|
+
reasons.push({
|
|
529
|
+
code: "suggesting_unsupported",
|
|
530
|
+
message: `"${commandType}" is not supported in suggesting mode.`,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const normalized = getNormalizedOverlay();
|
|
536
|
+
if (normalized) {
|
|
537
|
+
const matchingScope = getMatchingWorkflowScope(selection);
|
|
538
|
+
const activeScopes = getEffectiveWorkflowScopes(normalized);
|
|
539
|
+
const guardingScopes = activeScopes.filter(
|
|
540
|
+
(s) => !(s.visibility === "invisible" && s.mode !== "view"),
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
if (!matchingScope && guardingScopes.length > 0) {
|
|
544
|
+
reasons.push({
|
|
545
|
+
code: "outside_workflow_scope",
|
|
546
|
+
message: "Selection is outside any active workflow scope.",
|
|
547
|
+
});
|
|
548
|
+
} else if (matchingScope) {
|
|
549
|
+
if (matchingScope.mode === "comment") {
|
|
550
|
+
const isCommentCommand = commandType?.startsWith("comment.") ?? false;
|
|
551
|
+
if (!isCommentCommand) {
|
|
552
|
+
reasons.push({
|
|
553
|
+
code: "workflow_comment_only",
|
|
554
|
+
message: `Scope "${matchingScope.label ?? matchingScope.scopeId}" allows comments only.`,
|
|
555
|
+
scopeId: matchingScope.scopeId,
|
|
556
|
+
workItemId: matchingScope.workItemId,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
} else if (matchingScope.mode === "view") {
|
|
560
|
+
reasons.push({
|
|
561
|
+
code: "workflow_view_only",
|
|
562
|
+
message: `Scope "${matchingScope.label ?? matchingScope.scopeId}" is view-only.`,
|
|
563
|
+
scopeId: matchingScope.scopeId,
|
|
564
|
+
workItemId: matchingScope.workItemId,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return reasons;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* -------- snapshot derivations (cached) -------- */
|
|
574
|
+
|
|
575
|
+
function deriveWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null {
|
|
576
|
+
const normalized = getNormalizedOverlay();
|
|
577
|
+
if (!normalized) return null;
|
|
578
|
+
const guardSnapshot = getCachedInteractionGuardSnapshot();
|
|
579
|
+
const activeItem = normalized.activeWorkItemId
|
|
580
|
+
? normalized.workItems?.find(
|
|
581
|
+
(item) => item.workItemId === normalized.activeWorkItemId,
|
|
582
|
+
)
|
|
583
|
+
: undefined;
|
|
584
|
+
return {
|
|
585
|
+
overlayPresent: true,
|
|
586
|
+
activeWorkItemId: normalized.activeWorkItemId ?? null,
|
|
587
|
+
activeWorkItem: activeItem,
|
|
588
|
+
scopes: normalized.scopes,
|
|
589
|
+
candidates: normalized.candidates ?? [],
|
|
590
|
+
blockedReasons: guardSnapshot.blockedReasons,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function getCachedInteractionGuardSnapshot(): InteractionGuardSnapshot {
|
|
595
|
+
const state = deps.getState();
|
|
596
|
+
const activeStory = deps.getActiveStory();
|
|
597
|
+
const activeStoryKey = storyTargetKey(activeStory);
|
|
598
|
+
const protectionSnapshot = deps.getProtectionSnapshot();
|
|
599
|
+
const documentMode = deps.getDocumentMode();
|
|
600
|
+
const overlay = overlayStore.getOverlay();
|
|
601
|
+
const sharedWorkflowState = overlayStore.getSharedWorkflowState();
|
|
602
|
+
|
|
603
|
+
if (
|
|
604
|
+
cachedInteractionGuardSnapshot &&
|
|
605
|
+
cachedInteractionGuardSnapshot.revisionToken === state.revisionToken &&
|
|
606
|
+
cachedInteractionGuardSnapshot.activeStoryKey === activeStoryKey &&
|
|
607
|
+
cachedInteractionGuardSnapshot.selection === state.selection &&
|
|
608
|
+
cachedInteractionGuardSnapshot.readOnly === state.readOnly &&
|
|
609
|
+
cachedInteractionGuardSnapshot.documentMode === documentMode &&
|
|
610
|
+
cachedInteractionGuardSnapshot.protectionSnapshot === protectionSnapshot &&
|
|
611
|
+
cachedInteractionGuardSnapshot.overlay === overlay &&
|
|
612
|
+
cachedInteractionGuardSnapshot.sharedWorkflowState === sharedWorkflowState
|
|
613
|
+
) {
|
|
614
|
+
return cachedInteractionGuardSnapshot.snapshot;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const blockedReasons = evaluateBlockedReasons(state.selection);
|
|
618
|
+
const matchingScope = getMatchingWorkflowScope(state.selection);
|
|
619
|
+
const scopeStack = buildMatchingScopeStack(state.selection);
|
|
620
|
+
const primaryBlockedReason = blockedReasons[0];
|
|
621
|
+
const effectiveMode = primaryBlockedReason
|
|
622
|
+
? primaryBlockedReason.code === "workflow_comment_only"
|
|
623
|
+
? "comment"
|
|
624
|
+
: primaryBlockedReason.code === "workflow_view_only"
|
|
625
|
+
? "view"
|
|
626
|
+
: "blocked"
|
|
627
|
+
: getEffectiveDocumentMode(state.selection) === "suggesting"
|
|
628
|
+
? "suggest"
|
|
629
|
+
: matchingScope?.mode ?? "edit";
|
|
630
|
+
const matchedScopeStack: InteractionGuardSnapshot["matchedScopeStack"] =
|
|
631
|
+
scopeStack.length > 0
|
|
632
|
+
? scopeStack.map((s) => ({
|
|
633
|
+
scopeId: s.scopeId,
|
|
634
|
+
mode: s.mode,
|
|
635
|
+
visibility: s.visibility ?? "visible",
|
|
636
|
+
}))
|
|
637
|
+
: undefined;
|
|
638
|
+
const snapshot: InteractionGuardSnapshot = {
|
|
639
|
+
effectiveMode,
|
|
640
|
+
...(matchingScope?.scopeId ? { matchedScopeId: matchingScope.scopeId } : {}),
|
|
641
|
+
...(matchingScope?.mode ? { matchedScopeMode: matchingScope.mode } : {}),
|
|
642
|
+
...(matchedScopeStack ? { matchedScopeStack } : {}),
|
|
643
|
+
targetAccess:
|
|
644
|
+
effectiveMode === "edit"
|
|
645
|
+
? "direct-edit"
|
|
646
|
+
: effectiveMode === "suggest"
|
|
647
|
+
? "suggest"
|
|
648
|
+
: effectiveMode === "comment"
|
|
649
|
+
? "comment-only"
|
|
650
|
+
: effectiveMode === "view"
|
|
651
|
+
? "view-only"
|
|
652
|
+
: "blocked",
|
|
653
|
+
commandCapabilities: [
|
|
654
|
+
{
|
|
655
|
+
family: "text",
|
|
656
|
+
supported:
|
|
657
|
+
evaluateBlockedReasons(state.selection, "text.insert").length === 0,
|
|
658
|
+
blockedReasons: evaluateBlockedReasons(state.selection, "text.insert"),
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
family: "formatting",
|
|
662
|
+
supported:
|
|
663
|
+
evaluateBlockedReasons(state.selection, "toggleBold").length === 0,
|
|
664
|
+
blockedReasons: evaluateBlockedReasons(state.selection, "toggleBold"),
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
family: "structure",
|
|
668
|
+
supported:
|
|
669
|
+
evaluateBlockedReasons(state.selection, "insertTable").length === 0,
|
|
670
|
+
blockedReasons: evaluateBlockedReasons(state.selection, "insertTable"),
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
...(primaryBlockedReason ? { disabledReason: primaryBlockedReason.message } : {}),
|
|
674
|
+
blockedReasons,
|
|
675
|
+
};
|
|
676
|
+
cachedInteractionGuardSnapshot = {
|
|
677
|
+
revisionToken: state.revisionToken,
|
|
678
|
+
activeStoryKey,
|
|
679
|
+
selection: state.selection,
|
|
680
|
+
readOnly: state.readOnly,
|
|
681
|
+
documentMode,
|
|
682
|
+
protectionSnapshot,
|
|
683
|
+
overlay,
|
|
684
|
+
sharedWorkflowState,
|
|
685
|
+
snapshot,
|
|
686
|
+
};
|
|
687
|
+
return snapshot;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function getCachedWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null {
|
|
691
|
+
const overlay = overlayStore.getOverlay();
|
|
692
|
+
if (!overlay) return null;
|
|
693
|
+
const interactionGuardSnapshot = getCachedInteractionGuardSnapshot();
|
|
694
|
+
if (
|
|
695
|
+
cachedWorkflowScopeSnapshot &&
|
|
696
|
+
cachedWorkflowScopeSnapshot.overlay === overlay &&
|
|
697
|
+
cachedWorkflowScopeSnapshot.interactionGuardSnapshot === interactionGuardSnapshot
|
|
698
|
+
) {
|
|
699
|
+
return cachedWorkflowScopeSnapshot.snapshot;
|
|
700
|
+
}
|
|
701
|
+
const snapshot = deriveWorkflowScopeSnapshot()!;
|
|
702
|
+
cachedWorkflowScopeSnapshot = {
|
|
703
|
+
overlay,
|
|
704
|
+
interactionGuardSnapshot,
|
|
705
|
+
snapshot,
|
|
706
|
+
};
|
|
707
|
+
return snapshot;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function getCachedWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot {
|
|
711
|
+
const state = deps.getState();
|
|
712
|
+
const activeStory = deps.getActiveStory();
|
|
713
|
+
const activeStoryKey = storyTargetKey(activeStory);
|
|
714
|
+
const protectionSnapshot = deps.getProtectionSnapshot();
|
|
715
|
+
const preservation = deps.getDocument().preservation;
|
|
716
|
+
const overlay = overlayStore.getOverlay();
|
|
717
|
+
const metadataDefinitions = overlayStore.getMetadataDefinitions();
|
|
718
|
+
const metadataEntries = overlayStore.getMetadataEntries();
|
|
719
|
+
|
|
720
|
+
if (
|
|
721
|
+
cachedWorkflowMarkupSnapshot &&
|
|
722
|
+
cachedWorkflowMarkupSnapshot.revisionToken === state.revisionToken &&
|
|
723
|
+
cachedWorkflowMarkupSnapshot.activeStoryKey === activeStoryKey &&
|
|
724
|
+
cachedWorkflowMarkupSnapshot.protectionSnapshot === protectionSnapshot &&
|
|
725
|
+
cachedWorkflowMarkupSnapshot.preservation === preservation &&
|
|
726
|
+
cachedWorkflowMarkupSnapshot.overlay === overlay &&
|
|
727
|
+
cachedWorkflowMarkupSnapshot.metadataDefinitions === metadataDefinitions &&
|
|
728
|
+
cachedWorkflowMarkupSnapshot.metadataEntries === metadataEntries
|
|
729
|
+
) {
|
|
730
|
+
return cachedWorkflowMarkupSnapshot.snapshot;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const snapshot = collectWorkflowMarkupSnapshot({
|
|
734
|
+
renderSnapshot: deps.getRenderSnapshot(),
|
|
735
|
+
fieldSnapshot: deps.getFieldSnapshot(),
|
|
736
|
+
protectionSnapshot,
|
|
737
|
+
preservation,
|
|
738
|
+
workflowMetadataSnapshot: overlayStore.getMetadataSnapshot(),
|
|
739
|
+
});
|
|
740
|
+
cachedWorkflowMarkupSnapshot = {
|
|
741
|
+
revisionToken: state.revisionToken,
|
|
742
|
+
activeStoryKey,
|
|
743
|
+
protectionSnapshot,
|
|
744
|
+
preservation,
|
|
745
|
+
overlay,
|
|
746
|
+
metadataDefinitions,
|
|
747
|
+
metadataEntries,
|
|
748
|
+
snapshot,
|
|
749
|
+
};
|
|
750
|
+
return snapshot;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* -------- rail composition -------- */
|
|
754
|
+
|
|
755
|
+
function buildRailInput(): {
|
|
756
|
+
scopes: readonly WorkflowScope[];
|
|
757
|
+
candidates?: readonly WorkflowCandidateRange[];
|
|
758
|
+
activeWorkItemScopeIds: readonly string[];
|
|
759
|
+
activeStory: EditorStoryTarget;
|
|
760
|
+
} | null {
|
|
761
|
+
const normalized = getNormalizedOverlay();
|
|
762
|
+
if (!normalized) return null;
|
|
763
|
+
const activeWorkItemId = normalized.activeWorkItemId ?? null;
|
|
764
|
+
const activeWorkItem =
|
|
765
|
+
activeWorkItemId !== null
|
|
766
|
+
? normalized.workItems?.find(
|
|
767
|
+
(item) => item.workItemId === activeWorkItemId,
|
|
768
|
+
)
|
|
769
|
+
: undefined;
|
|
770
|
+
return {
|
|
771
|
+
scopes: normalized.scopes,
|
|
772
|
+
candidates: normalized.candidates,
|
|
773
|
+
activeWorkItemScopeIds: activeWorkItem?.scopeIds ?? [],
|
|
774
|
+
activeStory: deps.getActiveStory(),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/* -------- scope lifecycle (mutating) -------- */
|
|
779
|
+
|
|
780
|
+
function addScope(params: AddScopeParams): AddScopeResult {
|
|
781
|
+
const state = deps.getState();
|
|
782
|
+
const scopeId =
|
|
783
|
+
params.scopeId ??
|
|
784
|
+
`scope-${clock().replace(/[^0-9]/gu, "")}-${Math.floor(Math.random() * 1e6)}`;
|
|
785
|
+
const anchor =
|
|
786
|
+
params.anchor.kind === "range"
|
|
787
|
+
? { from: params.anchor.from, to: params.anchor.to }
|
|
788
|
+
: null;
|
|
789
|
+
|
|
790
|
+
if (!anchor) {
|
|
791
|
+
return { scopeId, anchor: params.anchor };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const { document: nextDocument } = insertScopeMarkers(
|
|
795
|
+
deps.getDocument(),
|
|
796
|
+
{ scopeId, from: anchor.from, to: anchor.to },
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
if (nextDocument !== deps.getDocument()) {
|
|
800
|
+
deps.dispatch({
|
|
801
|
+
type: "document.replace",
|
|
802
|
+
document: nextDocument,
|
|
803
|
+
origin: { source: "api", at: clock() },
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Coord-06 §13d — preserve the caller's assoc on the public anchor.
|
|
808
|
+
// resolveScope re-derives the range from the inserted markers but emits
|
|
809
|
+
// a hardcoded { start: -1, end: 1 }; without this override the caller's
|
|
810
|
+
// per-scope edge-stickiness choice is silently dropped before the scope
|
|
811
|
+
// ever lands on the overlay.
|
|
812
|
+
const callerAssoc: { readonly start: -1 | 1; readonly end: -1 | 1 } =
|
|
813
|
+
params.anchor.kind === "range"
|
|
814
|
+
? params.anchor.assoc
|
|
815
|
+
: { start: -1, end: 1 };
|
|
816
|
+
const resolved = resolveScope(nextDocument, scopeId);
|
|
817
|
+
const publicAnchor: EditorAnchorProjection =
|
|
818
|
+
resolved && resolved.kind === "range"
|
|
819
|
+
? { ...resolved, assoc: callerAssoc }
|
|
820
|
+
: {
|
|
821
|
+
kind: "range",
|
|
822
|
+
from: anchor.from,
|
|
823
|
+
to: anchor.to,
|
|
824
|
+
assoc: callerAssoc,
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
const currentOverlay: WorkflowOverlay =
|
|
828
|
+
overlayStore.getOverlay() ?? {
|
|
829
|
+
overlayVersion: "workflow-overlay/1",
|
|
830
|
+
scopes: [],
|
|
831
|
+
};
|
|
832
|
+
const existingScopes = currentOverlay.scopes.filter(
|
|
833
|
+
(existing) => existing.scopeId !== scopeId,
|
|
834
|
+
);
|
|
835
|
+
const scope: WorkflowScope = {
|
|
836
|
+
scopeId,
|
|
837
|
+
mode: params.mode ?? "comment",
|
|
838
|
+
anchor: publicAnchor,
|
|
839
|
+
...(params.storyTarget ? { storyTarget: params.storyTarget } : {}),
|
|
840
|
+
...(params.label ? { label: params.label } : {}),
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
deps.dispatch({
|
|
844
|
+
type: "workflow.set-overlay",
|
|
845
|
+
overlay: { ...currentOverlay, scopes: [...existingScopes, scope] },
|
|
846
|
+
origin: { source: "api", at: clock() },
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
if (params.persistence && params.persistence !== "runtime-only") {
|
|
850
|
+
const requestedMetadata = params.metadata ?? {};
|
|
851
|
+
const entryPersistence =
|
|
852
|
+
requestedMetadata.metadataPersistence ??
|
|
853
|
+
(params.persistence === "session" ? "external" : "internal");
|
|
854
|
+
const entry: WorkflowMetadataEntry = {
|
|
855
|
+
entryId: requestedMetadata.entryId ?? `scope-metadata-${scopeId}`,
|
|
856
|
+
metadataId: requestedMetadata.metadataId ?? "workflow.scope",
|
|
857
|
+
anchor: publicAnchor,
|
|
858
|
+
...(params.storyTarget ? { storyTarget: params.storyTarget } : {}),
|
|
859
|
+
scopeId,
|
|
860
|
+
...(requestedMetadata.workItemId
|
|
861
|
+
? { workItemId: requestedMetadata.workItemId }
|
|
862
|
+
: {}),
|
|
863
|
+
...(requestedMetadata.value !== undefined
|
|
864
|
+
? { value: requestedMetadata.value }
|
|
865
|
+
: params.persistence === "document-metadata" && params.label
|
|
866
|
+
? { value: { label: params.label } }
|
|
867
|
+
: {}),
|
|
868
|
+
metadataPersistence: entryPersistence,
|
|
869
|
+
...(requestedMetadata.storageRef !== undefined
|
|
870
|
+
? { storageRef: requestedMetadata.storageRef }
|
|
871
|
+
: {}),
|
|
872
|
+
...(requestedMetadata.metadataVersion !== undefined
|
|
873
|
+
? { metadataVersion: requestedMetadata.metadataVersion }
|
|
874
|
+
: {}),
|
|
875
|
+
};
|
|
876
|
+
const priorEntries = overlayStore.getMetadataEntries();
|
|
877
|
+
deps.dispatch({
|
|
878
|
+
type: "workflow.set-metadata-entries",
|
|
879
|
+
entries: [...priorEntries, entry],
|
|
880
|
+
origin: { source: "api", at: clock() },
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return { scopeId, anchor: publicAnchor };
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function removeScope(scopeId: string): void {
|
|
888
|
+
// Step 1: drop from overlay first (the workflow-blocked-reasons
|
|
889
|
+
// gate in dispatch would otherwise refuse the subsequent
|
|
890
|
+
// document.replace on comment/view scopes).
|
|
891
|
+
const overlay = overlayStore.getOverlay();
|
|
892
|
+
if (overlay) {
|
|
893
|
+
const nextScopes = overlay.scopes.filter((s) => s.scopeId !== scopeId);
|
|
894
|
+
if (nextScopes.length !== overlay.scopes.length) {
|
|
895
|
+
deps.dispatch({
|
|
896
|
+
type: "workflow.set-overlay",
|
|
897
|
+
overlay: { ...overlay, scopes: nextScopes },
|
|
898
|
+
origin: { source: "api", at: clock() },
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Step 2: strip markers from doc.
|
|
903
|
+
const nextDocument = removeScopeMarkers(deps.getDocument(), scopeId);
|
|
904
|
+
if (nextDocument !== deps.getDocument()) {
|
|
905
|
+
deps.dispatch({
|
|
906
|
+
type: "document.replace",
|
|
907
|
+
document: nextDocument,
|
|
908
|
+
origin: { source: "api", at: clock() },
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
// Step 3: clear customXml-persisted metadata entries.
|
|
912
|
+
const entries = overlayStore.getMetadataEntries();
|
|
913
|
+
const nextEntries = entries.filter((e) => e.scopeId !== scopeId);
|
|
914
|
+
if (nextEntries.length !== entries.length) {
|
|
915
|
+
deps.dispatch({
|
|
916
|
+
type: "workflow.set-metadata-entries",
|
|
917
|
+
entries: nextEntries,
|
|
918
|
+
origin: { source: "api", at: clock() },
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function addInvisibleScope(params: AddScopeParams): AddScopeResult {
|
|
924
|
+
const result = addScope({ ...params, mode: params.mode ?? "comment" });
|
|
925
|
+
setScopeVisibility(result.scopeId, "invisible");
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function setScopeVisibility(
|
|
930
|
+
scopeId: string,
|
|
931
|
+
visibility: ScopeVisibility,
|
|
932
|
+
): void {
|
|
933
|
+
const overlay = overlayStore.getOverlay();
|
|
934
|
+
if (!overlay) return;
|
|
935
|
+
const idx = overlay.scopes.findIndex((s) => s.scopeId === scopeId);
|
|
936
|
+
if (idx === -1) return;
|
|
937
|
+
const nextScopes = overlay.scopes.map((s) =>
|
|
938
|
+
s.scopeId === scopeId ? { ...s, visibility } : s,
|
|
939
|
+
);
|
|
940
|
+
deps.dispatch({
|
|
941
|
+
type: "workflow.set-overlay",
|
|
942
|
+
overlay: { ...overlay, scopes: nextScopes },
|
|
943
|
+
origin: { source: "api", at: clock() },
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function getScopeVisibility(scopeId: string): ScopeVisibility {
|
|
948
|
+
const overlay = overlayStore.getOverlay();
|
|
949
|
+
if (!overlay) return "visible";
|
|
950
|
+
const scope = overlay.scopes.find((s) => s.scopeId === scopeId);
|
|
951
|
+
return scope?.visibility ?? "visible";
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function getScope(scopeId: string): WorkflowScope | null {
|
|
955
|
+
const normalized = getNormalizedOverlay();
|
|
956
|
+
const fromOverlay = normalized?.scopes.find((s) => s.scopeId === scopeId);
|
|
957
|
+
if (fromOverlay) return fromOverlay;
|
|
958
|
+
const resolved = resolveScope(deps.getDocument(), scopeId);
|
|
959
|
+
if (!resolved) return null;
|
|
960
|
+
return { scopeId, mode: "comment", anchor: resolved };
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/* -------- overlay / metadata setters (dispatch-backed) -------- */
|
|
964
|
+
|
|
965
|
+
function setWorkflowOverlay(overlay: WorkflowOverlay): void {
|
|
966
|
+
deps.dispatch({
|
|
967
|
+
type: "workflow.set-overlay",
|
|
968
|
+
overlay,
|
|
969
|
+
origin: { source: "api", at: clock() },
|
|
970
|
+
});
|
|
971
|
+
const normalized = getNormalizedOverlay();
|
|
972
|
+
deps.editorStateChannel.recordMutation("workflowOverlay", {
|
|
973
|
+
namespace: "workflowOverlay",
|
|
974
|
+
schemaVersion: "workflow-overlay/1",
|
|
975
|
+
data: normalized ?? overlay,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function clearWorkflowOverlay(): void {
|
|
980
|
+
deps.dispatch({
|
|
981
|
+
type: "workflow.clear-overlay",
|
|
982
|
+
origin: { source: "api", at: clock() },
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function getWorkflowOverlay(): WorkflowOverlay | null {
|
|
987
|
+
return getNormalizedOverlay();
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function setWorkflowMetadataDefinitions(
|
|
991
|
+
definitions: readonly WorkflowMetadataDefinition[],
|
|
992
|
+
): void {
|
|
993
|
+
deps.dispatch({
|
|
994
|
+
type: "workflow.set-metadata-definitions",
|
|
995
|
+
definitions,
|
|
996
|
+
origin: { source: "api", at: clock() },
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function clearWorkflowMetadataDefinitions(): void {
|
|
1001
|
+
deps.dispatch({
|
|
1002
|
+
type: "workflow.clear-metadata-definitions",
|
|
1003
|
+
origin: { source: "api", at: clock() },
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function setWorkflowMetadataEntries(
|
|
1008
|
+
entries: readonly WorkflowMetadataEntry[],
|
|
1009
|
+
): void {
|
|
1010
|
+
deps.dispatch({
|
|
1011
|
+
type: "workflow.set-metadata-entries",
|
|
1012
|
+
entries,
|
|
1013
|
+
origin: { source: "api", at: clock() },
|
|
1014
|
+
});
|
|
1015
|
+
deps.editorStateChannel.recordMutation("workflowMetadata", {
|
|
1016
|
+
namespace: "workflowMetadata",
|
|
1017
|
+
schemaVersion: "workflow-metadata/1",
|
|
1018
|
+
data: entries,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function clearWorkflowMetadataEntries(): void {
|
|
1023
|
+
deps.dispatch({
|
|
1024
|
+
type: "workflow.clear-metadata-entries",
|
|
1025
|
+
origin: { source: "api", at: clock() },
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot {
|
|
1030
|
+
return overlayStore.getMetadataSnapshot();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function setSharedWorkflowState(state: SharedWorkflowState | null): void {
|
|
1034
|
+
const prior = overlayStore.getSharedWorkflowState();
|
|
1035
|
+
if (state === prior) return;
|
|
1036
|
+
overlayStore.replaceSharedWorkflowState(state);
|
|
1037
|
+
cachedInteractionGuardSnapshot = undefined;
|
|
1038
|
+
cachedWorkflowScopeSnapshot = undefined;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function setScopeChromeVisibility(state: ScopeChromeVisibilityState): void {
|
|
1042
|
+
overlayStore.replaceScopeChromeVisibility(state);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function getScopeChromeVisibility(): ScopeChromeVisibilityState {
|
|
1046
|
+
return overlayStore.getScopeChromeVisibility();
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/* -------- W10 overlay-visibility policy (class-A) -------- */
|
|
1050
|
+
|
|
1051
|
+
function getVisibilityPolicy(kind: OverlayKind): OverlayVisibilityPolicy | null {
|
|
1052
|
+
return overlayStore.getVisibilityPolicy(kind);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function getVisibilityPolicies(): readonly OverlayVisibilityPolicy[] {
|
|
1056
|
+
return overlayStore.getVisibilityPolicies();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function setVisibilityPolicy(policy: OverlayVisibilityPolicy): boolean {
|
|
1060
|
+
const changed = overlayStore.replaceVisibilityPolicy(policy.kind, policy);
|
|
1061
|
+
if (changed) emitVisibilityPolicyChanged();
|
|
1062
|
+
return changed;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function clearVisibilityPolicy(kind: OverlayKind): boolean {
|
|
1066
|
+
const changed = overlayStore.replaceVisibilityPolicy(kind, null);
|
|
1067
|
+
if (changed) emitVisibilityPolicyChanged();
|
|
1068
|
+
return changed;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function replaceVisibilityPoliciesBulk(
|
|
1072
|
+
policies: readonly OverlayVisibilityPolicy[],
|
|
1073
|
+
): boolean {
|
|
1074
|
+
const changed = overlayStore.replaceVisibilityPolicies(policies);
|
|
1075
|
+
if (changed) emitVisibilityPolicyChanged();
|
|
1076
|
+
return changed;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function subscribeVisibilityPolicy(listener: () => void): () => void {
|
|
1080
|
+
return overlayStore.subscribeVisibilityPolicy(listener);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/* -------- X5 class-A markup-mode policy -------- */
|
|
1084
|
+
|
|
1085
|
+
function getMarkupModePolicy(): WorkflowMarkupModePolicy | null {
|
|
1086
|
+
return overlayStore.getMarkupModePolicy();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function setMarkupModePolicy(
|
|
1090
|
+
policy: WorkflowMarkupModePolicy | null,
|
|
1091
|
+
): boolean {
|
|
1092
|
+
const changed = overlayStore.replaceMarkupModePolicy(policy);
|
|
1093
|
+
if (changed) {
|
|
1094
|
+
overlayStore.notifyMarkupModePolicyChanged();
|
|
1095
|
+
if (deps.telemetryBus.isEnabled("scope")) {
|
|
1096
|
+
deps.telemetryBus.emit({
|
|
1097
|
+
channel: "scope",
|
|
1098
|
+
type: "scope.markup_mode_policy_changed",
|
|
1099
|
+
t: 0,
|
|
1100
|
+
payload: { hasPolicy: policy !== null },
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return changed;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function subscribeMarkupModePolicy(listener: () => void): () => void {
|
|
1108
|
+
return overlayStore.subscribeMarkupModePolicy(listener);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function emitVisibilityPolicyChanged(): void {
|
|
1112
|
+
// Notify direct subscribers first — L10 X3's ui.overlays.subscribeVisibility
|
|
1113
|
+
// chains onto this signal so UI surfaces re-fire when an authoring tool
|
|
1114
|
+
// mutates policy mid-session.
|
|
1115
|
+
overlayStore.notifyVisibilityPolicyChanged();
|
|
1116
|
+
if (!deps.telemetryBus.isEnabled("scope")) return;
|
|
1117
|
+
deps.telemetryBus.emit({
|
|
1118
|
+
channel: "scope",
|
|
1119
|
+
type: "scope.visibility_policy_changed",
|
|
1120
|
+
t: 0,
|
|
1121
|
+
payload: {
|
|
1122
|
+
total: overlayStore.getVisibilityPolicies().length,
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/* -------- queries + rail -------- */
|
|
1128
|
+
|
|
1129
|
+
function queryScopes(filter?: ScopeQueryFilter): ScopeQueryResult[] {
|
|
1130
|
+
const inputs: ScopeQueryInputs = {
|
|
1131
|
+
overlay: overlayStore.getOverlay(),
|
|
1132
|
+
entries: overlayStore.getMetadataEntries(),
|
|
1133
|
+
document: deps.getDocument(),
|
|
1134
|
+
markerBackedScopeIds: overlayStore.getMarkerBackedScopeIds(),
|
|
1135
|
+
};
|
|
1136
|
+
return runQueryScopes(inputs, filter);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function getRailSegments(pageIndex: number): readonly ScopeRailSegment[] {
|
|
1140
|
+
const input = buildRailInput();
|
|
1141
|
+
if (!input) return [];
|
|
1142
|
+
return collectScopeRailSegments({
|
|
1143
|
+
...input,
|
|
1144
|
+
pageGraph: deps.getPageGraph(),
|
|
1145
|
+
}).filter((segment) => segment.pageIndex === pageIndex);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function getAllRailSegments(): readonly ScopeRailSegment[] {
|
|
1149
|
+
const input = buildRailInput();
|
|
1150
|
+
if (!input) return [];
|
|
1151
|
+
return collectScopeRailSegments({
|
|
1152
|
+
...input,
|
|
1153
|
+
pageGraph: deps.getPageGraph(),
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function getAllScopeCardModels(): readonly ScopeCardModel[] {
|
|
1158
|
+
const railInput = buildRailInput();
|
|
1159
|
+
if (!railInput) return [];
|
|
1160
|
+
const segments = collectScopeRailSegments({
|
|
1161
|
+
...railInput,
|
|
1162
|
+
pageGraph: deps.getPageGraph(),
|
|
1163
|
+
});
|
|
1164
|
+
if (segments.length === 0) return [];
|
|
1165
|
+
const metadata = getCachedWorkflowMarkupSnapshot().metadata;
|
|
1166
|
+
// deps returns `unknown` for suggestions / anchor-index — cast at
|
|
1167
|
+
// the composition boundary because attachScopeCardModel's types
|
|
1168
|
+
// accept the runtime-shaped values verbatim.
|
|
1169
|
+
return attachScopeCardModel({
|
|
1170
|
+
segments,
|
|
1171
|
+
scopes: railInput.scopes ?? [],
|
|
1172
|
+
metadata,
|
|
1173
|
+
anchorIndex: deps.getRenderFrameAnchorIndex() as never,
|
|
1174
|
+
suggestions: deps.getSuggestionsSnapshot() as never,
|
|
1175
|
+
reviewActionMetadata: metadata,
|
|
1176
|
+
candidates: railInput.candidates,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/* -------- dispatch branch handler -------- */
|
|
1181
|
+
|
|
1182
|
+
function applyOverlayCommand(
|
|
1183
|
+
command: CoordinatorDispatchedCommand,
|
|
1184
|
+
onRefreshRender: () => void,
|
|
1185
|
+
): MergeDetachedWarningsResult | null {
|
|
1186
|
+
const documentId = deps.getState().documentId;
|
|
1187
|
+
switch (command.type) {
|
|
1188
|
+
case "workflow.set-overlay": {
|
|
1189
|
+
overlayStore.replaceOverlay(command.overlay, deps.getDocument());
|
|
1190
|
+
const state = deps.getState();
|
|
1191
|
+
const warningDelta = overlayStore.mergeDetachedScopeWarnings(
|
|
1192
|
+
getNormalizedOverlay(),
|
|
1193
|
+
state.warnings,
|
|
1194
|
+
);
|
|
1195
|
+
onRefreshRender();
|
|
1196
|
+
const snapshot = deriveWorkflowScopeSnapshot()!;
|
|
1197
|
+
deps.emitEvent({
|
|
1198
|
+
type: "workflow_overlay_changed",
|
|
1199
|
+
documentId,
|
|
1200
|
+
snapshot,
|
|
1201
|
+
});
|
|
1202
|
+
if (command.overlay.activeWorkItemId !== undefined) {
|
|
1203
|
+
deps.emitEvent({
|
|
1204
|
+
type: "workflow_active_work_item_changed",
|
|
1205
|
+
documentId,
|
|
1206
|
+
activeWorkItemId: command.overlay.activeWorkItemId ?? null,
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
return warningDelta;
|
|
1210
|
+
}
|
|
1211
|
+
case "workflow.clear-overlay": {
|
|
1212
|
+
overlayStore.replaceOverlay(null, deps.getDocument());
|
|
1213
|
+
const state = deps.getState();
|
|
1214
|
+
const warningDelta = overlayStore.mergeDetachedScopeWarnings(
|
|
1215
|
+
null,
|
|
1216
|
+
state.warnings,
|
|
1217
|
+
);
|
|
1218
|
+
onRefreshRender();
|
|
1219
|
+
deps.emitEvent({
|
|
1220
|
+
type: "workflow_active_work_item_changed",
|
|
1221
|
+
documentId,
|
|
1222
|
+
activeWorkItemId: null,
|
|
1223
|
+
});
|
|
1224
|
+
deps.emitEvent({
|
|
1225
|
+
type: "workflow_overlay_changed",
|
|
1226
|
+
documentId,
|
|
1227
|
+
snapshot: {
|
|
1228
|
+
overlayPresent: false,
|
|
1229
|
+
activeWorkItemId: null,
|
|
1230
|
+
scopes: [],
|
|
1231
|
+
candidates: [],
|
|
1232
|
+
blockedReasons: [],
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
return warningDelta;
|
|
1236
|
+
}
|
|
1237
|
+
case "workflow.set-metadata-definitions": {
|
|
1238
|
+
overlayStore.replaceMetadataDefinitions(command.definitions);
|
|
1239
|
+
deps.emitEvent({
|
|
1240
|
+
type: "workflow_metadata_changed",
|
|
1241
|
+
documentId,
|
|
1242
|
+
snapshot: overlayStore.getMetadataSnapshot(),
|
|
1243
|
+
});
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
case "workflow.clear-metadata-definitions": {
|
|
1247
|
+
overlayStore.replaceMetadataDefinitions([]);
|
|
1248
|
+
deps.emitEvent({
|
|
1249
|
+
type: "workflow_metadata_changed",
|
|
1250
|
+
documentId,
|
|
1251
|
+
snapshot: overlayStore.getMetadataSnapshot(),
|
|
1252
|
+
});
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
case "workflow.set-metadata-entries": {
|
|
1256
|
+
overlayStore.replaceMetadataEntries(command.entries);
|
|
1257
|
+
deps.emitEvent({
|
|
1258
|
+
type: "workflow_metadata_changed",
|
|
1259
|
+
documentId,
|
|
1260
|
+
snapshot: overlayStore.getMetadataSnapshot(),
|
|
1261
|
+
});
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
case "workflow.clear-metadata-entries": {
|
|
1265
|
+
overlayStore.replaceMetadataEntries([]);
|
|
1266
|
+
deps.emitEvent({
|
|
1267
|
+
type: "workflow_metadata_changed",
|
|
1268
|
+
documentId,
|
|
1269
|
+
snapshot: overlayStore.getMetadataSnapshot(),
|
|
1270
|
+
});
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
default:
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/* -------- warning synthesis (called after document mutations) -------- */
|
|
1279
|
+
|
|
1280
|
+
function syncDetachedScopeWarnings(
|
|
1281
|
+
existing: readonly InternalEditorWarning[],
|
|
1282
|
+
): MergeDetachedWarningsResult {
|
|
1283
|
+
return overlayStore.mergeDetachedScopeWarnings(
|
|
1284
|
+
getNormalizedOverlay(),
|
|
1285
|
+
existing,
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* -------- cache invalidation -------- */
|
|
1290
|
+
|
|
1291
|
+
function invalidateCachesForDocumentMutation(): void {
|
|
1292
|
+
cachedInteractionGuardSnapshot = undefined;
|
|
1293
|
+
cachedWorkflowScopeSnapshot = undefined;
|
|
1294
|
+
cachedWorkflowMarkupSnapshot = undefined;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function invalidateGuardAndScopeCaches(): void {
|
|
1298
|
+
cachedInteractionGuardSnapshot = undefined;
|
|
1299
|
+
cachedWorkflowScopeSnapshot = undefined;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/* -------- public API surface -------- */
|
|
1303
|
+
|
|
1304
|
+
return {
|
|
1305
|
+
addScope,
|
|
1306
|
+
removeScope,
|
|
1307
|
+
addInvisibleScope,
|
|
1308
|
+
setScopeVisibility,
|
|
1309
|
+
getScopeVisibility,
|
|
1310
|
+
getScope,
|
|
1311
|
+
getMarkerBackedScopeIds: () => overlayStore.getMarkerBackedScopeIds(),
|
|
1312
|
+
setScopeChromeVisibility,
|
|
1313
|
+
getScopeChromeVisibility,
|
|
1314
|
+
getVisibilityPolicy,
|
|
1315
|
+
getVisibilityPolicies,
|
|
1316
|
+
setVisibilityPolicy,
|
|
1317
|
+
clearVisibilityPolicy,
|
|
1318
|
+
replaceVisibilityPolicies: replaceVisibilityPoliciesBulk,
|
|
1319
|
+
subscribeVisibilityPolicy,
|
|
1320
|
+
getMarkupModePolicy,
|
|
1321
|
+
setMarkupModePolicy,
|
|
1322
|
+
subscribeMarkupModePolicy,
|
|
1323
|
+
setWorkflowOverlay,
|
|
1324
|
+
clearWorkflowOverlay,
|
|
1325
|
+
getWorkflowOverlay,
|
|
1326
|
+
setWorkflowMetadataDefinitions,
|
|
1327
|
+
clearWorkflowMetadataDefinitions,
|
|
1328
|
+
setWorkflowMetadataEntries,
|
|
1329
|
+
clearWorkflowMetadataEntries,
|
|
1330
|
+
getWorkflowMetadataSnapshot,
|
|
1331
|
+
setSharedWorkflowState,
|
|
1332
|
+
getInteractionGuardSnapshot: getCachedInteractionGuardSnapshot,
|
|
1333
|
+
getWorkflowScopeSnapshot: getCachedWorkflowScopeSnapshot,
|
|
1334
|
+
getWorkflowMarkupSnapshot: getCachedWorkflowMarkupSnapshot,
|
|
1335
|
+
evaluateBlockedReasons,
|
|
1336
|
+
getMatchingScope: getMatchingWorkflowScope,
|
|
1337
|
+
getMatchingScopeStack: buildMatchingScopeStack,
|
|
1338
|
+
getEffectiveDocumentMode,
|
|
1339
|
+
queryScopes,
|
|
1340
|
+
getRailSegments,
|
|
1341
|
+
getAllRailSegments,
|
|
1342
|
+
getAllScopeCardModels,
|
|
1343
|
+
applyOverlayCommand,
|
|
1344
|
+
syncDetachedScopeWarnings,
|
|
1345
|
+
invalidateCachesForDocumentMutation,
|
|
1346
|
+
invalidateGuardAndScopeCaches,
|
|
1347
|
+
};
|
|
1348
|
+
}
|