@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
|
@@ -81,6 +81,9 @@ import type {
|
|
|
81
81
|
WorkflowCandidateRangeOptions,
|
|
82
82
|
WorkflowBlockedCommandReason,
|
|
83
83
|
WorkflowMetadataDefinition,
|
|
84
|
+
OverlayKind,
|
|
85
|
+
OverlayVisibilityPolicy,
|
|
86
|
+
WorkflowMarkupModePolicy,
|
|
84
87
|
WorkflowMetadataEntry,
|
|
85
88
|
WorkflowMetadataSnapshot,
|
|
86
89
|
WorkflowMarkupSnapshot,
|
|
@@ -129,7 +132,7 @@ import {
|
|
|
129
132
|
snapCommentAnchorAwayFromTable,
|
|
130
133
|
} from "../core/selection/review-anchors.ts";
|
|
131
134
|
import { buildBookmarkNameMap } from "../legal/bookmarks.ts";
|
|
132
|
-
import { createFieldResolver, type FieldResolver } from "./field
|
|
135
|
+
import { createFieldResolver, type FieldResolver } from "./formatting/field/resolver.ts";
|
|
133
136
|
import { createFootnoteResolver, type FootnoteResolver } from "./footnote-resolver.ts";
|
|
134
137
|
import {
|
|
135
138
|
describeOpaqueFragment,
|
|
@@ -150,12 +153,30 @@ import {
|
|
|
150
153
|
findAllScopesAt,
|
|
151
154
|
findScopesIntersecting,
|
|
152
155
|
resolveScope,
|
|
153
|
-
} from "./scope-resolver.ts";
|
|
156
|
+
} from "./workflow/scope-resolver.ts";
|
|
154
157
|
import { buildDiagnosticFromLegacyWarningCode } from "./diagnostics/build-diagnostic.ts";
|
|
158
|
+
import { TelemetryBus } from "./debug/telemetry-bus.ts";
|
|
159
|
+
import { emitStageToken } from "./debug/stage-tokens.ts";
|
|
160
|
+
import {
|
|
161
|
+
createRuntimeDebugFacet,
|
|
162
|
+
type RuntimeDebugFacet,
|
|
163
|
+
} from "./debug/runtime-debug-facet.ts";
|
|
164
|
+
import { setActiveSerializeTelemetryBus } from "../io/export/serialize-main-document.ts";
|
|
155
165
|
import {
|
|
156
166
|
projectScopeQueryResults,
|
|
157
167
|
queryScopes as runQueryScopes,
|
|
158
|
-
} from "./query-scopes.ts";
|
|
168
|
+
} from "./workflow/query-scopes.ts";
|
|
169
|
+
import {
|
|
170
|
+
createOverlayStore,
|
|
171
|
+
seedMarkerBackedScopeIds,
|
|
172
|
+
type OverlayStore,
|
|
173
|
+
} from "./workflow/overlay-store.ts";
|
|
174
|
+
import type { RuntimeOperationPlan, ScopeBundle } from "./scopes/semantic-scope-types.ts";
|
|
175
|
+
import { createScopeCompilerService } from "./scopes/compiler-service.ts";
|
|
176
|
+
import {
|
|
177
|
+
createWorkflowCoordinator,
|
|
178
|
+
type WorkflowCoordinator,
|
|
179
|
+
} from "./workflow/coordinator.ts";
|
|
159
180
|
import {
|
|
160
181
|
insertScopeMarkers,
|
|
161
182
|
removeScopeMarkers,
|
|
@@ -171,7 +192,12 @@ import {
|
|
|
171
192
|
import {
|
|
172
193
|
collectWorkflowMarkupSnapshot,
|
|
173
194
|
deriveWorkflowCandidateRangesFromMarkup,
|
|
174
|
-
} from "./workflow
|
|
195
|
+
} from "./workflow/markup.ts";
|
|
196
|
+
import {
|
|
197
|
+
attachScopeCardModel,
|
|
198
|
+
collectScopeRailSegments,
|
|
199
|
+
type ScopeRailSegment,
|
|
200
|
+
} from "./workflow/rail/compose.ts";
|
|
175
201
|
import {
|
|
176
202
|
createDocumentNavigationSnapshot,
|
|
177
203
|
findPageForOffset,
|
|
@@ -181,12 +207,18 @@ import {
|
|
|
181
207
|
createLayoutEngine,
|
|
182
208
|
createLayoutFacet,
|
|
183
209
|
createMeasurementProvider,
|
|
210
|
+
setActiveLayoutWarningEmitter,
|
|
184
211
|
type DocxFontLoader,
|
|
185
212
|
type LayoutEngineInstance,
|
|
213
|
+
type LayoutFacet,
|
|
186
214
|
type LayoutMeasurementProvider,
|
|
187
215
|
type WordReviewEditorLayoutFacet,
|
|
188
216
|
} from "./layout/index.ts";
|
|
189
217
|
import { createRenderKernel, type RenderKernel } from "./render/index.ts";
|
|
218
|
+
import {
|
|
219
|
+
createGeometryFacet,
|
|
220
|
+
type GeometryFacet,
|
|
221
|
+
} from "./geometry/index.ts";
|
|
190
222
|
import {
|
|
191
223
|
createDocumentOutlineSnapshot,
|
|
192
224
|
createDocumentSectionSnapshots,
|
|
@@ -237,24 +269,25 @@ import {
|
|
|
237
269
|
createEditorViewStateSnapshot,
|
|
238
270
|
type ViewState,
|
|
239
271
|
} from "./view-state.ts";
|
|
240
|
-
import { ThemeColorResolver } from "./theme-color
|
|
272
|
+
import { ThemeColorResolver } from "./formatting/theme-color.ts";
|
|
241
273
|
import type {
|
|
242
274
|
BlockNode,
|
|
243
275
|
CanonicalDocument,
|
|
244
276
|
FieldNode,
|
|
245
277
|
FieldRefreshStatus,
|
|
246
278
|
InlineNode,
|
|
279
|
+
MutableCanonicalDocument,
|
|
247
280
|
PageMargins,
|
|
248
281
|
ParagraphNode,
|
|
249
282
|
SectionProperties,
|
|
250
283
|
SubPartsCatalog,
|
|
251
284
|
} from "../model/canonical-document.ts";
|
|
252
285
|
import {
|
|
253
|
-
buildFieldRegistry,
|
|
254
286
|
isSupportedFieldFamily,
|
|
255
287
|
parseTocLevelRange,
|
|
256
288
|
resolveRefFieldText,
|
|
257
289
|
} from "../io/ooxml/parse-fields.ts";
|
|
290
|
+
import { rebuildFieldRegistry } from "./formatting/field/registry.ts";
|
|
258
291
|
import {
|
|
259
292
|
incrementInvalidationCounter,
|
|
260
293
|
recordPerfSample,
|
|
@@ -286,7 +319,7 @@ import {
|
|
|
286
319
|
import type { EditorStatePayload } from "../io/ooxml/workflow-payload.ts";
|
|
287
320
|
import type { SharedWorkflowState } from "./collab/workflow-shared.ts";
|
|
288
321
|
import { mapLocalSelectionOnRemoteReplay } from "./collab/map-local-selection-on-remote-replay.ts";
|
|
289
|
-
import { formatPageNumber } from "./page-number-format.ts";
|
|
322
|
+
import { formatPageNumber } from "./formatting/field/page-number-format.ts";
|
|
290
323
|
|
|
291
324
|
/** Internal extension of ExportDocxOptions that threads the collected
|
|
292
325
|
* editorState payload from the runtime to the docx serializer. */
|
|
@@ -393,6 +426,16 @@ export interface DocumentRuntime {
|
|
|
393
426
|
/** R.5.a — `true` when the runtime is inside one or more action brackets. */
|
|
394
427
|
isInAction(): boolean;
|
|
395
428
|
applyActiveStoryTextCommand(command: ActiveStoryTextCommand): TextCommandAck;
|
|
429
|
+
/**
|
|
430
|
+
* Layer-08 Slice-5 — apply a compiled `RuntimeOperationPlan` from the
|
|
431
|
+
* scope compiler. Each plan step lowers to one or more existing
|
|
432
|
+
* runtime commands (text.insert, tracked-change ops). Callers route
|
|
433
|
+
* through `src/runtime/scopes/replacement/apply.ts` which validates
|
|
434
|
+
* the plan and emits a single `ScopeActionAudit` on the `scope`
|
|
435
|
+
* telemetry channel — do not call this method directly from agent or
|
|
436
|
+
* UI paths.
|
|
437
|
+
*/
|
|
438
|
+
applyScopeReplacement(plan: RuntimeOperationPlan): void;
|
|
396
439
|
dispatch(command: EditorCommand): void;
|
|
397
440
|
/**
|
|
398
441
|
* Apply a command received from a remote collaborator. The command
|
|
@@ -469,6 +512,33 @@ export interface DocumentRuntime {
|
|
|
469
512
|
editCommentBody(commentId: string, body: string): void;
|
|
470
513
|
addScope(params: AddScopeParams): AddScopeResult;
|
|
471
514
|
getScope(scopeId: string): WorkflowScope | null;
|
|
515
|
+
/**
|
|
516
|
+
* Layer-08 scope-bundle lookup keyed by scopeId — read-side join over
|
|
517
|
+
* canonical document + enumerated scopes + workflow metadata entries.
|
|
518
|
+
* `nowUtc` stays caller-supplied (S3 determinism — no `new Date()`
|
|
519
|
+
* fallback). Returns `null` when the id does not enumerate.
|
|
520
|
+
*
|
|
521
|
+
* Exposed on the runtime so v3's `ui.scope.*` family (L10) can reach
|
|
522
|
+
* `ScopeBundle` without importing `src/runtime/scopes/**` (blocked by
|
|
523
|
+
* `ci-check-ui-api-layer-purity`). Parallels `ai.getScopeBundle`
|
|
524
|
+
* (scope-handle keyed) minus the handle-unpack step and the structured
|
|
525
|
+
* not-found discriminator — the L10 seam takes raw scopeIds from UI
|
|
526
|
+
* state so null/nothing semantics match lookup-by-id elsewhere on the
|
|
527
|
+
* handle (e.g. `getScope`).
|
|
528
|
+
*/
|
|
529
|
+
compileScopeBundleById(scopeId: string, nowUtc: string): ScopeBundle | null;
|
|
530
|
+
/**
|
|
531
|
+
* Debug projector support — readonly view of scope ids the runtime
|
|
532
|
+
* considers marker-backed (present in both `collectScopeLocations(doc)`
|
|
533
|
+
* and `workflowOverlay.scopes`). Internal-only.
|
|
534
|
+
*/
|
|
535
|
+
getMarkerBackedScopeIds(): ReadonlySet<string>;
|
|
536
|
+
/**
|
|
537
|
+
* Debug + telemetry facet. Internal-only. Not forwarded onto the public
|
|
538
|
+
* ref. Phase 2 `services/debug/` accesses it via direct runtime reference
|
|
539
|
+
* (in-process) or via the hidden harness page's `window.__debug` RPC.
|
|
540
|
+
*/
|
|
541
|
+
readonly debug: import("./debug/runtime-debug-facet.ts").RuntimeDebugFacet;
|
|
472
542
|
removeScope(scopeId: string): void;
|
|
473
543
|
/** §C8 — Add a scope with visibility: "invisible" atomically. */
|
|
474
544
|
addInvisibleScope(params: Omit<AddScopeParams, "mode"> & { mode?: WorkflowScopeMode }): AddScopeResult;
|
|
@@ -517,8 +587,59 @@ export interface DocumentRuntime {
|
|
|
517
587
|
* Runtime-owned layout facet. Provides graph-aware queries, fragment
|
|
518
588
|
* resolution, formatting inspection, and layout events. Prefer this over
|
|
519
589
|
* the opaque snapshot methods for new integration code.
|
|
590
|
+
*
|
|
591
|
+
* The public-API type name is `WordReviewEditorLayoutFacet`; the
|
|
592
|
+
* refactor-era structural alias is `LayoutFacet` (see
|
|
593
|
+
* `src/runtime/layout/layout-facet-types.ts`). Both resolve to the same
|
|
594
|
+
* interface.
|
|
520
595
|
*/
|
|
521
596
|
readonly layout: WordReviewEditorLayoutFacet;
|
|
597
|
+
/**
|
|
598
|
+
* Layer-06 (workflow) API surface. Canonical site for scope-rail
|
|
599
|
+
* composition and scope-card projection. Post refactor/06 Slice 4C
|
|
600
|
+
* rail-seam inversion, `runtime.layout` no longer exposes rail/card
|
|
601
|
+
* methods at all — this is the sole seam. See
|
|
602
|
+
* `docs/architecture/06-workflow-review.md` contract W7 + W9.
|
|
603
|
+
*/
|
|
604
|
+
readonly workflow: {
|
|
605
|
+
/** Scope rail segments for the given page index. */
|
|
606
|
+
getRailSegments(pageIndex: number): readonly ScopeRailSegment[];
|
|
607
|
+
/** All scope rail segments across every page in the current page graph. */
|
|
608
|
+
getAllRailSegments(): readonly ScopeRailSegment[];
|
|
609
|
+
/**
|
|
610
|
+
* Project current rail segments into per-scope `ScopeCardModel` values,
|
|
611
|
+
* joining issue/review-action metadata, suggestions, and (when a render
|
|
612
|
+
* kernel is available) primary anchor rects.
|
|
613
|
+
*/
|
|
614
|
+
getAllScopeCardModels(): readonly import("../api/public-types.ts").ScopeCardModel[];
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* Runtime-owned geometry facet (Layer 05 · Geometry Projection).
|
|
618
|
+
*
|
|
619
|
+
* Owns concrete visual geometry: rects, hit-tests, caret geometry, anchor
|
|
620
|
+
* rects, selection rects, replacement envelopes, object handles, and the
|
|
621
|
+
* runtime viewport. Projected from the render-kernel frame + layout
|
|
622
|
+
* semantics — never from the DOM (contract G2;
|
|
623
|
+
* `docs/architecture/05-geometry-projection.md`).
|
|
624
|
+
*
|
|
625
|
+
* Construction is wired to the same `renderKernelRef` as the layout facet,
|
|
626
|
+
* so hit-test and anchor queries read from the current render frame
|
|
627
|
+
* directly rather than via the layout-facet compat wrappers that remain on
|
|
628
|
+
* the layout surface for backwards-compat.
|
|
629
|
+
*
|
|
630
|
+
* v3 `runtime.geometry.*` family (`src/api/v3/runtime/geometry.ts`) reads
|
|
631
|
+
* through this facet; the v3 layer does NOT reach geometry through
|
|
632
|
+
* `runtime.layout` any more.
|
|
633
|
+
*/
|
|
634
|
+
readonly geometry: GeometryFacet;
|
|
635
|
+
/**
|
|
636
|
+
* Refactor/04 Slice 2 — expose the layout facet via the structural alias
|
|
637
|
+
* `LayoutFacet` for consumers that want to depend on the
|
|
638
|
+
* refactor-era name rather than the public-API name. Returns the same
|
|
639
|
+
* underlying facet as `runtime.layout`; the type narrows to `LayoutFacet`
|
|
640
|
+
* at the return site.
|
|
641
|
+
*/
|
|
642
|
+
getLayoutFacet(): LayoutFacet;
|
|
522
643
|
getCurrentLocation(): DocumentLocationSnapshot | null;
|
|
523
644
|
getLocationForSelection(selection: SelectionSnapshot): DocumentLocationSnapshot | null;
|
|
524
645
|
getLocationForAnchor(
|
|
@@ -576,6 +697,68 @@ export interface DocumentRuntime {
|
|
|
576
697
|
setWorkflowMetadataEntries(entries: WorkflowMetadataEntry[]): void;
|
|
577
698
|
clearWorkflowMetadataEntries(): void;
|
|
578
699
|
getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot;
|
|
700
|
+
/** W10 — class-A overlay-visibility policy (per-kind read). */
|
|
701
|
+
getVisibilityPolicy(kind: OverlayKind): OverlayVisibilityPolicy | null;
|
|
702
|
+
/** W10 — class-A policy set. */
|
|
703
|
+
getVisibilityPolicies(): readonly OverlayVisibilityPolicy[];
|
|
704
|
+
/** W10 — author a class-A policy. Returns true when state changed. */
|
|
705
|
+
setVisibilityPolicy(policy: OverlayVisibilityPolicy): boolean;
|
|
706
|
+
/** W10 — clear a class-A policy. Returns true when state changed. */
|
|
707
|
+
clearVisibilityPolicy(kind: OverlayKind): boolean;
|
|
708
|
+
/**
|
|
709
|
+
* W10 — subscribe to any policy-set change. Listener fires after a
|
|
710
|
+
* successful `setVisibilityPolicy` / `clearVisibilityPolicy` call or
|
|
711
|
+
* any internal bulk-replace (reload / collab sync). Returns an
|
|
712
|
+
* unsubscribe function. Consumed by L10 X3's
|
|
713
|
+
* `ui.overlays.subscribeVisibility` so UI subscribers re-fire when
|
|
714
|
+
* authoring-tool hosts mutate policy mid-session.
|
|
715
|
+
*/
|
|
716
|
+
subscribeVisibilityPolicy(listener: () => void): Unsubscribe;
|
|
717
|
+
/** X5 — read the class-A markup-mode policy. `null` means the doc
|
|
718
|
+
* carried no authored mode. */
|
|
719
|
+
getMarkupModePolicy(): WorkflowMarkupModePolicy | null;
|
|
720
|
+
/** X5 — author (or clear with `null`) the class-A markup-mode policy.
|
|
721
|
+
* Returns true when state changed. */
|
|
722
|
+
setMarkupModePolicy(policy: WorkflowMarkupModePolicy | null): boolean;
|
|
723
|
+
/** X5 — subscribe to markup-mode policy changes. Consumed by L10's
|
|
724
|
+
* `ui.viewport` composition (state-classes X5). */
|
|
725
|
+
subscribeMarkupModePolicy(listener: () => void): Unsubscribe;
|
|
726
|
+
/**
|
|
727
|
+
* X5 end-to-end activation — the composition site
|
|
728
|
+
* (`api.ui.viewport.getEffectiveMarkupMode`) hands the runtime a
|
|
729
|
+
* callback that returns the CURRENTLY composed effective markup
|
|
730
|
+
* mode (L06 policy × L10 local preference). When set, the runtime
|
|
731
|
+
* threads it into every `createEditorSurfaceSnapshot` call via
|
|
732
|
+
* `SurfaceProjectionOptions.getEffectiveMarkupMode` so the next
|
|
733
|
+
* projection applies posture without re-parsing the document.
|
|
734
|
+
*
|
|
735
|
+
* Typical wire site: `WordReviewEditor.tsx`, called once right
|
|
736
|
+
* after `createApiV3(runtime, { ui })` so the callback closes over
|
|
737
|
+
* the UI family's closure state (local preference + subscriber
|
|
738
|
+
* chain).
|
|
739
|
+
*
|
|
740
|
+
* Pass `undefined` to clear (teardown). When cleared, surface-
|
|
741
|
+
* projection falls back to the static `revisionMarkupMode` option
|
|
742
|
+
* (if any caller threads one) or to posture-off behavior.
|
|
743
|
+
*/
|
|
744
|
+
setEffectiveMarkupModeProvider(
|
|
745
|
+
provider:
|
|
746
|
+
| (() => "clean" | "simple" | "all" | undefined)
|
|
747
|
+
| undefined,
|
|
748
|
+
): void;
|
|
749
|
+
/**
|
|
750
|
+
* X5 end-to-end activation — the composition site (typically
|
|
751
|
+
* `WordReviewEditor.tsx` subscribing to
|
|
752
|
+
* `api.ui.viewport.subscribeEffectiveMarkupMode`) calls this when
|
|
753
|
+
* the composed mode changes due to a class-C local preference
|
|
754
|
+
* flip. Busts the derived-runtime surface cache + fires a host
|
|
755
|
+
* refresh so the new mode propagates on the next frame.
|
|
756
|
+
*
|
|
757
|
+
* Class-A policy changes already reach the runtime via
|
|
758
|
+
* `setMarkupModePolicy` (which triggers its own invalidation
|
|
759
|
+
* chain), so this entry is for the local-preference-only path.
|
|
760
|
+
*/
|
|
761
|
+
invalidateForMarkupModeChange(): void;
|
|
579
762
|
/**
|
|
580
763
|
* Phase C §C1 — snapshot-based filter + join projection. See
|
|
581
764
|
* `WordReviewEditorRef.queryScopes` for contract.
|
|
@@ -690,6 +873,14 @@ export interface CreateDocumentRuntimeOptions {
|
|
|
690
873
|
defaultAuthorId?: string;
|
|
691
874
|
fatalError?: EditorError;
|
|
692
875
|
clock?: () => string;
|
|
876
|
+
/**
|
|
877
|
+
* Optional pre-constructed telemetry bus. When provided, the runtime
|
|
878
|
+
* uses this instance instead of creating a fresh one — lets callers
|
|
879
|
+
* (e.g. `services/debug/lib/session-manager.ts`) share a single bus
|
|
880
|
+
* across the load path (so the parse channel emits via
|
|
881
|
+
* `setActiveParseTelemetryBus`) and the runtime lifetime.
|
|
882
|
+
*/
|
|
883
|
+
telemetryBus?: TelemetryBus;
|
|
693
884
|
exportDocx?: (
|
|
694
885
|
sessionState: EditorSessionState,
|
|
695
886
|
options?: ExportDocxOptions,
|
|
@@ -734,7 +925,7 @@ export interface CreateDocumentRuntimeOptions {
|
|
|
734
925
|
* `LAYOUT_ENGINE_VERSION` (callers rely on the cache-key scheme in
|
|
735
926
|
* `src/runtime/prerender/cache-key.ts` to guarantee this).
|
|
736
927
|
*
|
|
737
|
-
* See
|
|
928
|
+
* See CLAUDE.md (lane status table) §Phase 2.5 §3.7.
|
|
738
929
|
*/
|
|
739
930
|
seedLayoutCache?: import("./layout/page-graph.ts").RuntimePageGraph;
|
|
740
931
|
}
|
|
@@ -747,7 +938,7 @@ interface HistoryState {
|
|
|
747
938
|
/**
|
|
748
939
|
* L7 Phase 1.7.4 — structural-counts hash for the reviewWork cache key.
|
|
749
940
|
*
|
|
750
|
-
* `
|
|
941
|
+
* `workflowCoordinator.getWorkflowMarkupSnapshot()` returns a NEW object on every
|
|
751
942
|
* `revisionToken` bump, so the previous reference-equality check
|
|
752
943
|
* (`cachedReviewWork.workflowMarkup === wfMarkup`) always failed on pure
|
|
753
944
|
* text edits — forcing `createReviewWorkSnapshot` to re-walk every comment,
|
|
@@ -839,6 +1030,17 @@ export function createDocumentRuntime(
|
|
|
839
1030
|
const loadScheduler: LoadScheduler =
|
|
840
1031
|
options.loadScheduler ?? createLoadScheduler({ backendOverride: "sync" });
|
|
841
1032
|
|
|
1033
|
+
// X5 end-to-end activation (coord-03 §5b). Set by the UI API
|
|
1034
|
+
// composition site (`api.ui.viewport.getEffectiveMarkupMode`) via
|
|
1035
|
+
// `setEffectiveMarkupModeProvider` after `createApiV3` constructs
|
|
1036
|
+
// the ui family. When set, every `createEditorSurfaceSnapshot` call
|
|
1037
|
+
// below threads it into `SurfaceProjectionOptions.getEffectiveMarkupMode`
|
|
1038
|
+
// so revision-display posture reflects the composed L06 × L10 mode
|
|
1039
|
+
// without re-parsing the document.
|
|
1040
|
+
let effectiveMarkupModeProvider:
|
|
1041
|
+
| (() => "clean" | "simple" | "all" | undefined)
|
|
1042
|
+
| undefined;
|
|
1043
|
+
|
|
842
1044
|
// L7 Phase 0 — perf counters. Each `refreshRenderSnapshot` call increments
|
|
843
1045
|
// `refresh.all`. Phase 1 will add per-facet `facet.<name>.build` counters
|
|
844
1046
|
// wired to the per-facet cached builders. Cost is one Map.set per call.
|
|
@@ -878,27 +1080,46 @@ export function createDocumentRuntime(
|
|
|
878
1080
|
enforcedRangeCount: 0,
|
|
879
1081
|
preservedRangeCount: 0,
|
|
880
1082
|
};
|
|
881
|
-
|
|
882
|
-
|
|
1083
|
+
// Phase 1 — session-scoped telemetry. Hoisted early because the
|
|
1084
|
+
// workflow overlay store uses it for the `scope.marker_backed_delta`
|
|
1085
|
+
// event. All channels default off; cost is one bitmask AND per
|
|
1086
|
+
// hook-point when disabled.
|
|
1087
|
+
//
|
|
1088
|
+
// Accepts an injected bus so the load path (docx-session.parseMainDocumentXml
|
|
1089
|
+
// via setActiveParseTelemetryBus) can share one bus with the runtime it
|
|
1090
|
+
// will eventually back — see CreateDocumentRuntimeOptions.telemetryBus.
|
|
1091
|
+
const telemetryBus = options.telemetryBus ?? new TelemetryBus();
|
|
1092
|
+
let hostAnnotationOverlay: HostAnnotationOverlay | null = null;
|
|
1093
|
+
// Layer-06 workflow state + caches live in the overlay store and
|
|
1094
|
+
// coordinator (see src/runtime/workflow/overlay-store.ts +
|
|
1095
|
+
// coordinator.ts). The `overlayStore` is created here before
|
|
1096
|
+
// `state` so the coordinator can seed marker-backed scope IDs as
|
|
1097
|
+
// soon as the initial document lands. `workflowCoordinator` is
|
|
1098
|
+
// wired after `state` + `viewState` + event emitter are in place
|
|
1099
|
+
// (assignment below the initial render-snapshot setup).
|
|
1100
|
+
const overlayStore: OverlayStore = createOverlayStore({
|
|
1101
|
+
initialOverlay:
|
|
883
1102
|
options.initialSessionState?.workflowOverlay ??
|
|
884
1103
|
options.initialSnapshot?.workflowOverlay ??
|
|
885
1104
|
null,
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1105
|
+
initialMetadataDefinitions:
|
|
1106
|
+
options.initialSessionState?.workflowMetadata?.definitions ??
|
|
1107
|
+
options.initialSnapshot?.workflowMetadata?.definitions ??
|
|
1108
|
+
[],
|
|
1109
|
+
initialMetadataEntries:
|
|
1110
|
+
options.initialSessionState?.workflowMetadata?.entries ??
|
|
1111
|
+
options.initialSnapshot?.workflowMetadata?.entries ??
|
|
1112
|
+
[],
|
|
1113
|
+
initialVisibilityPolicies:
|
|
1114
|
+
options.initialSessionState?.visibilityPolicies ??
|
|
1115
|
+
options.initialSnapshot?.visibilityPolicies ??
|
|
1116
|
+
[],
|
|
1117
|
+
initialMarkupModePolicy:
|
|
1118
|
+
options.initialSessionState?.markupModePolicy ??
|
|
1119
|
+
options.initialSnapshot?.markupModePolicy ??
|
|
1120
|
+
null,
|
|
1121
|
+
telemetryBus,
|
|
1122
|
+
});
|
|
902
1123
|
const initialPersistedSnapshot = options.initialSessionState
|
|
903
1124
|
? persistedSnapshotFromEditorSessionState(options.initialSessionState, {
|
|
904
1125
|
savedAt: options.initialSessionState.updatedAt,
|
|
@@ -930,31 +1151,11 @@ export function createDocumentRuntime(
|
|
|
930
1151
|
storySelections.set(storyTargetKey(MAIN_STORY_TARGET), state.selection);
|
|
931
1152
|
lastHeadingFingerprint = computeHeadingFingerprint(state.document);
|
|
932
1153
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (!overlay) {
|
|
939
|
-
markerBackedScopeIds = presentScopeIds;
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
const overlayScopeIds = new Set(overlay.scopes.map((scope) => scope.scopeId));
|
|
943
|
-
const next = new Set<string>();
|
|
944
|
-
for (const scopeId of markerBackedScopeIds) {
|
|
945
|
-
if (overlayScopeIds.has(scopeId)) {
|
|
946
|
-
next.add(scopeId);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
for (const scopeId of presentScopeIds) {
|
|
950
|
-
if (overlayScopeIds.has(scopeId)) {
|
|
951
|
-
next.add(scopeId);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
markerBackedScopeIds = next;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
syncMarkerBackedScopeIds(state.document, workflowOverlay);
|
|
1154
|
+
// Seed marker-backed scope IDs from the initial document. The
|
|
1155
|
+
// overlay store's `replaceOverlay` is the sole writer of that set;
|
|
1156
|
+
// `seedMarkerBackedScopeIds` is an idempotent no-telemetry wrapper
|
|
1157
|
+
// that populates the set from the initial document + overlay.
|
|
1158
|
+
seedMarkerBackedScopeIds(overlayStore, state.document);
|
|
958
1159
|
|
|
959
1160
|
// Runtime-owned paginated layout engine (Phase 1+ of the layout facet work).
|
|
960
1161
|
// The engine caches graph + resolved-formatting + fragment mapper keyed on
|
|
@@ -966,7 +1167,9 @@ export function createDocumentRuntime(
|
|
|
966
1167
|
// `createMeasurementProvider({ preference: "auto", fontLoader })` which
|
|
967
1168
|
// upgrades to the canvas backend once fonts resolve. `swapMeasurementProvider`
|
|
968
1169
|
// emits `measurement_backend_ready` so chrome consumers can re-read metrics.
|
|
969
|
-
const layoutEngine: LayoutEngineInstance = createLayoutEngine(
|
|
1170
|
+
const layoutEngine: LayoutEngineInstance = createLayoutEngine({
|
|
1171
|
+
telemetryBus,
|
|
1172
|
+
});
|
|
970
1173
|
if (options.seedLayoutCache) {
|
|
971
1174
|
// L7 Phase 2.5 — seed the cached graph from the prerender envelope so
|
|
972
1175
|
// the first getPageGraph call skips fullRebuild. Seed is keyed on the
|
|
@@ -996,30 +1199,10 @@ export function createDocumentRuntime(
|
|
|
996
1199
|
}),
|
|
997
1200
|
canonicalDocument: () => state.document,
|
|
998
1201
|
renderKernel: () => renderKernelRef,
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const activeWorkItem =
|
|
1004
|
-
activeWorkItemId !== null
|
|
1005
|
-
? normalizedWorkflowOverlay.workItems?.find(
|
|
1006
|
-
(item) => item.workItemId === activeWorkItemId,
|
|
1007
|
-
)
|
|
1008
|
-
: undefined;
|
|
1009
|
-
return {
|
|
1010
|
-
scopes: normalizedWorkflowOverlay.scopes,
|
|
1011
|
-
candidates: normalizedWorkflowOverlay.candidates,
|
|
1012
|
-
activeWorkItemScopeIds: activeWorkItem?.scopeIds ?? [],
|
|
1013
|
-
activeStory,
|
|
1014
|
-
};
|
|
1015
|
-
},
|
|
1016
|
-
// R2 / scope-card-overlay P1 — surface metadata markup so
|
|
1017
|
-
// `facet.getAllScopeCardModels()` can attach `IssueMetadataValue`
|
|
1018
|
-
// to its scope without the chrome overlay having to re-fetch a
|
|
1019
|
-
// separate snapshot. Reads through the same cached snapshot the
|
|
1020
|
-
// runtime already builds for comment/revision/search consumers.
|
|
1021
|
-
getWorkflowMarkupMetadata: () =>
|
|
1022
|
-
getCachedWorkflowMarkupSnapshot().metadata,
|
|
1202
|
+
// Workflow rail / scope-card composition migrated to
|
|
1203
|
+
// `runtime.workflow` in Layer-06 Slice 4. The layout facet no
|
|
1204
|
+
// longer consumes workflow state — it only provides page-graph
|
|
1205
|
+
// input via the getQueryInput + engine accessors above.
|
|
1023
1206
|
// L7 Phase 2 — delegate viewport culling through the facet so the
|
|
1024
1207
|
// workspace (which only holds a `layoutFacet` ref) can drive culling
|
|
1025
1208
|
// without a separate `DocumentRuntime` prop. The lambdas forward to
|
|
@@ -1047,10 +1230,31 @@ export function createDocumentRuntime(
|
|
|
1047
1230
|
refreshSurfaceOnly();
|
|
1048
1231
|
},
|
|
1049
1232
|
});
|
|
1233
|
+
// D1 — wire the layout-guard warning emitter so `return []` guard sites
|
|
1234
|
+
// in public-facet.ts emit on the `warning` channel when it is enabled.
|
|
1235
|
+
setActiveLayoutWarningEmitter((type, guard, inputs) => {
|
|
1236
|
+
if (!telemetryBus.isEnabled("warning")) return;
|
|
1237
|
+
const t = typeof performance !== "undefined" && typeof performance.now === "function"
|
|
1238
|
+
? performance.now()
|
|
1239
|
+
: Date.now();
|
|
1240
|
+
telemetryBus.emit({ channel: "warning", type, t, payload: { guard, inputs } });
|
|
1241
|
+
});
|
|
1050
1242
|
renderKernelRef = createRenderKernel({
|
|
1051
1243
|
facet: layoutFacet,
|
|
1052
1244
|
getActiveStory: () => activeStory,
|
|
1053
1245
|
});
|
|
1246
|
+
// Layer 05 · Geometry Projection — construct the geometry facet once
|
|
1247
|
+
// the render kernel exists. The facet's `hitTest` / `getAnchorRects` /
|
|
1248
|
+
// `getBlockRects` / `getPage` / `getBlock` / `getCaret` /
|
|
1249
|
+
// `getSelectionRects` / `getReplacementEnvelope` / `getObjectHandles`
|
|
1250
|
+
// all resolve via the kernel's current frame through the pure helpers
|
|
1251
|
+
// in `src/runtime/geometry/**`. Architecture target:
|
|
1252
|
+
// `docs/architecture/05-geometry-projection.md`.
|
|
1253
|
+
const geometryFacet: GeometryFacet = createGeometryFacet({
|
|
1254
|
+
layoutFacet,
|
|
1255
|
+
renderKernel: () => renderKernelRef,
|
|
1256
|
+
getCanonicalDocument: () => state.document,
|
|
1257
|
+
});
|
|
1054
1258
|
// L7 Phase 2 — viewport block range for surface culling.
|
|
1055
1259
|
let viewportBlockRange: { start: number; end: number } | null = null;
|
|
1056
1260
|
|
|
@@ -1105,7 +1309,7 @@ export function createDocumentRuntime(
|
|
|
1105
1309
|
// - `workflowMarkupHash` — structural-counts hash over wfMarkup.
|
|
1106
1310
|
// Replaces the old `workflowMarkup === wfMarkup` reference check,
|
|
1107
1311
|
// which always failed on pure text edits because
|
|
1108
|
-
// `
|
|
1312
|
+
// `workflowCoordinator.getWorkflowMarkupSnapshot()` is keyed on revisionToken.
|
|
1109
1313
|
// Pure text edits keep all per-category item counts stable → hash
|
|
1110
1314
|
// equal. A comment ADDITION, revision AUTHORED, protected-range added,
|
|
1111
1315
|
// opaque-fragment added → at least one count changes → hash diverges.
|
|
@@ -1179,45 +1383,9 @@ export function createDocumentRuntime(
|
|
|
1179
1383
|
snapshot: EditorViewStateSnapshot;
|
|
1180
1384
|
}
|
|
1181
1385
|
| undefined;
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
activeStoryKey: string;
|
|
1186
|
-
selection: EditorState["selection"];
|
|
1187
|
-
readOnly: boolean;
|
|
1188
|
-
documentMode: DocumentMode;
|
|
1189
|
-
protectionSnapshot: ProtectionSnapshot;
|
|
1190
|
-
workflowOverlay: WorkflowOverlay | null;
|
|
1191
|
-
sharedWorkflowState: SharedWorkflowState | null;
|
|
1192
|
-
snapshot: InteractionGuardSnapshot;
|
|
1193
|
-
}
|
|
1194
|
-
| undefined;
|
|
1195
|
-
let cachedWorkflowScopeSnapshot:
|
|
1196
|
-
| {
|
|
1197
|
-
workflowOverlay: WorkflowOverlay;
|
|
1198
|
-
interactionGuardSnapshot: InteractionGuardSnapshot;
|
|
1199
|
-
snapshot: WorkflowScopeSnapshot;
|
|
1200
|
-
}
|
|
1201
|
-
| undefined;
|
|
1202
|
-
let cachedNormalizedWorkflowOverlay:
|
|
1203
|
-
| {
|
|
1204
|
-
document: CanonicalDocumentEnvelope;
|
|
1205
|
-
workflowOverlay: WorkflowOverlay;
|
|
1206
|
-
normalized: WorkflowOverlay;
|
|
1207
|
-
}
|
|
1208
|
-
| undefined;
|
|
1209
|
-
let cachedWorkflowMarkupSnapshot:
|
|
1210
|
-
| {
|
|
1211
|
-
revisionToken: string;
|
|
1212
|
-
activeStoryKey: string;
|
|
1213
|
-
protectionSnapshot: ProtectionSnapshot;
|
|
1214
|
-
preservation: CanonicalDocumentEnvelope["preservation"];
|
|
1215
|
-
workflowOverlay: WorkflowOverlay | null;
|
|
1216
|
-
workflowMetadataDefinitions: WorkflowMetadataDefinition[];
|
|
1217
|
-
workflowMetadataEntries: WorkflowMetadataEntry[];
|
|
1218
|
-
snapshot: WorkflowMarkupSnapshot;
|
|
1219
|
-
}
|
|
1220
|
-
| undefined;
|
|
1386
|
+
// Layer-06 workflow/review caches (interaction guard, scope
|
|
1387
|
+
// snapshot, normalized overlay, markup snapshot) live on the
|
|
1388
|
+
// workflow coordinator. See `src/runtime/workflow/coordinator.ts`.
|
|
1221
1389
|
// Keyed on block count + subParts identity (not revisionToken) — fields only
|
|
1222
1390
|
// change when blocks are inserted/deleted or subParts (headers/footers) change,
|
|
1223
1391
|
// NOT on text-only edits. Cleared explicitly by updateFields() for field-refresh.
|
|
@@ -1260,7 +1428,12 @@ export function createDocumentRuntime(
|
|
|
1260
1428
|
return cachedSurface.snapshot;
|
|
1261
1429
|
}
|
|
1262
1430
|
|
|
1263
|
-
const snapshot = createEditorSurfaceSnapshot(document, state.selection, nextActiveStory, {
|
|
1431
|
+
const snapshot = createEditorSurfaceSnapshot(document, state.selection, nextActiveStory, {
|
|
1432
|
+
viewportBlockRange,
|
|
1433
|
+
...(effectiveMarkupModeProvider
|
|
1434
|
+
? { getEffectiveMarkupMode: effectiveMarkupModeProvider }
|
|
1435
|
+
: {}),
|
|
1436
|
+
});
|
|
1264
1437
|
recordPerfSample("snapshot.surface");
|
|
1265
1438
|
incrementInvalidationCounter("runtime.snapshot.surfaceMisses");
|
|
1266
1439
|
cachedSurface = {
|
|
@@ -1505,197 +1678,13 @@ export function createDocumentRuntime(
|
|
|
1505
1678
|
return snapshot;
|
|
1506
1679
|
}
|
|
1507
1680
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
)
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
// is locked (the round state supersedes scope/overlay-level gating).
|
|
1516
|
-
// Emit a reason code whose effectiveMode mapping matches the mode intent:
|
|
1517
|
-
// "commenting" → workflow_comment_only (maps to effectiveMode: "comment")
|
|
1518
|
-
// "viewing" → workflow_view_only (maps to effectiveMode: "view")
|
|
1519
|
-
// "suggesting" → workflow_round_locked (no existing mapping; stays "blocked"
|
|
1520
|
-
// for this slice — full suggesting-mode semantics will be a
|
|
1521
|
-
// future slice that hooks getEffectiveDocumentMode instead).
|
|
1522
|
-
if (sharedWorkflowState?.lockedMode && sharedWorkflowState.lockedMode !== "editing") {
|
|
1523
|
-
const lockedMode = sharedWorkflowState.lockedMode;
|
|
1524
|
-
const code: WorkflowBlockedCommandReason["code"] =
|
|
1525
|
-
lockedMode === "commenting"
|
|
1526
|
-
? "workflow_comment_only"
|
|
1527
|
-
: lockedMode === "viewing"
|
|
1528
|
-
? "workflow_view_only"
|
|
1529
|
-
: "workflow_round_locked";
|
|
1530
|
-
reasons.push({
|
|
1531
|
-
code,
|
|
1532
|
-
message: `Round is locked to ${lockedMode} mode.`,
|
|
1533
|
-
});
|
|
1534
|
-
return reasons;
|
|
1535
|
-
}
|
|
1536
|
-
const selectionBounds = {
|
|
1537
|
-
from: Math.min(selection.anchor, selection.head),
|
|
1538
|
-
to: Math.max(selection.anchor, selection.head),
|
|
1539
|
-
};
|
|
1540
|
-
const selectionRange = expandSelectionRange(selectionBounds);
|
|
1541
|
-
const opaqueReason = deriveOpaqueWorkflowBlockedReason(selectionRange);
|
|
1542
|
-
|
|
1543
|
-
if (opaqueReason) {
|
|
1544
|
-
reasons.push(opaqueReason);
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
if (state.readOnly) {
|
|
1548
|
-
reasons.push({
|
|
1549
|
-
code: "document_read_only",
|
|
1550
|
-
message: "Document is in read-only mode.",
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
if (viewState.documentMode === "viewing" || viewState.documentMode === "commenting") {
|
|
1555
|
-
reasons.push({
|
|
1556
|
-
code: "document_viewing_mode",
|
|
1557
|
-
message: "Document is in viewing mode.",
|
|
1558
|
-
});
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
if (
|
|
1562
|
-
isBlockedByProtection(protectionSnapshot, selection)
|
|
1563
|
-
) {
|
|
1564
|
-
reasons.push({
|
|
1565
|
-
code: "protected_range",
|
|
1566
|
-
message: "Selection falls within a protected range.",
|
|
1567
|
-
});
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
const effectiveDocumentMode = getEffectiveDocumentMode(selection);
|
|
1571
|
-
|
|
1572
|
-
if (effectiveDocumentMode === "suggesting" && commandType) {
|
|
1573
|
-
if (SUGGESTING_UNSUPPORTED_COMMANDS.has(commandType)) {
|
|
1574
|
-
reasons.push({
|
|
1575
|
-
code: "suggesting_unsupported",
|
|
1576
|
-
message: `"${commandType}" is not supported in suggesting mode.`,
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
const normalizedWorkflowOverlay = getNormalizedWorkflowOverlay();
|
|
1582
|
-
if (normalizedWorkflowOverlay) {
|
|
1583
|
-
const matchingScope = getMatchingWorkflowScope(selection);
|
|
1584
|
-
const activeScopes = getEffectiveWorkflowScopes(normalizedWorkflowOverlay);
|
|
1585
|
-
// §C8: invisible non-view scopes are transparent to the interaction guard.
|
|
1586
|
-
// Don't count them toward the "outside_workflow_scope" threshold.
|
|
1587
|
-
const guardingScopes = activeScopes.filter(
|
|
1588
|
-
(s) => !(s.visibility === "invisible" && s.mode !== "view"),
|
|
1589
|
-
);
|
|
1590
|
-
|
|
1591
|
-
if (!matchingScope && guardingScopes.length > 0) {
|
|
1592
|
-
reasons.push({
|
|
1593
|
-
code: "outside_workflow_scope",
|
|
1594
|
-
message: "Selection is outside any active workflow scope.",
|
|
1595
|
-
});
|
|
1596
|
-
} else if (matchingScope) {
|
|
1597
|
-
if (matchingScope.mode === "comment") {
|
|
1598
|
-
const isCommentCommand =
|
|
1599
|
-
commandType?.startsWith("comment.") ?? false;
|
|
1600
|
-
if (!isCommentCommand) {
|
|
1601
|
-
reasons.push({
|
|
1602
|
-
code: "workflow_comment_only",
|
|
1603
|
-
message: `Scope "${matchingScope.label ?? matchingScope.scopeId}" allows comments only.`,
|
|
1604
|
-
scopeId: matchingScope.scopeId,
|
|
1605
|
-
workItemId: matchingScope.workItemId,
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
} else if (matchingScope.mode === "view") {
|
|
1609
|
-
reasons.push({
|
|
1610
|
-
code: "workflow_view_only",
|
|
1611
|
-
message: `Scope "${matchingScope.label ?? matchingScope.scopeId}" is view-only.`,
|
|
1612
|
-
scopeId: matchingScope.scopeId,
|
|
1613
|
-
workItemId: matchingScope.workItemId,
|
|
1614
|
-
});
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
return reasons;
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
// §C6 — most-restrictive-wins ordering for overlap layering.
|
|
1623
|
-
const MODE_RESTRICTIVENESS: Record<WorkflowScopeMode, number> = {
|
|
1624
|
-
edit: 0,
|
|
1625
|
-
suggest: 1,
|
|
1626
|
-
comment: 2,
|
|
1627
|
-
view: 3,
|
|
1628
|
-
};
|
|
1629
|
-
|
|
1630
|
-
/**
|
|
1631
|
-
* §C6 — Collect all guard-eligible scopes that contain `selection`,
|
|
1632
|
-
* sorted outermost→innermost (startPos ASC, endPos DESC, scopeId ASC).
|
|
1633
|
-
* Excludes invisible non-view scopes per §C8.
|
|
1634
|
-
*/
|
|
1635
|
-
function buildMatchingScopeStack(
|
|
1636
|
-
selection: EditorState["selection"],
|
|
1637
|
-
): WorkflowOverlay["scopes"] {
|
|
1638
|
-
if (!workflowOverlay) return [];
|
|
1639
|
-
const selectionBounds = {
|
|
1640
|
-
from: Math.min(selection.anchor, selection.head),
|
|
1641
|
-
to: Math.max(selection.anchor, selection.head),
|
|
1642
|
-
};
|
|
1643
|
-
const activeScopes = getEffectiveWorkflowScopes(workflowOverlay);
|
|
1644
|
-
const matching = activeScopes.filter((scope) => {
|
|
1645
|
-
// §C8
|
|
1646
|
-
if (scope.visibility === "invisible" && scope.mode !== "view") return false;
|
|
1647
|
-
if (scope.anchor.kind === "detached") return false;
|
|
1648
|
-
const scopeFrom = scope.anchor.kind === "range" ? scope.anchor.from : scope.anchor.at;
|
|
1649
|
-
const scopeTo = scope.anchor.kind === "range" ? scope.anchor.to : scope.anchor.at;
|
|
1650
|
-
return selectionBounds.from >= scopeFrom && selectionBounds.to <= scopeTo;
|
|
1651
|
-
});
|
|
1652
|
-
// Outermost first: startPos ASC, endPos DESC (wider span = outer), scopeId ASC tiebreak
|
|
1653
|
-
matching.sort((a, b) => {
|
|
1654
|
-
const aFrom = a.anchor.kind === "range" ? a.anchor.from : (a.anchor as { at: number }).at;
|
|
1655
|
-
const bFrom = b.anchor.kind === "range" ? b.anchor.from : (b.anchor as { at: number }).at;
|
|
1656
|
-
if (aFrom !== bFrom) return aFrom - bFrom;
|
|
1657
|
-
const aTo = a.anchor.kind === "range" ? a.anchor.to : (a.anchor as { at: number }).at;
|
|
1658
|
-
const bTo = b.anchor.kind === "range" ? b.anchor.to : (b.anchor as { at: number }).at;
|
|
1659
|
-
if (aTo !== bTo) return bTo - aTo; // wider first
|
|
1660
|
-
return a.scopeId < b.scopeId ? -1 : a.scopeId > b.scopeId ? 1 : 0;
|
|
1661
|
-
});
|
|
1662
|
-
return matching;
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
function getMatchingWorkflowScope(
|
|
1666
|
-
selection: EditorState["selection"],
|
|
1667
|
-
): WorkflowOverlay["scopes"][number] | null {
|
|
1668
|
-
const stack = buildMatchingScopeStack(selection);
|
|
1669
|
-
if (stack.length === 0) return null;
|
|
1670
|
-
// §C6 — most-restrictive scope wins across all overlapping scopes.
|
|
1671
|
-
return stack.reduce((best, scope) =>
|
|
1672
|
-
(MODE_RESTRICTIVENESS[scope.mode] ?? 0) > (MODE_RESTRICTIVENESS[best.mode] ?? 0)
|
|
1673
|
-
? scope
|
|
1674
|
-
: best,
|
|
1675
|
-
);
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
function getEffectiveDocumentMode(
|
|
1679
|
-
selection: EditorState["selection"],
|
|
1680
|
-
): DocumentMode {
|
|
1681
|
-
if (viewState.documentMode === "viewing" || viewState.documentMode === "commenting") {
|
|
1682
|
-
return viewState.documentMode;
|
|
1683
|
-
}
|
|
1684
|
-
const matchingScope = getMatchingWorkflowScope(selection);
|
|
1685
|
-
if (matchingScope?.mode === "suggest") {
|
|
1686
|
-
return "suggesting";
|
|
1687
|
-
}
|
|
1688
|
-
return viewState.documentMode;
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
function expandSelectionRange(
|
|
1692
|
-
range: { from: number; to: number },
|
|
1693
|
-
): { from: number; to: number } {
|
|
1694
|
-
return {
|
|
1695
|
-
from: range.from,
|
|
1696
|
-
to: range.to > range.from ? range.to : range.from + 1,
|
|
1697
|
-
};
|
|
1698
|
-
}
|
|
1681
|
+
// Layer-06 blocked-reason composition + scope-matching heuristics
|
|
1682
|
+
// live on the workflow coordinator (see
|
|
1683
|
+
// `src/runtime/workflow/coordinator.ts` — §C6 + §C8 rules, most-
|
|
1684
|
+
// restrictive-wins, invisible-scope carve-out). Runtime call sites
|
|
1685
|
+
// delegate via `workflowCoordinator.evaluateBlockedReasons`,
|
|
1686
|
+
// `workflowCoordinator.getMatchingScope`, and
|
|
1687
|
+
// `workflowCoordinator.getEffectiveDocumentMode`.
|
|
1699
1688
|
|
|
1700
1689
|
function deriveOpaqueWorkflowBlockedReason(
|
|
1701
1690
|
range: { from: number; to: number },
|
|
@@ -1920,265 +1909,71 @@ export function createDocumentRuntime(
|
|
|
1920
1909
|
return left.from < right.to && right.from < left.to;
|
|
1921
1910
|
}
|
|
1922
1911
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
if (left.kind !== right.kind) return false;
|
|
1928
|
-
switch (left.kind) {
|
|
1929
|
-
case "range":
|
|
1930
|
-
return (
|
|
1931
|
-
right.kind === "range" &&
|
|
1932
|
-
left.from === right.from &&
|
|
1933
|
-
left.to === right.to &&
|
|
1934
|
-
left.assoc.start === right.assoc.start &&
|
|
1935
|
-
left.assoc.end === right.assoc.end
|
|
1936
|
-
);
|
|
1937
|
-
case "node":
|
|
1938
|
-
return right.kind === "node" && left.at === right.at;
|
|
1939
|
-
case "detached":
|
|
1940
|
-
return (
|
|
1941
|
-
right.kind === "detached" &&
|
|
1942
|
-
left.reason === right.reason &&
|
|
1943
|
-
left.lastKnownRange.from === right.lastKnownRange.from &&
|
|
1944
|
-
left.lastKnownRange.to === right.lastKnownRange.to
|
|
1945
|
-
);
|
|
1946
|
-
default:
|
|
1947
|
-
return false;
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
function normalizeWorkflowOverlayForDocument(
|
|
1952
|
-
document: CanonicalDocumentEnvelope,
|
|
1953
|
-
overlay: WorkflowOverlay,
|
|
1954
|
-
): WorkflowOverlay {
|
|
1955
|
-
if (
|
|
1956
|
-
cachedNormalizedWorkflowOverlay &&
|
|
1957
|
-
cachedNormalizedWorkflowOverlay.document === document &&
|
|
1958
|
-
cachedNormalizedWorkflowOverlay.workflowOverlay === overlay
|
|
1959
|
-
) {
|
|
1960
|
-
return cachedNormalizedWorkflowOverlay.normalized;
|
|
1961
|
-
}
|
|
1912
|
+
// Layer-06 overlay normalization, rail input composition, and
|
|
1913
|
+
// anchor-equality helpers live on the workflow coordinator +
|
|
1914
|
+
// overlay store. See `src/runtime/workflow/overlay-store.ts` and
|
|
1915
|
+
// `coordinator.ts`.
|
|
1962
1916
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
return scope;
|
|
1972
|
-
}
|
|
1973
|
-
const location = locations.get(scope.scopeId);
|
|
1974
|
-
const isMarkerBacked = markerBackedScopeIds.has(scope.scopeId);
|
|
1975
|
-
let nextAnchor: EditorAnchorProjection | null = null;
|
|
1976
|
-
if (
|
|
1977
|
-
location &&
|
|
1978
|
-
location.startPos !== undefined &&
|
|
1979
|
-
location.endPos !== undefined
|
|
1980
|
-
) {
|
|
1981
|
-
nextAnchor = {
|
|
1982
|
-
kind: "range",
|
|
1983
|
-
from: Math.min(location.startPos, location.endPos),
|
|
1984
|
-
to: Math.max(location.startPos, location.endPos),
|
|
1985
|
-
assoc: { start: -1, end: 1 },
|
|
1986
|
-
};
|
|
1987
|
-
} else if (isMarkerBacked) {
|
|
1988
|
-
const lastKnownRange =
|
|
1989
|
-
scope.anchor.kind === "range"
|
|
1990
|
-
? { from: scope.anchor.from, to: scope.anchor.to }
|
|
1991
|
-
: scope.anchor.kind === "node"
|
|
1992
|
-
? { from: scope.anchor.at, to: scope.anchor.at }
|
|
1993
|
-
: scope.anchor.lastKnownRange;
|
|
1994
|
-
nextAnchor = {
|
|
1995
|
-
kind: "detached",
|
|
1996
|
-
reason:
|
|
1997
|
-
location && (location.startPos !== undefined || location.endPos !== undefined)
|
|
1998
|
-
? "deleted"
|
|
1999
|
-
: "invalidatedByStructureChange",
|
|
2000
|
-
lastKnownRange,
|
|
2001
|
-
};
|
|
2002
|
-
} else {
|
|
2003
|
-
return scope;
|
|
2004
|
-
}
|
|
2005
|
-
if (workflowAnchorsEqual(scope.anchor, nextAnchor)) {
|
|
2006
|
-
return scope;
|
|
2007
|
-
}
|
|
2008
|
-
changed = true;
|
|
2009
|
-
return {
|
|
2010
|
-
...scope,
|
|
2011
|
-
anchor: nextAnchor,
|
|
2012
|
-
};
|
|
2013
|
-
});
|
|
2014
|
-
|
|
2015
|
-
const normalized = changed
|
|
2016
|
-
? {
|
|
2017
|
-
...overlay,
|
|
2018
|
-
scopes: normalizedScopes,
|
|
2019
|
-
}
|
|
2020
|
-
: overlay;
|
|
2021
|
-
cachedNormalizedWorkflowOverlay = {
|
|
2022
|
-
document,
|
|
2023
|
-
workflowOverlay: overlay,
|
|
2024
|
-
normalized,
|
|
2025
|
-
};
|
|
2026
|
-
return normalized;
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
function getNormalizedWorkflowOverlay(): WorkflowOverlay | null {
|
|
2030
|
-
if (!workflowOverlay) return null;
|
|
2031
|
-
return normalizeWorkflowOverlayForDocument(state.document, workflowOverlay);
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
function buildWarningSignature(warning: InternalEditorWarning): string {
|
|
2035
|
-
return JSON.stringify({
|
|
2036
|
-
code: warning.code,
|
|
2037
|
-
severity: warning.severity,
|
|
2038
|
-
message: warning.message,
|
|
2039
|
-
source: warning.source,
|
|
2040
|
-
featureEntryId: warning.featureEntryId ?? null,
|
|
2041
|
-
details: warning.details ?? null,
|
|
2042
|
-
affectedAnchor: warning.affectedAnchor ?? null,
|
|
1917
|
+
function getCurrentPageGraph() {
|
|
1918
|
+
return layoutEngine.getPageGraph({
|
|
1919
|
+
document: state.document,
|
|
1920
|
+
viewState: {
|
|
1921
|
+
activeStory,
|
|
1922
|
+
workspaceMode: viewState.workspaceMode,
|
|
1923
|
+
zoomLevel: viewState.zoomLevel,
|
|
1924
|
+
},
|
|
2043
1925
|
});
|
|
2044
1926
|
}
|
|
2045
1927
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
);
|
|
2064
|
-
const existingDetachedWarnings = existingWarnings.filter(
|
|
2065
|
-
(warning) => warning.code === "workflow_scope_invalidated",
|
|
2066
|
-
);
|
|
2067
|
-
const existingById = new Map(
|
|
2068
|
-
existingDetachedWarnings.map((warning) => [warning.warningId, warning] as const),
|
|
2069
|
-
);
|
|
2070
|
-
const desiredById = new Map(
|
|
2071
|
-
[...detachedScopesById.values()].map((scope) => {
|
|
2072
|
-
const warning = createInvalidatedWorkflowScopeWarning(scope);
|
|
2073
|
-
return [warning.warningId, warning] as const;
|
|
2074
|
-
}),
|
|
1928
|
+
// Layer-06 workflow coordinator — owns the orchestration layer
|
|
1929
|
+
// that used to live inline here (scope lifecycle, overlay / metadata
|
|
1930
|
+
// setters, interaction-guard + scope + markup snapshot caches,
|
|
1931
|
+
// blocked-reason composition + scope matching, rail composition,
|
|
1932
|
+
// dispatch-branch handler, warning synthesis). Dependencies are
|
|
1933
|
+
// late-bound via accessor functions — `emit`, `refreshRenderSnapshot`
|
|
1934
|
+
// etc. are function declarations (hoisted) so the accessor closures
|
|
1935
|
+
// remain valid even though the declarations live further down the
|
|
1936
|
+
// file. `dispatchToRuntime` is similarly late-bound to avoid a
|
|
1937
|
+
// circular reference between coordinator and the `runtime` object
|
|
1938
|
+
// literal it gets spliced into.
|
|
1939
|
+
//
|
|
1940
|
+
// The runtime's own public methods forward to `workflowCoordinator`
|
|
1941
|
+
// — see the method definitions on the returned `runtime` object.
|
|
1942
|
+
let dispatchToRuntime: (command: unknown) => void = () => {
|
|
1943
|
+
throw new Error(
|
|
1944
|
+
"workflow coordinator: dispatch called before runtime is fully constructed",
|
|
2075
1945
|
);
|
|
1946
|
+
};
|
|
2076
1947
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
nextWarnings: [...retainedWarnings, ...desiredById.values()],
|
|
2100
|
-
added,
|
|
2101
|
-
cleared,
|
|
2102
|
-
};
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
function createInvalidatedWorkflowScopeWarning(
|
|
2106
|
-
scope: WorkflowScope,
|
|
2107
|
-
): InternalEditorWarning {
|
|
2108
|
-
const anchor = scope.anchor.kind === "detached" ? scope.anchor : null;
|
|
2109
|
-
const subject = scope.label
|
|
2110
|
-
? `Workflow scope "${scope.label}" (${scope.scopeId})`
|
|
2111
|
-
: `Workflow scope ${scope.scopeId}`;
|
|
2112
|
-
const reasonPhrase =
|
|
2113
|
-
anchor?.reason === "deleted"
|
|
2114
|
-
? "its anchored text was deleted"
|
|
2115
|
-
: anchor?.reason === "invalidatedByStructureChange"
|
|
2116
|
-
? "document structure changed around it"
|
|
2117
|
-
: "its anchor could not be resolved unambiguously";
|
|
2118
|
-
const modePhrase =
|
|
2119
|
-
scope.mode === "view"
|
|
2120
|
-
? "read-only enforcement"
|
|
2121
|
-
: `${scope.mode} enforcement`;
|
|
2122
|
-
|
|
2123
|
-
return {
|
|
2124
|
-
warningId: `warning:workflow-scope-invalidated:${scope.scopeId}`,
|
|
2125
|
-
code: "workflow_scope_invalidated",
|
|
2126
|
-
severity: "warning",
|
|
2127
|
-
message: `${subject} was invalidated because ${reasonPhrase}. Reapply the scope before relying on ${modePhrase}.`,
|
|
2128
|
-
source: "runtime",
|
|
2129
|
-
affectedAnchor: anchor ? toInternalAnchorProjection(anchor) : undefined,
|
|
2130
|
-
diagnostic: buildDiagnosticFromLegacyWarningCode("workflow_scope_invalidated", {
|
|
2131
|
-
diagnosticId: `warning-diag:workflow-scope-invalidated:${scope.scopeId}`,
|
|
2132
|
-
technical: {
|
|
2133
|
-
message: `${subject} lost its trusted anchor and is now detached.`,
|
|
2134
|
-
source: "runtime",
|
|
2135
|
-
},
|
|
2136
|
-
details: {
|
|
2137
|
-
scopeId: scope.scopeId,
|
|
2138
|
-
label: scope.label,
|
|
2139
|
-
mode: scope.mode,
|
|
2140
|
-
reason: anchor?.reason,
|
|
2141
|
-
lastKnownRange: anchor?.lastKnownRange,
|
|
2142
|
-
storyTarget: scope.storyTarget,
|
|
2143
|
-
reapplySuggested: true,
|
|
2144
|
-
},
|
|
2145
|
-
affectedAnchor: anchor ? scope.anchor : undefined,
|
|
2146
|
-
llmMetadata: {
|
|
2147
|
-
userSummary: `${subject} is no longer attached to trusted document content. Reapply the scope before relying on it.`,
|
|
2148
|
-
remediation: {
|
|
2149
|
-
kind: "fallback",
|
|
2150
|
-
suggestion:
|
|
2151
|
-
"Locate the intended text using warning.details.scopeId and warning.details.lastKnownRange, then call addScope again for the repaired range.",
|
|
2152
|
-
},
|
|
2153
|
-
recoveryClass: "requires-input",
|
|
2154
|
-
echoedInput: {
|
|
2155
|
-
scopeId: scope.scopeId,
|
|
2156
|
-
lastKnownRange: anchor?.lastKnownRange,
|
|
2157
|
-
},
|
|
2158
|
-
},
|
|
2159
|
-
}),
|
|
2160
|
-
details: {
|
|
2161
|
-
scopeId: scope.scopeId,
|
|
2162
|
-
label: scope.label,
|
|
2163
|
-
mode: scope.mode,
|
|
2164
|
-
reason: anchor?.reason,
|
|
2165
|
-
lastKnownRange: anchor?.lastKnownRange,
|
|
2166
|
-
storyTarget: scope.storyTarget,
|
|
2167
|
-
reapplySuggested: true,
|
|
2168
|
-
actionabilityNote:
|
|
2169
|
-
"Resolve the intended text again, then reapply the scope; the previous anchor is no longer trusted.",
|
|
2170
|
-
},
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
1948
|
+
const workflowCoordinator: WorkflowCoordinator = createWorkflowCoordinator({
|
|
1949
|
+
overlayStore,
|
|
1950
|
+
telemetryBus,
|
|
1951
|
+
clock,
|
|
1952
|
+
getDocument: () => state.document,
|
|
1953
|
+
getState: () => state,
|
|
1954
|
+
getActiveStory: () => activeStory,
|
|
1955
|
+
getDocumentMode: () => viewState.documentMode,
|
|
1956
|
+
getProtectionSnapshot: () => protectionSnapshot,
|
|
1957
|
+
getRenderSnapshot: () => cachedRenderSnapshot,
|
|
1958
|
+
getFieldSnapshot: () => getCachedFieldSnapshot(state.document),
|
|
1959
|
+
getSuggestionsSnapshot: () => getCachedSuggestionsSnapshot(state),
|
|
1960
|
+
getRenderFrameAnchorIndex: () =>
|
|
1961
|
+
renderKernelRef?.getRenderFrame?.()?.anchorIndex ?? null,
|
|
1962
|
+
getPageGraph: () => getCurrentPageGraph(),
|
|
1963
|
+
deriveOpaqueWorkflowBlockedReason,
|
|
1964
|
+
isBlockedByProtection,
|
|
1965
|
+
dispatch: (command) => dispatchToRuntime(command as never),
|
|
1966
|
+
emitEvent: (event) => emit(event as DocumentRuntimeEvent),
|
|
1967
|
+
editorStateChannel,
|
|
1968
|
+
suggestingUnsupportedCommands: SUGGESTING_UNSUPPORTED_COMMANDS,
|
|
1969
|
+
});
|
|
2173
1970
|
|
|
2174
1971
|
function syncDetachedWorkflowScopeWarningsInState(): {
|
|
2175
1972
|
added: InternalEditorWarning[];
|
|
2176
1973
|
cleared: Array<{ warningId: string; code: InternalEditorWarning["code"] }>;
|
|
2177
1974
|
} {
|
|
2178
|
-
const { nextWarnings, added, cleared } =
|
|
2179
|
-
|
|
2180
|
-
state.warnings,
|
|
2181
|
-
);
|
|
1975
|
+
const { nextWarnings, added, cleared } =
|
|
1976
|
+
workflowCoordinator.syncDetachedScopeWarnings(state.warnings);
|
|
2182
1977
|
if (added.length === 0 && cleared.length === 0) {
|
|
2183
1978
|
return { added, cleared };
|
|
2184
1979
|
}
|
|
@@ -2186,25 +1981,6 @@ export function createDocumentRuntime(
|
|
|
2186
1981
|
return { added, cleared };
|
|
2187
1982
|
}
|
|
2188
1983
|
|
|
2189
|
-
function deriveWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null {
|
|
2190
|
-
const normalizedWorkflowOverlay = getNormalizedWorkflowOverlay();
|
|
2191
|
-
if (!normalizedWorkflowOverlay) return null;
|
|
2192
|
-
const blockedReasons = getCachedInteractionGuardSnapshot().blockedReasons;
|
|
2193
|
-
const activeItem = normalizedWorkflowOverlay.activeWorkItemId
|
|
2194
|
-
? normalizedWorkflowOverlay.workItems?.find(
|
|
2195
|
-
(item) => item.workItemId === normalizedWorkflowOverlay.activeWorkItemId,
|
|
2196
|
-
)
|
|
2197
|
-
: undefined;
|
|
2198
|
-
return {
|
|
2199
|
-
overlayPresent: true,
|
|
2200
|
-
activeWorkItemId: normalizedWorkflowOverlay.activeWorkItemId ?? null,
|
|
2201
|
-
activeWorkItem: activeItem,
|
|
2202
|
-
scopes: normalizedWorkflowOverlay.scopes,
|
|
2203
|
-
candidates: normalizedWorkflowOverlay.candidates ?? [],
|
|
2204
|
-
blockedReasons,
|
|
2205
|
-
};
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
1984
|
function deriveHostAnnotationSnapshot(): HostAnnotationSnapshot {
|
|
2209
1985
|
return {
|
|
2210
1986
|
totalCount: hostAnnotationOverlay?.annotations.length ?? 0,
|
|
@@ -2212,189 +1988,13 @@ export function createDocumentRuntime(
|
|
|
2212
1988
|
};
|
|
2213
1989
|
}
|
|
2214
1990
|
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
function getEffectiveWorkflowScopes(overlay: WorkflowOverlay): WorkflowOverlay["scopes"] {
|
|
2223
|
-
const normalizedOverlay = normalizeWorkflowOverlayForDocument(state.document, overlay);
|
|
2224
|
-
const activeWorkItemId = normalizedOverlay.activeWorkItemId ?? null;
|
|
2225
|
-
const activeWorkItemScopeIds =
|
|
2226
|
-
activeWorkItemId === null
|
|
2227
|
-
? null
|
|
2228
|
-
: new Set(
|
|
2229
|
-
normalizedOverlay.workItems?.find((item) => item.workItemId === activeWorkItemId)?.scopeIds ?? [],
|
|
2230
|
-
);
|
|
2231
|
-
|
|
2232
|
-
return normalizedOverlay.scopes.filter((scope) => {
|
|
2233
|
-
const scopeStoryTarget = scope.storyTarget ?? MAIN_STORY_TARGET;
|
|
2234
|
-
if (!storyTargetsEqual(scopeStoryTarget, activeStory)) {
|
|
2235
|
-
return false;
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
if (activeWorkItemId === null) {
|
|
2239
|
-
return true;
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
return (
|
|
2243
|
-
scope.workItemId === activeWorkItemId ||
|
|
2244
|
-
activeWorkItemScopeIds?.has(scope.scopeId) === true
|
|
2245
|
-
);
|
|
2246
|
-
});
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
function getCachedInteractionGuardSnapshot(): InteractionGuardSnapshot {
|
|
2250
|
-
const activeStoryKey = storyTargetKey(activeStory);
|
|
2251
|
-
if (
|
|
2252
|
-
cachedInteractionGuardSnapshot &&
|
|
2253
|
-
cachedInteractionGuardSnapshot.revisionToken === state.revisionToken &&
|
|
2254
|
-
cachedInteractionGuardSnapshot.activeStoryKey === activeStoryKey &&
|
|
2255
|
-
cachedInteractionGuardSnapshot.selection === state.selection &&
|
|
2256
|
-
cachedInteractionGuardSnapshot.readOnly === state.readOnly &&
|
|
2257
|
-
cachedInteractionGuardSnapshot.documentMode === viewState.documentMode &&
|
|
2258
|
-
cachedInteractionGuardSnapshot.protectionSnapshot === protectionSnapshot &&
|
|
2259
|
-
cachedInteractionGuardSnapshot.workflowOverlay === workflowOverlay &&
|
|
2260
|
-
cachedInteractionGuardSnapshot.sharedWorkflowState === sharedWorkflowState
|
|
2261
|
-
) {
|
|
2262
|
-
return cachedInteractionGuardSnapshot.snapshot;
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
const blockedReasons = evaluateWorkflowBlockedReasons(state.selection);
|
|
2266
|
-
const matchingScope = getMatchingWorkflowScope(state.selection);
|
|
2267
|
-
const scopeStack = buildMatchingScopeStack(state.selection);
|
|
2268
|
-
const primaryBlockedReason = blockedReasons[0];
|
|
2269
|
-
const effectiveMode = primaryBlockedReason
|
|
2270
|
-
? (
|
|
2271
|
-
primaryBlockedReason.code === "workflow_comment_only"
|
|
2272
|
-
? "comment"
|
|
2273
|
-
: primaryBlockedReason.code === "workflow_view_only"
|
|
2274
|
-
? "view"
|
|
2275
|
-
: "blocked"
|
|
2276
|
-
)
|
|
2277
|
-
: getEffectiveDocumentMode(state.selection) === "suggesting"
|
|
2278
|
-
? "suggest"
|
|
2279
|
-
: matchingScope?.mode ?? "edit";
|
|
2280
|
-
const matchedScopeStack: InteractionGuardSnapshot["matchedScopeStack"] =
|
|
2281
|
-
scopeStack.length > 0
|
|
2282
|
-
? scopeStack.map((s) => ({
|
|
2283
|
-
scopeId: s.scopeId,
|
|
2284
|
-
mode: s.mode,
|
|
2285
|
-
visibility: s.visibility ?? "visible",
|
|
2286
|
-
}))
|
|
2287
|
-
: undefined;
|
|
2288
|
-
const snapshot: InteractionGuardSnapshot = {
|
|
2289
|
-
effectiveMode,
|
|
2290
|
-
...(matchingScope?.scopeId ? { matchedScopeId: matchingScope.scopeId } : {}),
|
|
2291
|
-
...(matchingScope?.mode ? { matchedScopeMode: matchingScope.mode } : {}),
|
|
2292
|
-
...(matchedScopeStack ? { matchedScopeStack } : {}),
|
|
2293
|
-
targetAccess:
|
|
2294
|
-
effectiveMode === "edit"
|
|
2295
|
-
? "direct-edit"
|
|
2296
|
-
: effectiveMode === "suggest"
|
|
2297
|
-
? "suggest"
|
|
2298
|
-
: effectiveMode === "comment"
|
|
2299
|
-
? "comment-only"
|
|
2300
|
-
: effectiveMode === "view"
|
|
2301
|
-
? "view-only"
|
|
2302
|
-
: "blocked",
|
|
2303
|
-
commandCapabilities: [
|
|
2304
|
-
{
|
|
2305
|
-
family: "text",
|
|
2306
|
-
supported: evaluateWorkflowBlockedReasons(state.selection, "text.insert").length === 0,
|
|
2307
|
-
blockedReasons: evaluateWorkflowBlockedReasons(state.selection, "text.insert"),
|
|
2308
|
-
},
|
|
2309
|
-
{
|
|
2310
|
-
family: "formatting",
|
|
2311
|
-
supported: evaluateWorkflowBlockedReasons(state.selection, "toggleBold").length === 0,
|
|
2312
|
-
blockedReasons: evaluateWorkflowBlockedReasons(state.selection, "toggleBold"),
|
|
2313
|
-
},
|
|
2314
|
-
{
|
|
2315
|
-
family: "structure",
|
|
2316
|
-
supported: evaluateWorkflowBlockedReasons(state.selection, "insertTable").length === 0,
|
|
2317
|
-
blockedReasons: evaluateWorkflowBlockedReasons(state.selection, "insertTable"),
|
|
2318
|
-
},
|
|
2319
|
-
],
|
|
2320
|
-
...(primaryBlockedReason ? { disabledReason: primaryBlockedReason.message } : {}),
|
|
2321
|
-
blockedReasons,
|
|
2322
|
-
};
|
|
2323
|
-
cachedInteractionGuardSnapshot = {
|
|
2324
|
-
revisionToken: state.revisionToken,
|
|
2325
|
-
activeStoryKey,
|
|
2326
|
-
selection: state.selection,
|
|
2327
|
-
readOnly: state.readOnly,
|
|
2328
|
-
documentMode: viewState.documentMode,
|
|
2329
|
-
protectionSnapshot,
|
|
2330
|
-
workflowOverlay,
|
|
2331
|
-
sharedWorkflowState,
|
|
2332
|
-
snapshot,
|
|
2333
|
-
};
|
|
2334
|
-
return snapshot;
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
function getCachedWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null {
|
|
2338
|
-
if (!workflowOverlay) {
|
|
2339
|
-
return null;
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
const interactionGuardSnapshot = getCachedInteractionGuardSnapshot();
|
|
2343
|
-
if (
|
|
2344
|
-
cachedWorkflowScopeSnapshot &&
|
|
2345
|
-
cachedWorkflowScopeSnapshot.workflowOverlay === workflowOverlay &&
|
|
2346
|
-
cachedWorkflowScopeSnapshot.interactionGuardSnapshot === interactionGuardSnapshot
|
|
2347
|
-
) {
|
|
2348
|
-
return cachedWorkflowScopeSnapshot.snapshot;
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
const snapshot = deriveWorkflowScopeSnapshot()!;
|
|
2352
|
-
cachedWorkflowScopeSnapshot = {
|
|
2353
|
-
workflowOverlay,
|
|
2354
|
-
interactionGuardSnapshot,
|
|
2355
|
-
snapshot,
|
|
2356
|
-
};
|
|
2357
|
-
return snapshot;
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
function getCachedWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot {
|
|
2361
|
-
const activeStoryKey = storyTargetKey(activeStory);
|
|
2362
|
-
if (
|
|
2363
|
-
cachedWorkflowMarkupSnapshot &&
|
|
2364
|
-
cachedWorkflowMarkupSnapshot.revisionToken === state.revisionToken &&
|
|
2365
|
-
cachedWorkflowMarkupSnapshot.activeStoryKey === activeStoryKey &&
|
|
2366
|
-
cachedWorkflowMarkupSnapshot.protectionSnapshot === protectionSnapshot &&
|
|
2367
|
-
cachedWorkflowMarkupSnapshot.preservation === state.document.preservation &&
|
|
2368
|
-
cachedWorkflowMarkupSnapshot.workflowOverlay === workflowOverlay &&
|
|
2369
|
-
cachedWorkflowMarkupSnapshot.workflowMetadataDefinitions === workflowMetadataDefinitions &&
|
|
2370
|
-
cachedWorkflowMarkupSnapshot.workflowMetadataEntries === workflowMetadataEntries
|
|
2371
|
-
) {
|
|
2372
|
-
return cachedWorkflowMarkupSnapshot.snapshot;
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
const snapshot = collectWorkflowMarkupSnapshot({
|
|
2376
|
-
renderSnapshot: cachedRenderSnapshot,
|
|
2377
|
-
fieldSnapshot: getCachedFieldSnapshot(state.document),
|
|
2378
|
-
protectionSnapshot,
|
|
2379
|
-
preservation: state.document.preservation,
|
|
2380
|
-
workflowMetadataSnapshot: deriveWorkflowMetadataSnapshot(),
|
|
2381
|
-
perfStage: (name, durationMs) => {
|
|
2382
|
-
perfCounters.increment(`wfMarkup.${name}.us`, Math.round(durationMs * 1000));
|
|
2383
|
-
perfCounters.increment(`wfMarkup.${name}.calls`);
|
|
2384
|
-
},
|
|
2385
|
-
});
|
|
2386
|
-
cachedWorkflowMarkupSnapshot = {
|
|
2387
|
-
revisionToken: state.revisionToken,
|
|
2388
|
-
activeStoryKey,
|
|
2389
|
-
protectionSnapshot,
|
|
2390
|
-
preservation: state.document.preservation,
|
|
2391
|
-
workflowOverlay,
|
|
2392
|
-
workflowMetadataDefinitions,
|
|
2393
|
-
workflowMetadataEntries,
|
|
2394
|
-
snapshot,
|
|
2395
|
-
};
|
|
2396
|
-
return snapshot;
|
|
2397
|
-
}
|
|
1991
|
+
// Layer-06 metadata snapshot derivation + getEffectiveWorkflowScopes
|
|
1992
|
+
// + cached interaction-guard / scope / markup snapshots live on the
|
|
1993
|
+
// workflow coordinator. Runtime call sites delegate via
|
|
1994
|
+
// `workflowCoordinator.getInteractionGuardSnapshot()`,
|
|
1995
|
+
// `workflowCoordinator.getWorkflowScopeSnapshot()`,
|
|
1996
|
+
// `workflowCoordinator.getWorkflowMarkupSnapshot()`, and
|
|
1997
|
+
// `workflowCoordinator.getWorkflowMetadataSnapshot()`.
|
|
2398
1998
|
|
|
2399
1999
|
function getCachedRuntimeContextAnalytics(
|
|
2400
2000
|
query?: RuntimeContextAnalyticsQuery,
|
|
@@ -2419,7 +2019,7 @@ export function createDocumentRuntime(
|
|
|
2419
2019
|
cachedEntry.selection === selectionCacheKey &&
|
|
2420
2020
|
cachedEntry.readOnly === state.readOnly &&
|
|
2421
2021
|
cachedEntry.documentMode === viewState.documentMode &&
|
|
2422
|
-
cachedEntry.workflowOverlay ===
|
|
2022
|
+
cachedEntry.workflowOverlay === overlayStore.getOverlay() &&
|
|
2423
2023
|
cachedEntry.protectionSnapshot === protectionSnapshot &&
|
|
2424
2024
|
cachedEntry.warnings === state.warnings &&
|
|
2425
2025
|
cachedEntry.fatalError === state.fatalError
|
|
@@ -2428,9 +2028,9 @@ export function createDocumentRuntime(
|
|
|
2428
2028
|
}
|
|
2429
2029
|
|
|
2430
2030
|
const tWf = performance.now();
|
|
2431
|
-
const wfScope =
|
|
2432
|
-
const wfGuard =
|
|
2433
|
-
const wfMarkup =
|
|
2031
|
+
const wfScope = workflowCoordinator.getWorkflowScopeSnapshot();
|
|
2032
|
+
const wfGuard = workflowCoordinator.getInteractionGuardSnapshot();
|
|
2033
|
+
const wfMarkup = workflowCoordinator.getWorkflowMarkupSnapshot();
|
|
2434
2034
|
perfCounters.increment("ctxa.workflow.us", Math.round((performance.now() - tWf) * 1000));
|
|
2435
2035
|
|
|
2436
2036
|
const tSugg = performance.now();
|
|
@@ -2485,7 +2085,7 @@ export function createDocumentRuntime(
|
|
|
2485
2085
|
const tCompat = performance.now();
|
|
2486
2086
|
const compat = toPublicCompatibilityReport(createDerivedCompatibility(state));
|
|
2487
2087
|
perfCounters.increment("ctxa.compat.us", Math.round((performance.now() - tCompat) * 1000));
|
|
2488
|
-
const normalizedWorkflowOverlay =
|
|
2088
|
+
const normalizedWorkflowOverlay = workflowCoordinator.getWorkflowOverlay();
|
|
2489
2089
|
|
|
2490
2090
|
const tBuild = performance.now();
|
|
2491
2091
|
const snapshot = createRuntimeContextAnalyticsSnapshot({
|
|
@@ -2509,7 +2109,7 @@ export function createDocumentRuntime(
|
|
|
2509
2109
|
selection: selectionCacheKey,
|
|
2510
2110
|
readOnly: state.readOnly,
|
|
2511
2111
|
documentMode: viewState.documentMode,
|
|
2512
|
-
workflowOverlay,
|
|
2112
|
+
workflowOverlay: overlayStore.getOverlay(),
|
|
2513
2113
|
protectionSnapshot,
|
|
2514
2114
|
warnings: state.warnings,
|
|
2515
2115
|
fatalError: state.fatalError,
|
|
@@ -2531,7 +2131,7 @@ export function createDocumentRuntime(
|
|
|
2531
2131
|
undefined,
|
|
2532
2132
|
{ scopeKind: "document" },
|
|
2533
2133
|
];
|
|
2534
|
-
const workflowScopeSnapshot =
|
|
2134
|
+
const workflowScopeSnapshot = workflowCoordinator.getWorkflowScopeSnapshot();
|
|
2535
2135
|
const seenScopeIds = new Set<string>();
|
|
2536
2136
|
for (const scope of workflowScopeSnapshot?.scopes ?? []) {
|
|
2537
2137
|
if (seenScopeIds.has(scope.scopeId)) {
|
|
@@ -2544,7 +2144,7 @@ export function createDocumentRuntime(
|
|
|
2544
2144
|
});
|
|
2545
2145
|
}
|
|
2546
2146
|
const seenWorkItemIds = new Set<string>();
|
|
2547
|
-
for (const workItem of
|
|
2147
|
+
for (const workItem of overlayStore.getOverlay()?.workItems ?? []) {
|
|
2548
2148
|
if (seenWorkItemIds.has(workItem.workItemId)) {
|
|
2549
2149
|
continue;
|
|
2550
2150
|
}
|
|
@@ -2602,9 +2202,12 @@ export function createDocumentRuntime(
|
|
|
2602
2202
|
}
|
|
2603
2203
|
|
|
2604
2204
|
function refreshRenderSnapshot(): RuntimeRenderSnapshot {
|
|
2205
|
+
emitStageToken(telemetryBus, "layout", "layout.refresh.start", {
|
|
2206
|
+
revisionToken: state.revisionToken,
|
|
2207
|
+
});
|
|
2605
2208
|
perfCounters.increment("refresh.all");
|
|
2606
2209
|
const surface = timeFacet("surface", () => getCachedSurface(state.document, activeStory));
|
|
2607
|
-
|
|
2210
|
+
const snapshot = {
|
|
2608
2211
|
documentId: state.documentId,
|
|
2609
2212
|
sessionId: state.sessionId,
|
|
2610
2213
|
sourceLabel: state.sourceLabel,
|
|
@@ -2631,6 +2234,10 @@ export function createDocumentRuntime(
|
|
|
2631
2234
|
protectionSnapshot,
|
|
2632
2235
|
grabbedObjectId: grabState.objectId,
|
|
2633
2236
|
};
|
|
2237
|
+
emitStageToken(telemetryBus, "layout", "layout.refresh.complete", {
|
|
2238
|
+
revisionToken: state.revisionToken,
|
|
2239
|
+
});
|
|
2240
|
+
return snapshot;
|
|
2634
2241
|
}
|
|
2635
2242
|
|
|
2636
2243
|
/**
|
|
@@ -2644,7 +2251,12 @@ export function createDocumentRuntime(
|
|
|
2644
2251
|
state.document,
|
|
2645
2252
|
state.selection,
|
|
2646
2253
|
activeStory,
|
|
2647
|
-
{
|
|
2254
|
+
{
|
|
2255
|
+
viewportBlockRange,
|
|
2256
|
+
...(effectiveMarkupModeProvider
|
|
2257
|
+
? { getEffectiveMarkupMode: effectiveMarkupModeProvider }
|
|
2258
|
+
: {}),
|
|
2259
|
+
},
|
|
2648
2260
|
);
|
|
2649
2261
|
// Refresh the cache with the just-built snapshot so a subsequent
|
|
2650
2262
|
// refreshRenderSnapshot (same revisionToken + activeStoryKey) hits cache.
|
|
@@ -2675,10 +2287,7 @@ export function createDocumentRuntime(
|
|
|
2675
2287
|
cachedPageLayout = undefined;
|
|
2676
2288
|
cachedNavigation = undefined;
|
|
2677
2289
|
cachedViewStateSnapshot = undefined;
|
|
2678
|
-
|
|
2679
|
-
cachedWorkflowScopeSnapshot = undefined;
|
|
2680
|
-
cachedNormalizedWorkflowOverlay = undefined;
|
|
2681
|
-
cachedWorkflowMarkupSnapshot = undefined;
|
|
2290
|
+
workflowCoordinator.invalidateCachesForDocumentMutation();
|
|
2682
2291
|
cachedFieldSnapshotEntry = null;
|
|
2683
2292
|
cachedContextAnalyticsSnapshots.clear();
|
|
2684
2293
|
lastEmittedContextAnalyticsSnapshots = undefined;
|
|
@@ -2820,6 +2429,11 @@ export function createDocumentRuntime(
|
|
|
2820
2429
|
const r5ScratchReplayState: typeof state = { ...state };
|
|
2821
2430
|
const r5ScratchReplaySnapshot: typeof cachedRenderSnapshot = { ...cachedRenderSnapshot };
|
|
2822
2431
|
|
|
2432
|
+
// `telemetryBus` is declared earlier (near `markerBackedScopeIds`) so
|
|
2433
|
+
// early-hoisted calls to `syncMarkerBackedScopeIds` can emit on it. This
|
|
2434
|
+
// is the facet consumed by `runtime.debug`.
|
|
2435
|
+
const debugFacet = createRuntimeDebugFacet(() => runtime, telemetryBus);
|
|
2436
|
+
|
|
2823
2437
|
const runtime: DocumentRuntime & {
|
|
2824
2438
|
hydrateCanonicalDocumentInternally(
|
|
2825
2439
|
document: CanonicalDocumentEnvelope,
|
|
@@ -2838,6 +2452,9 @@ export function createDocumentRuntime(
|
|
|
2838
2452
|
};
|
|
2839
2453
|
},
|
|
2840
2454
|
getRenderSnapshot() {
|
|
2455
|
+
emitStageToken(telemetryBus, "render", "render.snapshot.read", {
|
|
2456
|
+
revisionToken: cachedRenderSnapshot?.revisionToken ?? null,
|
|
2457
|
+
});
|
|
2841
2458
|
return cachedRenderSnapshot;
|
|
2842
2459
|
},
|
|
2843
2460
|
getCanonicalDocument() {
|
|
@@ -2872,7 +2489,7 @@ export function createDocumentRuntime(
|
|
|
2872
2489
|
dispatch(command) {
|
|
2873
2490
|
const commandSelection = getCommandSelection(command, state.selection);
|
|
2874
2491
|
if (isMutationCommand(command)) {
|
|
2875
|
-
const blockedReasons =
|
|
2492
|
+
const blockedReasons = workflowCoordinator.evaluateBlockedReasons(
|
|
2876
2493
|
commandSelection,
|
|
2877
2494
|
command.type,
|
|
2878
2495
|
);
|
|
@@ -2903,7 +2520,7 @@ export function createDocumentRuntime(
|
|
|
2903
2520
|
applyRuntimeStateOverlayCommand(command);
|
|
2904
2521
|
const context = {
|
|
2905
2522
|
timestamp: normalizeCommandTimestamp(command.origin?.timestamp) ?? clock(),
|
|
2906
|
-
documentMode: getEffectiveDocumentMode(commandSelection),
|
|
2523
|
+
documentMode: workflowCoordinator.getEffectiveDocumentMode(commandSelection),
|
|
2907
2524
|
defaultAuthorId: defaultAuthorId ?? undefined,
|
|
2908
2525
|
} as const;
|
|
2909
2526
|
const noopTransaction: EditorTransaction = {
|
|
@@ -2924,7 +2541,7 @@ export function createDocumentRuntime(
|
|
|
2924
2541
|
try {
|
|
2925
2542
|
const context = {
|
|
2926
2543
|
timestamp: normalizeCommandTimestamp(command.origin?.timestamp) ?? clock(),
|
|
2927
|
-
documentMode: getEffectiveDocumentMode(commandSelection),
|
|
2544
|
+
documentMode: workflowCoordinator.getEffectiveDocumentMode(commandSelection),
|
|
2928
2545
|
defaultAuthorId: defaultAuthorId ?? undefined,
|
|
2929
2546
|
renderSnapshot: cachedRenderSnapshot,
|
|
2930
2547
|
} as const;
|
|
@@ -3000,7 +2617,7 @@ export function createDocumentRuntime(
|
|
|
3000
2617
|
// at the same logical content. Do NOT adopt
|
|
3001
2618
|
// `transaction.nextState.selection` — that is the remote
|
|
3002
2619
|
// author's caret position, not B's. Without this, B would see
|
|
3003
|
-
// their own cursor jump whenever A types.
|
|
2620
|
+
// their own cursor jump whenever A types. (PR #243 fix.)
|
|
3004
2621
|
const mappedLocalSelection = mapLocalSelectionOnRemoteReplay(
|
|
3005
2622
|
state.selection,
|
|
3006
2623
|
transaction.mapping,
|
|
@@ -3038,7 +2655,7 @@ export function createDocumentRuntime(
|
|
|
3038
2655
|
// Running local selection: seeded from CURRENT local state, then
|
|
3039
2656
|
// mapped forward through each same-story envelope's mapping.
|
|
3040
2657
|
// Cross-story envelopes do not touch this value — B's caret
|
|
3041
|
-
// should never move from a story B isn't looking at.
|
|
2658
|
+
// should never move from a story B isn't looking at. (PR #243 fix.)
|
|
3042
2659
|
let runningLocalSelection: typeof state.selection = state.selection;
|
|
3043
2660
|
const warningsAdded: import("../core/state/editor-state.ts").EditorWarning[] = [];
|
|
3044
2661
|
const warningsCleared: Array<{ warningId: string; code: import("../core/state/editor-state.ts").EditorWarning["code"] }> = [];
|
|
@@ -3204,6 +2821,143 @@ export function createDocumentRuntime(
|
|
|
3204
2821
|
emitError(toRuntimeError(error));
|
|
3205
2822
|
}
|
|
3206
2823
|
},
|
|
2824
|
+
applyScopeReplacement(plan: RuntimeOperationPlan) {
|
|
2825
|
+
// Layer-08 Slice 5. Each step lowers to an existing command; the
|
|
2826
|
+
// apply pipeline (`src/runtime/scopes/replacement/apply.ts`) owns
|
|
2827
|
+
// the validation + audit emission around this call — keep the
|
|
2828
|
+
// implementation strictly mechanical.
|
|
2829
|
+
for (const step of plan.steps) {
|
|
2830
|
+
if (step.kind === "text-replace" && step.range && typeof step.text === "string") {
|
|
2831
|
+
const anchor: EditorAnchorProjection = {
|
|
2832
|
+
kind: "range",
|
|
2833
|
+
from: step.range.from,
|
|
2834
|
+
to: step.range.to,
|
|
2835
|
+
assoc: { start: -1, end: 1 },
|
|
2836
|
+
};
|
|
2837
|
+
const timestamp = clock();
|
|
2838
|
+
try {
|
|
2839
|
+
applyTextCommandInActiveStory(
|
|
2840
|
+
{
|
|
2841
|
+
type: "text.insert",
|
|
2842
|
+
text: step.text,
|
|
2843
|
+
origin: createOrigin("api", timestamp),
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
selection: createSelectionFromPublicAnchor(anchor),
|
|
2847
|
+
blockedCommandName: "applyScopeReplacement",
|
|
2848
|
+
},
|
|
2849
|
+
);
|
|
2850
|
+
} catch (error) {
|
|
2851
|
+
emitError(toRuntimeError(error));
|
|
2852
|
+
}
|
|
2853
|
+
} else if (
|
|
2854
|
+
step.kind === "fragment-replace" &&
|
|
2855
|
+
step.range &&
|
|
2856
|
+
step.fragment &&
|
|
2857
|
+
Array.isArray((step.fragment as { blocks?: unknown }).blocks)
|
|
2858
|
+
) {
|
|
2859
|
+
// Structured-fragment path (coord item 8 unblocked by L02
|
|
2860
|
+
// shipping CanonicalDocumentFragment, 2026-04-22).
|
|
2861
|
+
// Route through the existing fragment.insert command with
|
|
2862
|
+
// the block range as selection — runtime command handler
|
|
2863
|
+
// (src/core/commands/index.ts → applyFragmentInsert) replaces
|
|
2864
|
+
// the selected content with the fragment's blocks.
|
|
2865
|
+
const anchor: EditorAnchorProjection = {
|
|
2866
|
+
kind: "range",
|
|
2867
|
+
from: step.range.from,
|
|
2868
|
+
to: step.range.to,
|
|
2869
|
+
assoc: { start: -1, end: 1 },
|
|
2870
|
+
};
|
|
2871
|
+
const timestamp = clock();
|
|
2872
|
+
try {
|
|
2873
|
+
applyTextCommandInActiveStory(
|
|
2874
|
+
{
|
|
2875
|
+
type: "fragment.insert",
|
|
2876
|
+
fragment: step.fragment as CanonicalDocumentFragment,
|
|
2877
|
+
origin: createOrigin("api", timestamp),
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
selection: createSelectionFromPublicAnchor(anchor),
|
|
2881
|
+
blockedCommandName: "applyScopeReplacement",
|
|
2882
|
+
},
|
|
2883
|
+
);
|
|
2884
|
+
} catch (error) {
|
|
2885
|
+
emitError(toRuntimeError(error));
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
else if (
|
|
2889
|
+
step.kind === "text-insert-tracked" &&
|
|
2890
|
+
step.range &&
|
|
2891
|
+
typeof step.text === "string"
|
|
2892
|
+
) {
|
|
2893
|
+
// L08 coord-08 §2 — suggest-mode tracked-insert dispatch primitive.
|
|
2894
|
+
// The compiler emits `text-insert-tracked` only when apply posture
|
|
2895
|
+
// is `"suggest-mode"`; we force the command context's documentMode
|
|
2896
|
+
// to `"suggesting"` so `executeEditorCommand` routes the text.insert
|
|
2897
|
+
// through `applySuggestingInsert` (which produces the insertion
|
|
2898
|
+
// revision record) regardless of what the scope's auto-derived mode
|
|
2899
|
+
// would be. For replacement ranges (from !== to), the tracked
|
|
2900
|
+
// insert handler's non-collapsed branch emits a tracked deletion
|
|
2901
|
+
// for the existing range and a tracked insertion for the new text
|
|
2902
|
+
// in a single review-store update.
|
|
2903
|
+
const anchor: EditorAnchorProjection = {
|
|
2904
|
+
kind: "range",
|
|
2905
|
+
from: step.range.from,
|
|
2906
|
+
to: step.range.to,
|
|
2907
|
+
assoc: { start: -1, end: 1 },
|
|
2908
|
+
};
|
|
2909
|
+
const timestamp = clock();
|
|
2910
|
+
try {
|
|
2911
|
+
applyTextCommandInActiveStory(
|
|
2912
|
+
{
|
|
2913
|
+
type: "text.insert",
|
|
2914
|
+
text: step.text,
|
|
2915
|
+
origin: createOrigin("api", timestamp),
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
selection: createSelectionFromPublicAnchor(anchor),
|
|
2919
|
+
blockedCommandName: "applyScopeReplacement",
|
|
2920
|
+
documentModeOverride: "suggesting",
|
|
2921
|
+
},
|
|
2922
|
+
);
|
|
2923
|
+
} catch (error) {
|
|
2924
|
+
emitError(toRuntimeError(error));
|
|
2925
|
+
}
|
|
2926
|
+
} else if (
|
|
2927
|
+
step.kind === "text-delete-tracked" &&
|
|
2928
|
+
step.range &&
|
|
2929
|
+
step.range.to > step.range.from
|
|
2930
|
+
) {
|
|
2931
|
+
// L08 coord-08 §2 — tracked deletion. Non-collapsed delete-forward
|
|
2932
|
+
// with documentMode override routes into `applySuggestingDelete`,
|
|
2933
|
+
// which appends a tracked deletion revision over the range without
|
|
2934
|
+
// removing the content (matching the ledger-ish invariant of
|
|
2935
|
+
// suggesting mode: accept → gone, reject → stays).
|
|
2936
|
+
const anchor: EditorAnchorProjection = {
|
|
2937
|
+
kind: "range",
|
|
2938
|
+
from: step.range.from,
|
|
2939
|
+
to: step.range.to,
|
|
2940
|
+
assoc: { start: -1, end: 1 },
|
|
2941
|
+
};
|
|
2942
|
+
const timestamp = clock();
|
|
2943
|
+
try {
|
|
2944
|
+
applyTextCommandInActiveStory(
|
|
2945
|
+
{
|
|
2946
|
+
type: "text.delete-forward",
|
|
2947
|
+
origin: createOrigin("api", timestamp),
|
|
2948
|
+
},
|
|
2949
|
+
{
|
|
2950
|
+
selection: createSelectionFromPublicAnchor(anchor),
|
|
2951
|
+
blockedCommandName: "applyScopeReplacement",
|
|
2952
|
+
documentModeOverride: "suggesting",
|
|
2953
|
+
},
|
|
2954
|
+
);
|
|
2955
|
+
} catch (error) {
|
|
2956
|
+
emitError(toRuntimeError(error));
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
3207
2961
|
insertFragment(fragment, target) {
|
|
3208
2962
|
// I2 Tier B Slice 1 — dispatch `fragment.insert` against the active story. The
|
|
3209
2963
|
// runtime command handler routes into `applyFragmentInsert` (structure-ops).
|
|
@@ -3526,196 +3280,36 @@ export function createDocumentRuntime(
|
|
|
3526
3280
|
origin: createOrigin("api", clock()),
|
|
3527
3281
|
});
|
|
3528
3282
|
},
|
|
3529
|
-
addScope(params)
|
|
3530
|
-
|
|
3531
|
-
params.scopeId ??
|
|
3532
|
-
`scope-${clock().replace(/[^0-9]/gu, "")}-${Math.floor(Math.random() * 1e6)}`;
|
|
3533
|
-
const anchor =
|
|
3534
|
-
params.anchor.kind === "range"
|
|
3535
|
-
? { from: params.anchor.from, to: params.anchor.to }
|
|
3536
|
-
: null;
|
|
3537
|
-
|
|
3538
|
-
if (!anchor) {
|
|
3539
|
-
return {
|
|
3540
|
-
scopeId,
|
|
3541
|
-
anchor: params.anchor,
|
|
3542
|
-
};
|
|
3543
|
-
}
|
|
3544
|
-
|
|
3545
|
-
const { document: nextDocument } = insertScopeMarkers(state.document, {
|
|
3546
|
-
scopeId,
|
|
3547
|
-
from: anchor.from,
|
|
3548
|
-
to: anchor.to,
|
|
3549
|
-
});
|
|
3550
|
-
|
|
3551
|
-
if (nextDocument !== state.document) {
|
|
3552
|
-
this.dispatch({
|
|
3553
|
-
type: "document.replace",
|
|
3554
|
-
document: nextDocument,
|
|
3555
|
-
origin: createOrigin("api", clock()),
|
|
3556
|
-
});
|
|
3557
|
-
}
|
|
3558
|
-
|
|
3559
|
-
const resolved = resolveScope(nextDocument, scopeId);
|
|
3560
|
-
const publicAnchor: EditorAnchorProjection =
|
|
3561
|
-
resolved && resolved.kind === "range"
|
|
3562
|
-
? resolved
|
|
3563
|
-
: {
|
|
3564
|
-
kind: "range",
|
|
3565
|
-
from: anchor.from,
|
|
3566
|
-
to: anchor.to,
|
|
3567
|
-
assoc: { start: -1, end: 1 },
|
|
3568
|
-
};
|
|
3569
|
-
|
|
3570
|
-
const currentOverlay: WorkflowOverlay = workflowOverlay ?? {
|
|
3571
|
-
overlayVersion: "workflow-overlay/1",
|
|
3572
|
-
scopes: [],
|
|
3573
|
-
};
|
|
3574
|
-
const existingScopes = currentOverlay.scopes.filter(
|
|
3575
|
-
(existing) => existing.scopeId !== scopeId,
|
|
3576
|
-
);
|
|
3577
|
-
const scope: WorkflowScope = {
|
|
3578
|
-
scopeId,
|
|
3579
|
-
mode: params.mode ?? "comment",
|
|
3580
|
-
anchor: publicAnchor,
|
|
3581
|
-
...(params.storyTarget ? { storyTarget: params.storyTarget } : {}),
|
|
3582
|
-
...(params.label ? { label: params.label } : {}),
|
|
3583
|
-
};
|
|
3584
|
-
this.dispatch({
|
|
3585
|
-
type: "workflow.set-overlay",
|
|
3586
|
-
overlay: {
|
|
3587
|
-
...currentOverlay,
|
|
3588
|
-
scopes: [...existingScopes, scope],
|
|
3589
|
-
},
|
|
3590
|
-
origin: createOrigin("api", clock()),
|
|
3591
|
-
});
|
|
3592
|
-
|
|
3593
|
-
if (params.persistence && params.persistence !== "runtime-only") {
|
|
3594
|
-
const requestedMetadata = params.metadata ?? {};
|
|
3595
|
-
const entryPersistence =
|
|
3596
|
-
requestedMetadata.metadataPersistence ??
|
|
3597
|
-
(params.persistence === "session" ? "external" : "internal");
|
|
3598
|
-
const entry: WorkflowMetadataEntry = {
|
|
3599
|
-
entryId: requestedMetadata.entryId ?? `scope-metadata-${scopeId}`,
|
|
3600
|
-
metadataId: requestedMetadata.metadataId ?? "workflow.scope",
|
|
3601
|
-
anchor: publicAnchor,
|
|
3602
|
-
...(params.storyTarget ? { storyTarget: params.storyTarget } : {}),
|
|
3603
|
-
scopeId,
|
|
3604
|
-
...(requestedMetadata.workItemId ? { workItemId: requestedMetadata.workItemId } : {}),
|
|
3605
|
-
...(requestedMetadata.value !== undefined
|
|
3606
|
-
? { value: requestedMetadata.value }
|
|
3607
|
-
: params.persistence === "document-metadata" && params.label
|
|
3608
|
-
? { value: { label: params.label } }
|
|
3609
|
-
: {}),
|
|
3610
|
-
metadataPersistence: entryPersistence,
|
|
3611
|
-
...(requestedMetadata.storageRef !== undefined
|
|
3612
|
-
? { storageRef: requestedMetadata.storageRef }
|
|
3613
|
-
: {}),
|
|
3614
|
-
...(requestedMetadata.metadataVersion !== undefined
|
|
3615
|
-
? { metadataVersion: requestedMetadata.metadataVersion }
|
|
3616
|
-
: {}),
|
|
3617
|
-
};
|
|
3618
|
-
this.dispatch({
|
|
3619
|
-
type: "workflow.set-metadata-entries",
|
|
3620
|
-
entries: [...(workflowMetadataEntries ?? []), entry],
|
|
3621
|
-
origin: createOrigin("api", clock()),
|
|
3622
|
-
});
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3625
|
-
return {
|
|
3626
|
-
scopeId,
|
|
3627
|
-
anchor: publicAnchor,
|
|
3628
|
-
};
|
|
3283
|
+
addScope(params) {
|
|
3284
|
+
return workflowCoordinator.addScope(params);
|
|
3629
3285
|
},
|
|
3630
3286
|
getScope(scopeId) {
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
return normalizedScope;
|
|
3636
|
-
}
|
|
3637
|
-
const resolved = resolveScope(state.document, scopeId);
|
|
3638
|
-
if (!resolved) {
|
|
3639
|
-
return null;
|
|
3640
|
-
}
|
|
3641
|
-
return {
|
|
3642
|
-
scopeId,
|
|
3643
|
-
mode: "comment",
|
|
3644
|
-
anchor: resolved,
|
|
3645
|
-
};
|
|
3287
|
+
return workflowCoordinator.getScope(scopeId);
|
|
3288
|
+
},
|
|
3289
|
+
compileScopeBundleById(scopeId, nowUtc) {
|
|
3290
|
+
return createScopeCompilerService(runtime).compileBundleById(scopeId, nowUtc);
|
|
3646
3291
|
},
|
|
3292
|
+
getMarkerBackedScopeIds() {
|
|
3293
|
+
return workflowCoordinator.getMarkerBackedScopeIds();
|
|
3294
|
+
},
|
|
3295
|
+
debug: debugFacet as RuntimeDebugFacet,
|
|
3647
3296
|
removeScope(scopeId) {
|
|
3648
|
-
|
|
3649
|
-
// "comment" / "view" the workflow-blocked-reasons gate in `dispatch`
|
|
3650
|
-
// would otherwise refuse the subsequent `document.replace` with
|
|
3651
|
-
// `workflow_comment_only` / `workflow_view_only`. Overlay commands are
|
|
3652
|
-
// routed through `applyRuntimeStateOverlayCommand` and bypass that gate.
|
|
3653
|
-
if (workflowOverlay) {
|
|
3654
|
-
const nextScopes = workflowOverlay.scopes.filter(
|
|
3655
|
-
(scope) => scope.scopeId !== scopeId,
|
|
3656
|
-
);
|
|
3657
|
-
if (nextScopes.length !== workflowOverlay.scopes.length) {
|
|
3658
|
-
this.dispatch({
|
|
3659
|
-
type: "workflow.set-overlay",
|
|
3660
|
-
overlay: { ...workflowOverlay, scopes: nextScopes },
|
|
3661
|
-
origin: createOrigin("api", clock()),
|
|
3662
|
-
});
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
// Step 2: now that the scope is gone, strip the markers from the doc.
|
|
3666
|
-
const nextDocument = removeScopeMarkers(state.document, scopeId);
|
|
3667
|
-
if (nextDocument !== state.document) {
|
|
3668
|
-
this.dispatch({
|
|
3669
|
-
type: "document.replace",
|
|
3670
|
-
document: nextDocument,
|
|
3671
|
-
origin: createOrigin("api", clock()),
|
|
3672
|
-
});
|
|
3673
|
-
}
|
|
3674
|
-
// Step 3: clear any customXml-persisted metadata entries.
|
|
3675
|
-
if (workflowMetadataEntries) {
|
|
3676
|
-
const nextEntries = workflowMetadataEntries.filter(
|
|
3677
|
-
(entry) => entry.scopeId !== scopeId,
|
|
3678
|
-
);
|
|
3679
|
-
if (nextEntries.length !== workflowMetadataEntries.length) {
|
|
3680
|
-
this.dispatch({
|
|
3681
|
-
type: "workflow.set-metadata-entries",
|
|
3682
|
-
entries: nextEntries,
|
|
3683
|
-
origin: createOrigin("api", clock()),
|
|
3684
|
-
});
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3297
|
+
workflowCoordinator.removeScope(scopeId);
|
|
3687
3298
|
},
|
|
3688
3299
|
addInvisibleScope(params) {
|
|
3689
|
-
|
|
3690
|
-
...params,
|
|
3691
|
-
mode: params.mode ?? "comment",
|
|
3692
|
-
});
|
|
3693
|
-
this.setScopeVisibility(result.scopeId, "invisible");
|
|
3694
|
-
return result;
|
|
3300
|
+
return workflowCoordinator.addInvisibleScope(params);
|
|
3695
3301
|
},
|
|
3696
3302
|
setScopeVisibility(scopeId, visibility) {
|
|
3697
|
-
|
|
3698
|
-
const idx = workflowOverlay.scopes.findIndex((s) => s.scopeId === scopeId);
|
|
3699
|
-
if (idx === -1) return;
|
|
3700
|
-
const nextScopes = workflowOverlay.scopes.map((s) =>
|
|
3701
|
-
s.scopeId === scopeId ? { ...s, visibility } : s,
|
|
3702
|
-
);
|
|
3703
|
-
this.dispatch({
|
|
3704
|
-
type: "workflow.set-overlay",
|
|
3705
|
-
overlay: { ...workflowOverlay, scopes: nextScopes },
|
|
3706
|
-
origin: createOrigin("api", clock()),
|
|
3707
|
-
});
|
|
3303
|
+
workflowCoordinator.setScopeVisibility(scopeId, visibility);
|
|
3708
3304
|
},
|
|
3709
|
-
getScopeVisibility(scopeId)
|
|
3710
|
-
|
|
3711
|
-
const scope = workflowOverlay.scopes.find((s) => s.scopeId === scopeId);
|
|
3712
|
-
return scope?.visibility ?? "visible";
|
|
3305
|
+
getScopeVisibility(scopeId) {
|
|
3306
|
+
return workflowCoordinator.getScopeVisibility(scopeId);
|
|
3713
3307
|
},
|
|
3714
|
-
setScopeChromeVisibility(
|
|
3715
|
-
|
|
3308
|
+
setScopeChromeVisibility(chromeVisibility) {
|
|
3309
|
+
workflowCoordinator.setScopeChromeVisibility(chromeVisibility);
|
|
3716
3310
|
},
|
|
3717
|
-
getScopeChromeVisibility()
|
|
3718
|
-
return
|
|
3311
|
+
getScopeChromeVisibility() {
|
|
3312
|
+
return workflowCoordinator.getScopeChromeVisibility();
|
|
3719
3313
|
},
|
|
3720
3314
|
acceptChange(changeId) {
|
|
3721
3315
|
this.dispatch({
|
|
@@ -3808,7 +3402,7 @@ export function createDocumentRuntime(
|
|
|
3808
3402
|
};
|
|
3809
3403
|
|
|
3810
3404
|
const suggesting =
|
|
3811
|
-
getEffectiveDocumentMode(state.selection) === "suggesting";
|
|
3405
|
+
workflowCoordinator.getEffectiveDocumentMode(state.selection) === "suggesting";
|
|
3812
3406
|
if (suggesting) {
|
|
3813
3407
|
if (activeStory.kind !== "main") {
|
|
3814
3408
|
this.emitBlockedCommand("clearHighlight", [{
|
|
@@ -3980,7 +3574,7 @@ export function createDocumentRuntime(
|
|
|
3980
3574
|
bookmarkMap,
|
|
3981
3575
|
paragraphOffsets,
|
|
3982
3576
|
styles: state.document.styles,
|
|
3983
|
-
contentRoot: state.document.content as unknown as import("./field
|
|
3577
|
+
contentRoot: state.document.content as unknown as import("./formatting/field/resolver.ts").DocumentContainerNode,
|
|
3984
3578
|
});
|
|
3985
3579
|
},
|
|
3986
3580
|
getFootnoteResolver(): FootnoteResolver | undefined {
|
|
@@ -3994,6 +3588,21 @@ export function createDocumentRuntime(
|
|
|
3994
3588
|
);
|
|
3995
3589
|
},
|
|
3996
3590
|
layout: layoutFacet,
|
|
3591
|
+
workflow: {
|
|
3592
|
+
getRailSegments(pageIndex: number) {
|
|
3593
|
+
return workflowCoordinator.getRailSegments(pageIndex);
|
|
3594
|
+
},
|
|
3595
|
+
getAllRailSegments() {
|
|
3596
|
+
return workflowCoordinator.getAllRailSegments();
|
|
3597
|
+
},
|
|
3598
|
+
getAllScopeCardModels() {
|
|
3599
|
+
return workflowCoordinator.getAllScopeCardModels();
|
|
3600
|
+
},
|
|
3601
|
+
},
|
|
3602
|
+
geometry: geometryFacet,
|
|
3603
|
+
getLayoutFacet() {
|
|
3604
|
+
return layoutFacet;
|
|
3605
|
+
},
|
|
3997
3606
|
getCurrentLocation() {
|
|
3998
3607
|
const navigation = getCachedDocumentNavigationSnapshot(state, activeStory);
|
|
3999
3608
|
return createCurrentLocation({
|
|
@@ -4179,7 +3788,9 @@ export function createDocumentRuntime(
|
|
|
4179
3788
|
},
|
|
4180
3789
|
getSessionState() {
|
|
4181
3790
|
const compatibility = createDerivedCompatibility(state);
|
|
4182
|
-
const normalizedWorkflowOverlay =
|
|
3791
|
+
const normalizedWorkflowOverlay = workflowCoordinator.getWorkflowOverlay();
|
|
3792
|
+
const visibilityPolicies = workflowCoordinator.getVisibilityPolicies();
|
|
3793
|
+
const markupModePolicy = workflowCoordinator.getMarkupModePolicy();
|
|
4183
3794
|
return editorSessionStateFromPersistedSnapshot(
|
|
4184
3795
|
{
|
|
4185
3796
|
...(createPersistedEditorSnapshot(state, {
|
|
@@ -4189,7 +3800,9 @@ export function createDocumentRuntime(
|
|
|
4189
3800
|
protectionSnapshot,
|
|
4190
3801
|
}) as unknown as PersistedEditorSnapshot),
|
|
4191
3802
|
workflowOverlay: normalizedWorkflowOverlay ?? undefined,
|
|
4192
|
-
workflowMetadata:
|
|
3803
|
+
workflowMetadata: workflowCoordinator.getWorkflowMetadataSnapshot(),
|
|
3804
|
+
...(visibilityPolicies.length > 0 ? { visibilityPolicies } : {}),
|
|
3805
|
+
...(markupModePolicy ? { markupModePolicy } : {}),
|
|
4193
3806
|
},
|
|
4194
3807
|
);
|
|
4195
3808
|
},
|
|
@@ -4234,17 +3847,25 @@ export function createDocumentRuntime(
|
|
|
4234
3847
|
return { schemaVersion: "host-annotation-overlay/1", data: snap };
|
|
4235
3848
|
}
|
|
4236
3849
|
case "workflowOverlay": {
|
|
4237
|
-
const ov =
|
|
3850
|
+
const ov = workflowCoordinator.getWorkflowOverlay();
|
|
4238
3851
|
if (!ov) return null;
|
|
4239
3852
|
return { schemaVersion: "workflow-overlay/1", data: ov };
|
|
4240
3853
|
}
|
|
4241
3854
|
case "workflowMetadata": {
|
|
4242
|
-
const meta =
|
|
3855
|
+
const meta = workflowCoordinator.getWorkflowMetadataSnapshot();
|
|
4243
3856
|
if (!meta || (meta.definitions.length === 0 && meta.entries.length === 0)) return null;
|
|
4244
3857
|
return { schemaVersion: "workflow-metadata/1", data: meta };
|
|
4245
3858
|
}
|
|
4246
3859
|
case "workItems":
|
|
4247
3860
|
return null;
|
|
3861
|
+
case "embeddings":
|
|
3862
|
+
// P8 Step 7: the session layer owns the `embeddings`
|
|
3863
|
+
// namespace (load-time offload + export-time
|
|
3864
|
+
// reconstitute) via
|
|
3865
|
+
// `src/session/import/embedded-offload.ts` +
|
|
3866
|
+
// `src/session/export/embedded-reconstitute.ts`.
|
|
3867
|
+
// The runtime has no per-doc state to serialize here.
|
|
3868
|
+
return null;
|
|
4248
3869
|
}
|
|
4249
3870
|
},
|
|
4250
3871
|
});
|
|
@@ -4253,7 +3874,37 @@ export function createDocumentRuntime(
|
|
|
4253
3874
|
_editorState: collectedEditorState,
|
|
4254
3875
|
};
|
|
4255
3876
|
|
|
4256
|
-
|
|
3877
|
+
// Phase 1 io + serialize channels — bracket the exporter call so the
|
|
3878
|
+
// `serialize.main_document.completed` emit runs with this runtime's bus.
|
|
3879
|
+
setActiveSerializeTelemetryBus(telemetryBus);
|
|
3880
|
+
let exportStart = 0;
|
|
3881
|
+
if (telemetryBus.isEnabled("io")) {
|
|
3882
|
+
exportStart = performance.now();
|
|
3883
|
+
telemetryBus.emit({
|
|
3884
|
+
channel: "io",
|
|
3885
|
+
type: "io.export.start",
|
|
3886
|
+
t: 0,
|
|
3887
|
+
payload: { documentId: state.documentId },
|
|
3888
|
+
});
|
|
3889
|
+
}
|
|
3890
|
+
let result: Awaited<ReturnType<typeof options.exportDocx>>;
|
|
3891
|
+
try {
|
|
3892
|
+
result = await options.exportDocx(this.getSessionState(), internalOptions);
|
|
3893
|
+
} finally {
|
|
3894
|
+
setActiveSerializeTelemetryBus(undefined);
|
|
3895
|
+
}
|
|
3896
|
+
if (telemetryBus.isEnabled("io")) {
|
|
3897
|
+
telemetryBus.emit({
|
|
3898
|
+
channel: "io",
|
|
3899
|
+
type: "io.export.completed",
|
|
3900
|
+
t: 0,
|
|
3901
|
+
payload: {
|
|
3902
|
+
documentId: state.documentId,
|
|
3903
|
+
bytes: result.bytes?.byteLength ?? 0,
|
|
3904
|
+
ms: performance.now() - exportStart,
|
|
3905
|
+
},
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
4257
3908
|
|
|
4258
3909
|
emit({
|
|
4259
3910
|
type: "export_completed",
|
|
@@ -4264,87 +3915,81 @@ export function createDocumentRuntime(
|
|
|
4264
3915
|
return result;
|
|
4265
3916
|
},
|
|
4266
3917
|
setWorkflowOverlay(overlay) {
|
|
4267
|
-
|
|
4268
|
-
type: "workflow.set-overlay",
|
|
4269
|
-
overlay,
|
|
4270
|
-
origin: createOrigin("api", clock()),
|
|
4271
|
-
});
|
|
4272
|
-
const normalizedWorkflowOverlay = getNormalizedWorkflowOverlay();
|
|
4273
|
-
editorStateChannel.recordMutation("workflowOverlay", {
|
|
4274
|
-
namespace: "workflowOverlay",
|
|
4275
|
-
schemaVersion: "workflow-overlay/1",
|
|
4276
|
-
data: normalizedWorkflowOverlay ?? overlay,
|
|
4277
|
-
});
|
|
3918
|
+
workflowCoordinator.setWorkflowOverlay(overlay);
|
|
4278
3919
|
},
|
|
4279
3920
|
clearWorkflowOverlay() {
|
|
4280
|
-
|
|
4281
|
-
type: "workflow.clear-overlay",
|
|
4282
|
-
origin: createOrigin("api", clock()),
|
|
4283
|
-
});
|
|
3921
|
+
workflowCoordinator.clearWorkflowOverlay();
|
|
4284
3922
|
},
|
|
4285
3923
|
getWorkflowOverlay() {
|
|
4286
|
-
return
|
|
3924
|
+
return workflowCoordinator.getWorkflowOverlay();
|
|
4287
3925
|
},
|
|
4288
|
-
setSharedWorkflowState(
|
|
4289
|
-
|
|
4290
|
-
sharedWorkflowState = state;
|
|
4291
|
-
// Invalidate guard/scope caches so next derivation reflects the new state.
|
|
4292
|
-
cachedInteractionGuardSnapshot = undefined;
|
|
4293
|
-
cachedWorkflowScopeSnapshot = undefined;
|
|
3926
|
+
setSharedWorkflowState(sharedState) {
|
|
3927
|
+
workflowCoordinator.setSharedWorkflowState(sharedState);
|
|
4294
3928
|
},
|
|
4295
3929
|
getWorkflowScopeSnapshot() {
|
|
4296
|
-
return
|
|
3930
|
+
return workflowCoordinator.getWorkflowScopeSnapshot();
|
|
4297
3931
|
},
|
|
4298
3932
|
getInteractionGuardSnapshot() {
|
|
4299
|
-
return
|
|
3933
|
+
return workflowCoordinator.getInteractionGuardSnapshot();
|
|
4300
3934
|
},
|
|
4301
3935
|
getWorkflowMarkupSnapshot() {
|
|
4302
|
-
return
|
|
3936
|
+
return workflowCoordinator.getWorkflowMarkupSnapshot();
|
|
4303
3937
|
},
|
|
4304
3938
|
setWorkflowMetadataDefinitions(definitions) {
|
|
4305
|
-
|
|
4306
|
-
type: "workflow.set-metadata-definitions",
|
|
4307
|
-
definitions,
|
|
4308
|
-
origin: createOrigin("api", clock()),
|
|
4309
|
-
});
|
|
3939
|
+
workflowCoordinator.setWorkflowMetadataDefinitions(definitions);
|
|
4310
3940
|
},
|
|
4311
3941
|
clearWorkflowMetadataDefinitions() {
|
|
4312
|
-
|
|
4313
|
-
type: "workflow.clear-metadata-definitions",
|
|
4314
|
-
origin: createOrigin("api", clock()),
|
|
4315
|
-
});
|
|
3942
|
+
workflowCoordinator.clearWorkflowMetadataDefinitions();
|
|
4316
3943
|
},
|
|
4317
3944
|
setWorkflowMetadataEntries(entries) {
|
|
4318
|
-
|
|
4319
|
-
type: "workflow.set-metadata-entries",
|
|
4320
|
-
entries,
|
|
4321
|
-
origin: createOrigin("api", clock()),
|
|
4322
|
-
});
|
|
4323
|
-
editorStateChannel.recordMutation("workflowMetadata", {
|
|
4324
|
-
namespace: "workflowMetadata",
|
|
4325
|
-
schemaVersion: "workflow-metadata/1",
|
|
4326
|
-
data: entries,
|
|
4327
|
-
});
|
|
3945
|
+
workflowCoordinator.setWorkflowMetadataEntries(entries);
|
|
4328
3946
|
},
|
|
4329
3947
|
clearWorkflowMetadataEntries() {
|
|
4330
|
-
|
|
4331
|
-
type: "workflow.clear-metadata-entries",
|
|
4332
|
-
origin: createOrigin("api", clock()),
|
|
4333
|
-
});
|
|
3948
|
+
workflowCoordinator.clearWorkflowMetadataEntries();
|
|
4334
3949
|
},
|
|
4335
3950
|
getWorkflowMetadataSnapshot() {
|
|
4336
|
-
return
|
|
3951
|
+
return workflowCoordinator.getWorkflowMetadataSnapshot();
|
|
3952
|
+
},
|
|
3953
|
+
getVisibilityPolicy(kind) {
|
|
3954
|
+
return workflowCoordinator.getVisibilityPolicy(kind);
|
|
3955
|
+
},
|
|
3956
|
+
getVisibilityPolicies() {
|
|
3957
|
+
return workflowCoordinator.getVisibilityPolicies();
|
|
3958
|
+
},
|
|
3959
|
+
setVisibilityPolicy(policy) {
|
|
3960
|
+
return workflowCoordinator.setVisibilityPolicy(policy);
|
|
3961
|
+
},
|
|
3962
|
+
clearVisibilityPolicy(kind) {
|
|
3963
|
+
return workflowCoordinator.clearVisibilityPolicy(kind);
|
|
3964
|
+
},
|
|
3965
|
+
subscribeVisibilityPolicy(listener) {
|
|
3966
|
+
return workflowCoordinator.subscribeVisibilityPolicy(listener);
|
|
3967
|
+
},
|
|
3968
|
+
getMarkupModePolicy() {
|
|
3969
|
+
return workflowCoordinator.getMarkupModePolicy();
|
|
3970
|
+
},
|
|
3971
|
+
setMarkupModePolicy(policy) {
|
|
3972
|
+
return workflowCoordinator.setMarkupModePolicy(policy);
|
|
3973
|
+
},
|
|
3974
|
+
subscribeMarkupModePolicy(listener) {
|
|
3975
|
+
return workflowCoordinator.subscribeMarkupModePolicy(listener);
|
|
3976
|
+
},
|
|
3977
|
+
setEffectiveMarkupModeProvider(provider) {
|
|
3978
|
+
effectiveMarkupModeProvider = provider ?? undefined;
|
|
3979
|
+
// Re-project the surface so `cachedRenderSnapshot` reflects the
|
|
3980
|
+
// new mode on the next `getRenderSnapshot()`. `refreshSurfaceOnly`
|
|
3981
|
+
// also notifies listeners — React trees that have subscribed
|
|
3982
|
+
// re-read synchronously.
|
|
3983
|
+
refreshSurfaceOnly();
|
|
3984
|
+
},
|
|
3985
|
+
invalidateForMarkupModeChange() {
|
|
3986
|
+
// Fast path for class-C local-preference flips. Provider is
|
|
3987
|
+
// stable; cache invalidation is what propagates the new value.
|
|
3988
|
+
// `refreshSurfaceOnly` notifies listeners as part of its work.
|
|
3989
|
+
refreshSurfaceOnly();
|
|
4337
3990
|
},
|
|
4338
3991
|
queryScopes(filter) {
|
|
4339
|
-
return
|
|
4340
|
-
{
|
|
4341
|
-
overlay: workflowOverlay,
|
|
4342
|
-
entries: workflowMetadataEntries,
|
|
4343
|
-
document: state.document,
|
|
4344
|
-
markerBackedScopeIds,
|
|
4345
|
-
},
|
|
4346
|
-
filter,
|
|
4347
|
-
);
|
|
3992
|
+
return workflowCoordinator.queryScopes(filter);
|
|
4348
3993
|
},
|
|
4349
3994
|
subscribeToScopeQuery(filter, callback) {
|
|
4350
3995
|
const buildAnchorKey = (anchor: EditorAnchorProjection): string => {
|
|
@@ -4444,10 +4089,10 @@ export function createDocumentRuntime(
|
|
|
4444
4089
|
const hits = findAllScopesAt(state.document, pos);
|
|
4445
4090
|
return projectScopeQueryResults(
|
|
4446
4091
|
{
|
|
4447
|
-
overlay:
|
|
4448
|
-
entries:
|
|
4092
|
+
overlay: overlayStore.getOverlay(),
|
|
4093
|
+
entries: overlayStore.getMetadataEntries(),
|
|
4449
4094
|
document: state.document,
|
|
4450
|
-
markerBackedScopeIds,
|
|
4095
|
+
markerBackedScopeIds: overlayStore.getMarkerBackedScopeIds(),
|
|
4451
4096
|
},
|
|
4452
4097
|
hits.map((h) => h.scopeId),
|
|
4453
4098
|
options,
|
|
@@ -4458,10 +4103,10 @@ export function createDocumentRuntime(
|
|
|
4458
4103
|
const hits = findScopesIntersecting(state.document, range.from, range.to, options?.mode);
|
|
4459
4104
|
return projectScopeQueryResults(
|
|
4460
4105
|
{
|
|
4461
|
-
overlay:
|
|
4462
|
-
entries:
|
|
4106
|
+
overlay: overlayStore.getOverlay(),
|
|
4107
|
+
entries: overlayStore.getMetadataEntries(),
|
|
4463
4108
|
document: state.document,
|
|
4464
|
-
markerBackedScopeIds,
|
|
4109
|
+
markerBackedScopeIds: overlayStore.getMarkerBackedScopeIds(),
|
|
4465
4110
|
},
|
|
4466
4111
|
hits.map((h) => h.scopeId),
|
|
4467
4112
|
options,
|
|
@@ -4530,14 +4175,14 @@ export function createDocumentRuntime(
|
|
|
4530
4175
|
return createWorkflowChunks({
|
|
4531
4176
|
document: state.document,
|
|
4532
4177
|
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
4533
|
-
workflowMarkup:
|
|
4178
|
+
workflowMarkup: workflowCoordinator.getWorkflowMarkupSnapshot(),
|
|
4534
4179
|
});
|
|
4535
4180
|
},
|
|
4536
4181
|
getReviewWorkSnapshot() {
|
|
4537
4182
|
return createReviewWorkSnapshot({
|
|
4538
4183
|
comments: cachedRenderSnapshot.comments,
|
|
4539
4184
|
trackedChanges: cachedRenderSnapshot.trackedChanges,
|
|
4540
|
-
workflowMarkup:
|
|
4185
|
+
workflowMarkup: workflowCoordinator.getWorkflowMarkupSnapshot(),
|
|
4541
4186
|
document: state.document,
|
|
4542
4187
|
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
4543
4188
|
});
|
|
@@ -4590,6 +4235,17 @@ export function createDocumentRuntime(
|
|
|
4590
4235
|
},
|
|
4591
4236
|
};
|
|
4592
4237
|
|
|
4238
|
+
// Late-bind the workflow coordinator's dispatch accessor now that
|
|
4239
|
+
// the runtime object exists. addScope / removeScope / setWorkflowOverlay /
|
|
4240
|
+
// setWorkflowMetadataEntries and other coordinator methods route
|
|
4241
|
+
// through this. Calling any of those before this line is a bug
|
|
4242
|
+
// (guarded by the throw in the placeholder).
|
|
4243
|
+
dispatchToRuntime = (command: unknown) => {
|
|
4244
|
+
runtime.dispatch(
|
|
4245
|
+
command as Parameters<typeof runtime.dispatch>[0],
|
|
4246
|
+
);
|
|
4247
|
+
};
|
|
4248
|
+
|
|
4593
4249
|
return runtime;
|
|
4594
4250
|
|
|
4595
4251
|
function applyHistory(direction: "undo" | "redo"): void {
|
|
@@ -4667,6 +4323,12 @@ export function createDocumentRuntime(
|
|
|
4667
4323
|
return;
|
|
4668
4324
|
}
|
|
4669
4325
|
|
|
4326
|
+
emitStageToken(telemetryBus, "commit", "commit.apply.start", {
|
|
4327
|
+
revisionToken: state.revisionToken,
|
|
4328
|
+
markDirty: transaction.markDirty,
|
|
4329
|
+
stepCount: transaction.mapping?.steps?.length ?? 0,
|
|
4330
|
+
});
|
|
4331
|
+
|
|
4670
4332
|
const previous = state;
|
|
4671
4333
|
|
|
4672
4334
|
const tApply0 = performance.now();
|
|
@@ -4675,7 +4337,10 @@ export function createDocumentRuntime(
|
|
|
4675
4337
|
state = finalizeState(transaction.nextState, transaction.markDirty, clock());
|
|
4676
4338
|
perfCounters.increment("commit.finalizeState.us", Math.round((performance.now() - tFinalize0) * 1000));
|
|
4677
4339
|
storySelections.set(storyTargetKey(activeStory), state.selection);
|
|
4678
|
-
|
|
4340
|
+
// Re-sync marker-backed scope IDs against the new document; the
|
|
4341
|
+
// store handles set computation + telemetry. `replaceOverlay(current, doc)`
|
|
4342
|
+
// is idempotent on state and re-derives the marker-backed set.
|
|
4343
|
+
overlayStore.replaceOverlay(overlayStore.getOverlay(), state.document);
|
|
4679
4344
|
const detachedWorkflowScopeWarnings = syncDetachedWorkflowScopeWarningsInState();
|
|
4680
4345
|
|
|
4681
4346
|
const tInvalidate0 = performance.now();
|
|
@@ -4763,6 +4428,9 @@ export function createDocumentRuntime(
|
|
|
4763
4428
|
});
|
|
4764
4429
|
perfCounters.increment("commit.notify.us", Math.round((performance.now() - tNotify0) * 1000));
|
|
4765
4430
|
perfCounters.increment("commit.total.us", Math.round((performance.now() - tApply0) * 1000));
|
|
4431
|
+
emitStageToken(telemetryBus, "commit", "commit.apply.complete", {
|
|
4432
|
+
revisionToken: state.revisionToken,
|
|
4433
|
+
});
|
|
4766
4434
|
}
|
|
4767
4435
|
|
|
4768
4436
|
function notify(
|
|
@@ -4986,13 +4654,42 @@ export function createDocumentRuntime(
|
|
|
4986
4654
|
textOptions: {
|
|
4987
4655
|
selection?: EditorState["selection"];
|
|
4988
4656
|
blockedCommandName?: string;
|
|
4657
|
+
/**
|
|
4658
|
+
* Force the command-execution `documentMode` for this dispatch
|
|
4659
|
+
* regardless of what the workflow coordinator would infer from
|
|
4660
|
+
* the selection's scope stack. Used by
|
|
4661
|
+
* `applyScopeReplacement` for the `text-insert-tracked` +
|
|
4662
|
+
* `text-delete-tracked` step kinds emitted by the L08 compiler:
|
|
4663
|
+
* the compiler has already decided this step should produce
|
|
4664
|
+
* tracked insertion/deletion revisions, so the runtime must
|
|
4665
|
+
* route through the suggesting-mode branch unconditionally.
|
|
4666
|
+
*
|
|
4667
|
+
* Coord-08 §2 ("Suggest-mode tracked-insert dispatch primitive")
|
|
4668
|
+
* asks for this verb; the implementation is a single-field
|
|
4669
|
+
* override to the context that `executeEditorCommand` reads.
|
|
4670
|
+
*/
|
|
4671
|
+
documentModeOverride?: DocumentMode;
|
|
4989
4672
|
} = {},
|
|
4990
4673
|
): TextCommandAck {
|
|
4674
|
+
emitStageToken(telemetryBus, "command", "command.dispatch.start", {
|
|
4675
|
+
commandType: command.type,
|
|
4676
|
+
});
|
|
4677
|
+
// Every exit of this function must emit a matching `.complete` stage
|
|
4678
|
+
// token so the `command` channel carries start/complete pairs just like
|
|
4679
|
+
// the `commit` channel does. D1 lineage reads the pair as proof no
|
|
4680
|
+
// silent short-circuit swallowed the dispatch.
|
|
4681
|
+
const completeDispatch = (ack: TextCommandAck): TextCommandAck => {
|
|
4682
|
+
emitStageToken(telemetryBus, "command", "command.dispatch.complete", {
|
|
4683
|
+
commandType: command.type,
|
|
4684
|
+
outcome: ack.kind,
|
|
4685
|
+
});
|
|
4686
|
+
return ack;
|
|
4687
|
+
};
|
|
4991
4688
|
const opId = (command.origin as { opId?: string } | undefined)?.opId;
|
|
4992
4689
|
const selection = textOptions.selection ?? state.selection;
|
|
4993
4690
|
if (
|
|
4994
4691
|
activeStory.kind !== "main" &&
|
|
4995
|
-
getEffectiveDocumentMode(selection) === "suggesting" &&
|
|
4692
|
+
workflowCoordinator.getEffectiveDocumentMode(selection) === "suggesting" &&
|
|
4996
4693
|
command.type === "paragraph.split"
|
|
4997
4694
|
) {
|
|
4998
4695
|
const message = `"${command.type}" is not supported in suggesting mode for this story.`;
|
|
@@ -5006,14 +4703,14 @@ export function createDocumentRuntime(
|
|
|
5006
4703
|
storyTarget: activeStory,
|
|
5007
4704
|
}],
|
|
5008
4705
|
});
|
|
5009
|
-
return {
|
|
4706
|
+
return completeDispatch({
|
|
5010
4707
|
kind: "rejected",
|
|
5011
4708
|
opId,
|
|
5012
4709
|
newRevisionToken: "",
|
|
5013
4710
|
blockedReasons: [{ code: "suggesting_unsupported", message }],
|
|
5014
|
-
};
|
|
4711
|
+
});
|
|
5015
4712
|
}
|
|
5016
|
-
const blockedReasons =
|
|
4713
|
+
const blockedReasons = workflowCoordinator.evaluateBlockedReasons(selection, command.type);
|
|
5017
4714
|
if (blockedReasons.length > 0) {
|
|
5018
4715
|
emit({
|
|
5019
4716
|
type: "command_blocked",
|
|
@@ -5021,18 +4718,20 @@ export function createDocumentRuntime(
|
|
|
5021
4718
|
command: textOptions.blockedCommandName ?? command.type,
|
|
5022
4719
|
reasons: blockedReasons,
|
|
5023
4720
|
});
|
|
5024
|
-
return {
|
|
4721
|
+
return completeDispatch({
|
|
5025
4722
|
kind: "rejected",
|
|
5026
4723
|
opId,
|
|
5027
4724
|
newRevisionToken: "",
|
|
5028
4725
|
blockedReasons: blockedReasons.map((r) => ({ code: r.code, message: r.message })),
|
|
5029
|
-
};
|
|
4726
|
+
});
|
|
5030
4727
|
}
|
|
5031
4728
|
|
|
5032
4729
|
const timestamp = normalizeCommandTimestamp(command.origin?.timestamp) ?? clock();
|
|
5033
4730
|
const context = {
|
|
5034
4731
|
timestamp,
|
|
5035
|
-
documentMode:
|
|
4732
|
+
documentMode:
|
|
4733
|
+
textOptions.documentModeOverride ??
|
|
4734
|
+
workflowCoordinator.getEffectiveDocumentMode(selection),
|
|
5036
4735
|
defaultAuthorId: defaultAuthorId ?? undefined,
|
|
5037
4736
|
renderSnapshot: cachedRenderSnapshot,
|
|
5038
4737
|
} as const;
|
|
@@ -5054,13 +4753,13 @@ export function createDocumentRuntime(
|
|
|
5054
4753
|
activeStory: preActiveStory,
|
|
5055
4754
|
priorDocument,
|
|
5056
4755
|
});
|
|
5057
|
-
return classifyAck({
|
|
4756
|
+
return completeDispatch(classifyAck({
|
|
5058
4757
|
command,
|
|
5059
4758
|
opId,
|
|
5060
4759
|
priorState: baseState,
|
|
5061
4760
|
transaction: mainTransaction,
|
|
5062
4761
|
newRevisionToken: state.revisionToken,
|
|
5063
|
-
});
|
|
4762
|
+
}));
|
|
5064
4763
|
}
|
|
5065
4764
|
|
|
5066
4765
|
const localState = createEditorState({
|
|
@@ -5091,11 +4790,11 @@ export function createDocumentRuntime(
|
|
|
5091
4790
|
historyBoundary: "skip",
|
|
5092
4791
|
markDirty: false,
|
|
5093
4792
|
});
|
|
5094
|
-
return {
|
|
4793
|
+
return completeDispatch({
|
|
5095
4794
|
kind: "equivalent",
|
|
5096
4795
|
opId,
|
|
5097
4796
|
newRevisionToken: state.revisionToken,
|
|
5098
|
-
};
|
|
4797
|
+
});
|
|
5099
4798
|
}
|
|
5100
4799
|
|
|
5101
4800
|
const nextDocument = replaceStoryBlocks(
|
|
@@ -5136,13 +4835,13 @@ export function createDocumentRuntime(
|
|
|
5136
4835
|
activeStory: preActiveStory,
|
|
5137
4836
|
priorDocument,
|
|
5138
4837
|
});
|
|
5139
|
-
return classifyAck({
|
|
4838
|
+
return completeDispatch(classifyAck({
|
|
5140
4839
|
command,
|
|
5141
4840
|
opId,
|
|
5142
4841
|
priorState: baseState,
|
|
5143
4842
|
transaction: mergedTransaction,
|
|
5144
4843
|
newRevisionToken: state.revisionToken,
|
|
5145
|
-
});
|
|
4844
|
+
}));
|
|
5146
4845
|
}
|
|
5147
4846
|
|
|
5148
4847
|
function classifyAck(params: {
|
|
@@ -5346,7 +5045,7 @@ export function createDocumentRuntime(
|
|
|
5346
5045
|
// text is not heading text), so this does not loop.
|
|
5347
5046
|
const ctx = {
|
|
5348
5047
|
timestamp: clock(),
|
|
5349
|
-
documentMode: getEffectiveDocumentMode(state.selection),
|
|
5048
|
+
documentMode: workflowCoordinator.getEffectiveDocumentMode(state.selection),
|
|
5350
5049
|
defaultAuthorId: defaultAuthorId ?? undefined,
|
|
5351
5050
|
renderSnapshot: cachedRenderSnapshot,
|
|
5352
5051
|
} as const;
|
|
@@ -5381,6 +5080,54 @@ export function createDocumentRuntime(
|
|
|
5381
5080
|
const t0 = performance.now();
|
|
5382
5081
|
emitInternal(event);
|
|
5383
5082
|
perfCounters.increment(`emit.${event.type}.internal.us`, Math.round((performance.now() - t0) * 1000));
|
|
5083
|
+
// Phase 1 — telemetry fan-out. One central emit covering warning / scope /
|
|
5084
|
+
// commit / selection / collab channels. Each branch early-returns if the
|
|
5085
|
+
// corresponding channel is off (one bitmask AND). Zero alloc when all off.
|
|
5086
|
+
if (telemetryBus.isEnabled("warning") && event.type === "warning_added") {
|
|
5087
|
+
telemetryBus.emit({
|
|
5088
|
+
channel: "warning",
|
|
5089
|
+
type: `warning.${event.warning.code}`,
|
|
5090
|
+
t: 0,
|
|
5091
|
+
payload: {
|
|
5092
|
+
warningId: event.warning.warningId,
|
|
5093
|
+
code: event.warning.code,
|
|
5094
|
+
severity: event.warning.severity,
|
|
5095
|
+
source: event.warning.source,
|
|
5096
|
+
scopeId: (event.warning as { scopeId?: string }).scopeId,
|
|
5097
|
+
},
|
|
5098
|
+
});
|
|
5099
|
+
} else if (telemetryBus.isEnabled("warning") && event.type === "warning_cleared") {
|
|
5100
|
+
telemetryBus.emit({
|
|
5101
|
+
channel: "warning",
|
|
5102
|
+
type: "warning.cleared",
|
|
5103
|
+
t: 0,
|
|
5104
|
+
payload: { warningId: event.warningId, code: event.code },
|
|
5105
|
+
});
|
|
5106
|
+
}
|
|
5107
|
+
if (
|
|
5108
|
+
telemetryBus.isEnabled("scope") &&
|
|
5109
|
+
event.type === "workflow_overlay_changed"
|
|
5110
|
+
) {
|
|
5111
|
+
telemetryBus.emitLazy("scope", () => {
|
|
5112
|
+
const overlay = overlayStore.getOverlay();
|
|
5113
|
+
return {
|
|
5114
|
+
type: "scope.overlay_changed",
|
|
5115
|
+
payload: {
|
|
5116
|
+
markerBackedCount: overlayStore.getMarkerBackedScopeIds().size,
|
|
5117
|
+
overlayScopeCount: overlay?.scopes.length ?? 0,
|
|
5118
|
+
activeWorkItemId: overlay?.activeWorkItemId ?? null,
|
|
5119
|
+
},
|
|
5120
|
+
};
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5123
|
+
if (telemetryBus.isEnabled("selection") && event.type === "selection_changed") {
|
|
5124
|
+
telemetryBus.emit({
|
|
5125
|
+
channel: "selection",
|
|
5126
|
+
type: "selection.changed",
|
|
5127
|
+
t: 0,
|
|
5128
|
+
payload: { isCollapsed: (event as { selection?: { isCollapsed?: boolean } }).selection?.isCollapsed ?? true },
|
|
5129
|
+
});
|
|
5130
|
+
}
|
|
5384
5131
|
if (shouldEmitContextAnalyticsChanged(event)) {
|
|
5385
5132
|
scheduleContextAnalyticsEmit();
|
|
5386
5133
|
}
|
|
@@ -5396,85 +5143,43 @@ export function createDocumentRuntime(
|
|
|
5396
5143
|
}
|
|
5397
5144
|
| null = null;
|
|
5398
5145
|
switch (command.type) {
|
|
5399
|
-
case "workflow.set-overlay":
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
cachedRenderSnapshot = refreshRenderSnapshot();
|
|
5405
|
-
const snapshot = deriveWorkflowScopeSnapshot()!;
|
|
5406
|
-
emit({
|
|
5407
|
-
type: "workflow_overlay_changed",
|
|
5408
|
-
documentId: state.documentId,
|
|
5409
|
-
snapshot,
|
|
5410
|
-
});
|
|
5411
|
-
if (workflowOverlay.activeWorkItemId !== undefined) {
|
|
5412
|
-
emit({
|
|
5413
|
-
type: "workflow_active_work_item_changed",
|
|
5414
|
-
documentId: state.documentId,
|
|
5415
|
-
activeWorkItemId: workflowOverlay.activeWorkItemId ?? null,
|
|
5416
|
-
});
|
|
5417
|
-
}
|
|
5418
|
-
break;
|
|
5419
|
-
}
|
|
5420
|
-
case "workflow.clear-overlay": {
|
|
5421
|
-
workflowOverlay = null;
|
|
5422
|
-
syncMarkerBackedScopeIds(state.document, workflowOverlay);
|
|
5423
|
-
cachedNormalizedWorkflowOverlay = undefined;
|
|
5424
|
-
detachedWorkflowScopeWarnings = syncDetachedWorkflowScopeWarningsInState();
|
|
5425
|
-
cachedRenderSnapshot = refreshRenderSnapshot();
|
|
5426
|
-
emit({
|
|
5427
|
-
type: "workflow_active_work_item_changed",
|
|
5428
|
-
documentId: state.documentId,
|
|
5429
|
-
activeWorkItemId: null,
|
|
5430
|
-
});
|
|
5431
|
-
emit({
|
|
5432
|
-
type: "workflow_overlay_changed",
|
|
5433
|
-
documentId: state.documentId,
|
|
5434
|
-
snapshot: {
|
|
5435
|
-
overlayPresent: false,
|
|
5436
|
-
activeWorkItemId: null,
|
|
5437
|
-
scopes: [],
|
|
5438
|
-
candidates: [],
|
|
5439
|
-
blockedReasons: [],
|
|
5440
|
-
},
|
|
5441
|
-
});
|
|
5442
|
-
break;
|
|
5443
|
-
}
|
|
5444
|
-
case "workflow.set-metadata-definitions": {
|
|
5445
|
-
workflowMetadataDefinitions = structuredClone(command.definitions);
|
|
5446
|
-
emit({
|
|
5447
|
-
type: "workflow_metadata_changed",
|
|
5448
|
-
documentId: state.documentId,
|
|
5449
|
-
snapshot: deriveWorkflowMetadataSnapshot(),
|
|
5450
|
-
});
|
|
5451
|
-
break;
|
|
5452
|
-
}
|
|
5453
|
-
case "workflow.clear-metadata-definitions": {
|
|
5454
|
-
workflowMetadataDefinitions = [];
|
|
5455
|
-
emit({
|
|
5456
|
-
type: "workflow_metadata_changed",
|
|
5457
|
-
documentId: state.documentId,
|
|
5458
|
-
snapshot: deriveWorkflowMetadataSnapshot(),
|
|
5459
|
-
});
|
|
5460
|
-
break;
|
|
5461
|
-
}
|
|
5462
|
-
case "workflow.set-metadata-entries": {
|
|
5463
|
-
workflowMetadataEntries = structuredClone(command.entries);
|
|
5464
|
-
emit({
|
|
5465
|
-
type: "workflow_metadata_changed",
|
|
5466
|
-
documentId: state.documentId,
|
|
5467
|
-
snapshot: deriveWorkflowMetadataSnapshot(),
|
|
5468
|
-
});
|
|
5469
|
-
break;
|
|
5470
|
-
}
|
|
5146
|
+
case "workflow.set-overlay":
|
|
5147
|
+
case "workflow.clear-overlay":
|
|
5148
|
+
case "workflow.set-metadata-definitions":
|
|
5149
|
+
case "workflow.clear-metadata-definitions":
|
|
5150
|
+
case "workflow.set-metadata-entries":
|
|
5471
5151
|
case "workflow.clear-metadata-entries": {
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5152
|
+
// Delegate all workflow overlay / metadata commands to the
|
|
5153
|
+
// coordinator. It owns state mutation (via the overlay store),
|
|
5154
|
+
// cache invalidation, and event emission. The returned
|
|
5155
|
+
// warning-delta (if any) is rewritten into the runtime's
|
|
5156
|
+
// state.warnings array by `syncDetachedScopeWarnings` called
|
|
5157
|
+
// from inside the coordinator, then surfaced through the
|
|
5158
|
+
// `warning_added` / `warning_cleared` emit loop below.
|
|
5159
|
+
const warningDelta = workflowCoordinator.applyOverlayCommand(
|
|
5160
|
+
command as Parameters<typeof workflowCoordinator.applyOverlayCommand>[0],
|
|
5161
|
+
() => {
|
|
5162
|
+
cachedRenderSnapshot = refreshRenderSnapshot();
|
|
5163
|
+
},
|
|
5164
|
+
);
|
|
5165
|
+
if (warningDelta) {
|
|
5166
|
+
const priorWarnings = state.warnings;
|
|
5167
|
+
if (
|
|
5168
|
+
warningDelta.nextWarnings !== priorWarnings &&
|
|
5169
|
+
warningDelta.nextWarnings.length + priorWarnings.length > 0
|
|
5170
|
+
) {
|
|
5171
|
+
state = { ...state, warnings: warningDelta.nextWarnings };
|
|
5172
|
+
}
|
|
5173
|
+
if (warningDelta.added.length > 0 || warningDelta.cleared.length > 0) {
|
|
5174
|
+
detachedWorkflowScopeWarnings = {
|
|
5175
|
+
added: warningDelta.added,
|
|
5176
|
+
cleared: warningDelta.cleared.map((c) => ({
|
|
5177
|
+
warningId: c.warningId,
|
|
5178
|
+
code: c.code,
|
|
5179
|
+
})),
|
|
5180
|
+
};
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5478
5183
|
break;
|
|
5479
5184
|
}
|
|
5480
5185
|
case "host-annotation.set-overlay": {
|
|
@@ -5591,8 +5296,8 @@ export function createDocumentRuntime(
|
|
|
5591
5296
|
trackedSnapshots.get(
|
|
5592
5297
|
getRuntimeContextAnalyticsQueryKey(
|
|
5593
5298
|
resolveCurrentContextAnalyticsQuery({
|
|
5594
|
-
workflowScopeSnapshot:
|
|
5595
|
-
interactionGuardSnapshot:
|
|
5299
|
+
workflowScopeSnapshot: workflowCoordinator.getWorkflowScopeSnapshot(),
|
|
5300
|
+
interactionGuardSnapshot: workflowCoordinator.getInteractionGuardSnapshot(),
|
|
5596
5301
|
}),
|
|
5597
5302
|
),
|
|
5598
5303
|
) ?? null,
|
|
@@ -5994,15 +5699,17 @@ function toPublicCompatibilityReport(
|
|
|
5994
5699
|
function toPublicCompatibilityFeatureEntry(
|
|
5995
5700
|
entry: InternalCompatibilityFeatureEntry,
|
|
5996
5701
|
) {
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
: undefined,
|
|
6002
|
-
};
|
|
5702
|
+
// Internal* collapsed to public-type aliases 2026-04-22 — the record
|
|
5703
|
+
// `affectedAnchor` is already the public flat shape end-to-end (see
|
|
5704
|
+
// `docs/plans/cross-layer-coord-02.md §8`).
|
|
5705
|
+
return { ...entry };
|
|
6003
5706
|
}
|
|
6004
5707
|
|
|
6005
5708
|
function toPublicWarning(warning: InternalEditorWarning): EditorWarning {
|
|
5709
|
+
// Internal* collapsed to public-type aliases 2026-04-22; anchor is
|
|
5710
|
+
// already public flat. The function remains meaningful — it still
|
|
5711
|
+
// synthesises the v2.0.0 `diagnostic` composite from legacy warning
|
|
5712
|
+
// codes when absent. Anchor handling is now identity.
|
|
6006
5713
|
const diagnostic =
|
|
6007
5714
|
warning.diagnostic ??
|
|
6008
5715
|
(() => {
|
|
@@ -6025,9 +5732,7 @@ function toPublicWarning(warning: InternalEditorWarning): EditorWarning {
|
|
|
6025
5732
|
source: warning.source,
|
|
6026
5733
|
},
|
|
6027
5734
|
details: warning.details,
|
|
6028
|
-
affectedAnchor: warning.affectedAnchor
|
|
6029
|
-
? toPublicAnchorProjection(warning.affectedAnchor)
|
|
6030
|
-
: undefined,
|
|
5735
|
+
affectedAnchor: warning.affectedAnchor,
|
|
6031
5736
|
});
|
|
6032
5737
|
default:
|
|
6033
5738
|
return undefined;
|
|
@@ -6035,9 +5740,6 @@ function toPublicWarning(warning: InternalEditorWarning): EditorWarning {
|
|
|
6035
5740
|
})();
|
|
6036
5741
|
return {
|
|
6037
5742
|
...warning,
|
|
6038
|
-
affectedAnchor: warning.affectedAnchor
|
|
6039
|
-
? toPublicAnchorProjection(warning.affectedAnchor)
|
|
6040
|
-
: undefined,
|
|
6041
5743
|
diagnostic,
|
|
6042
5744
|
};
|
|
6043
5745
|
}
|
|
@@ -6771,7 +6473,7 @@ function refreshDocumentFields(
|
|
|
6771
6473
|
return { document, updatedCount, changed: false };
|
|
6772
6474
|
}
|
|
6773
6475
|
|
|
6774
|
-
const nextDocument:
|
|
6476
|
+
const nextDocument: MutableCanonicalDocument = {
|
|
6775
6477
|
...document,
|
|
6776
6478
|
content: {
|
|
6777
6479
|
...document.content,
|
|
@@ -6779,7 +6481,7 @@ function refreshDocumentFields(
|
|
|
6779
6481
|
},
|
|
6780
6482
|
...(nextSubParts ? { subParts: nextSubParts } : {}),
|
|
6781
6483
|
};
|
|
6782
|
-
const nextRegistry =
|
|
6484
|
+
const nextRegistry = rebuildFieldRegistry({
|
|
6783
6485
|
content: nextDocument.content,
|
|
6784
6486
|
styles: nextDocument.styles,
|
|
6785
6487
|
subParts: nextDocument.subParts,
|
|
@@ -6883,14 +6585,14 @@ function refreshDocumentTableOfContents(
|
|
|
6883
6585
|
};
|
|
6884
6586
|
}
|
|
6885
6587
|
|
|
6886
|
-
const nextDocument:
|
|
6588
|
+
const nextDocument: MutableCanonicalDocument = {
|
|
6887
6589
|
...document,
|
|
6888
6590
|
content: {
|
|
6889
6591
|
...document.content,
|
|
6890
6592
|
children: nextChildren,
|
|
6891
6593
|
},
|
|
6892
6594
|
};
|
|
6893
|
-
const nextRegistry =
|
|
6595
|
+
const nextRegistry = rebuildFieldRegistry({
|
|
6894
6596
|
content: nextDocument.content,
|
|
6895
6597
|
styles: nextDocument.styles,
|
|
6896
6598
|
subParts: nextDocument.subParts,
|