@beyondwork/docx-react-component 1.0.66 → 1.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -931
- package/package.json +26 -27
- package/src/api/anchor-conversion.ts +43 -0
- package/src/api/editor-state-types.ts +2 -1
- package/src/api/public-types.ts +504 -101
- package/src/api/session-state.ts +4 -0
- package/src/api/v3/README.md +91 -0
- package/src/api/v3/_create.ts +146 -0
- package/src/api/v3/_layer-metadata.ts +362 -0
- package/src/api/v3/_mocks.ts +84 -0
- package/src/api/v3/_runtime-handle.ts +162 -0
- package/src/api/v3/_ux-response.ts +73 -0
- package/src/api/v3/ai/_metadata-audit.ts +225 -0
- package/src/api/v3/ai/attach.ts +235 -0
- package/src/api/v3/ai/bundle.ts +132 -0
- package/src/api/v3/ai/explain.ts +144 -0
- package/src/api/v3/ai/export.ts +54 -0
- package/src/api/v3/ai/inspect.ts +118 -0
- package/src/api/v3/ai/policy.ts +77 -0
- package/src/api/v3/ai/replacement.ts +341 -0
- package/src/api/v3/ai/resolve.ts +133 -0
- package/src/api/v3/index.ts +79 -0
- package/src/api/v3/runtime/chart.ts +310 -0
- package/src/api/v3/runtime/clipboard.ts +81 -0
- package/src/api/v3/runtime/collab.ts +331 -0
- package/src/api/v3/runtime/content.ts +236 -0
- package/src/api/v3/runtime/document.ts +282 -0
- package/src/api/v3/runtime/formatting.ts +186 -0
- package/src/api/v3/runtime/geometry.ts +349 -0
- package/src/api/v3/runtime/layout.ts +108 -0
- package/src/api/v3/runtime/review.ts +129 -0
- package/src/api/v3/runtime/search.ts +74 -0
- package/src/api/v3/runtime/table.ts +63 -0
- package/src/api/v3/runtime/workflow.ts +434 -0
- package/src/api/v3/ui/_context.ts +86 -0
- package/src/api/v3/ui/_create.ts +65 -0
- package/src/api/v3/ui/_types.ts +520 -0
- package/src/api/v3/ui/chrome-composition.ts +342 -0
- package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
- package/src/api/v3/ui/chrome.ts +476 -0
- package/src/api/v3/ui/debug.ts +124 -0
- package/src/api/v3/ui/index.ts +64 -0
- package/src/api/v3/ui/overlays-visibility.ts +170 -0
- package/src/api/v3/ui/overlays.ts +427 -0
- package/src/api/v3/ui/scope.ts +71 -0
- package/src/api/v3/ui/session.ts +100 -0
- package/src/api/v3/ui/surface.ts +170 -0
- package/src/api/v3/ui/viewport.ts +303 -0
- package/src/core/commands/index.ts +28 -6
- package/src/core/commands/list-commands.ts +3 -2
- package/src/core/commands/section-layout-commands.ts +9 -8
- package/src/core/schema/text-schema.ts +16 -0
- package/src/core/selection/mapping.ts +33 -72
- package/src/core/state/editor-state.ts +96 -189
- package/src/index.ts +23 -4
- package/src/io/chart-preview-resolver.ts +1 -1
- package/src/io/docx-session.ts +36 -4795
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +1 -1
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +45 -0
- package/src/io/export/serialize-run-formatting.ts +17 -2
- package/src/io/export/twip.ts +1 -1
- package/src/io/normalize/normalize-text.ts +27 -20
- package/src/io/ooxml/chart/parse-series.ts +1 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +1 -1
- package/src/io/ooxml/classify-embedding.ts +83 -33
- package/src/io/ooxml/parse-fill.ts +1 -1
- package/src/io/ooxml/parse-main-document.ts +71 -1
- package/src/io/ooxml/parse-object.ts +14 -10
- package/src/io/ooxml/parse-run-formatting.ts +47 -1
- package/src/io/ooxml/property-grab-bag.ts +2 -2
- package/src/io/ooxml/units.ts +11 -0
- package/src/io/ooxml/workflow-payload.ts +282 -7
- package/src/model/anchor.ts +85 -0
- package/src/model/canonical-document.ts +351 -15
- package/src/model/chart-types.ts +1 -1
- package/src/model/layout/index.ts +83 -0
- package/src/model/layout/page-graph-types.ts +181 -0
- package/src/model/layout/page-layout-snapshot.ts +105 -0
- package/src/model/layout/resolved-layout-types.ts +47 -0
- package/src/model/layout/runtime-page-graph-types.ts +102 -0
- package/src/model/paragraph-scope-ids.ts +72 -0
- package/src/model/review/comment-types.ts +112 -0
- package/src/model/review/index.ts +2 -0
- package/src/model/review/revision-types.ts +215 -0
- package/src/model/snapshot.ts +32 -0
- package/src/review/store/comment-store.ts +21 -47
- package/src/review/store/revision-types.ts +40 -198
- package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
- package/src/runtime/collab/runtime-collab-sync.ts +13 -3
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
- package/src/runtime/debug/event-ring-buffer.ts +64 -0
- package/src/runtime/debug/probability-sampler.ts +18 -0
- package/src/runtime/debug/runtime-debug-facet.ts +67 -0
- package/src/runtime/debug/stage-tokens.ts +31 -0
- package/src/runtime/debug/telemetry-bus.ts +271 -0
- package/src/runtime/debug/types.ts +275 -0
- package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
- package/src/runtime/document-layout.ts +8 -6
- package/src/runtime/document-runtime.ts +843 -1141
- package/src/runtime/document-search.ts +1 -1
- package/src/runtime/edit-ops/index.ts +1 -1
- package/src/runtime/external-send-runtime.ts +1 -1
- package/src/runtime/formatting/document-lookup.ts +235 -0
- package/src/runtime/formatting/field/registry.ts +41 -0
- package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
- package/src/runtime/formatting/font-resolution.ts +83 -0
- package/src/runtime/formatting/formatting-context.ts +903 -0
- package/src/runtime/formatting/formatting-types.ts +157 -0
- package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
- package/src/runtime/formatting/index.ts +125 -0
- package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
- package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
- package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
- package/src/runtime/formatting/projector.ts +75 -0
- package/src/runtime/formatting/resolve-effective.ts +407 -0
- package/src/runtime/formatting/revision-display.ts +105 -0
- package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
- package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
- package/src/runtime/formatting/telemetry-bridge.ts +106 -0
- package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
- package/src/runtime/geometry/caret-geometry.ts +164 -0
- package/src/runtime/geometry/geometry-facet.ts +364 -0
- package/src/runtime/geometry/geometry-types.ts +256 -0
- package/src/runtime/geometry/hit-test.ts +125 -0
- package/src/runtime/geometry/index.ts +71 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
- package/src/runtime/geometry/invalidation.ts +35 -0
- package/src/runtime/geometry/object-handles.ts +77 -0
- package/src/runtime/geometry/overlay-rects.ts +85 -0
- package/src/runtime/geometry/project-anchors.ts +100 -0
- package/src/runtime/geometry/project-fragments.ts +216 -0
- package/src/runtime/geometry/projector.ts +129 -0
- package/src/runtime/geometry/replacement-envelope.ts +130 -0
- package/src/runtime/geometry/viewport.ts +218 -0
- package/src/runtime/layout/compat-input-ledger.ts +211 -0
- package/src/runtime/layout/index.ts +6 -1
- package/src/runtime/layout/inert-layout-facet.ts +12 -7
- package/src/runtime/layout/layout-engine-instance.ts +189 -11
- package/src/runtime/layout/layout-engine-version.ts +450 -1
- package/src/runtime/layout/layout-facet-types.ts +60 -0
- package/src/runtime/layout/layout-measurement-provider.ts +13 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
- package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
- package/src/runtime/layout/page-graph.ts +62 -209
- package/src/runtime/layout/page-story-resolver.ts +7 -12
- package/src/runtime/layout/paginated-layout-engine.ts +186 -11
- package/src/runtime/layout/project-block-fragments.ts +11 -0
- package/src/runtime/layout/projector.ts +90 -0
- package/src/runtime/layout/public-facet.ts +187 -442
- package/src/runtime/layout/resolved-formatting-state.ts +158 -26
- package/src/runtime/layout/table-render-plan.ts +1 -1
- package/src/runtime/prerender/cache-envelope.ts +6 -1
- package/src/runtime/prerender/prerender-document.ts +18 -23
- package/src/runtime/render/decoration-resolver.ts +1 -1
- package/src/runtime/render/render-frame-types.ts +20 -0
- package/src/runtime/render/render-kernel.ts +94 -25
- package/src/runtime/scopes/_formatting-seam.ts +262 -0
- package/src/runtime/scopes/_scope-dependencies.ts +49 -0
- package/src/runtime/scopes/action-validation.ts +356 -0
- package/src/runtime/scopes/attach-explanation.ts +102 -0
- package/src/runtime/scopes/audit-bundle.ts +71 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
- package/src/runtime/scopes/compile-scope.ts +262 -0
- package/src/runtime/scopes/compiler-service.ts +431 -0
- package/src/runtime/scopes/create-issue.ts +107 -0
- package/src/runtime/scopes/enumerate-scopes.ts +543 -0
- package/src/runtime/scopes/evidence.ts +233 -0
- package/src/runtime/scopes/index.ts +150 -0
- package/src/runtime/scopes/position-map.ts +214 -0
- package/src/runtime/scopes/preservation-boundary.ts +91 -0
- package/src/runtime/scopes/projector.ts +49 -0
- package/src/runtime/scopes/replaceability.ts +87 -0
- package/src/runtime/scopes/replacement/apply.ts +228 -0
- package/src/runtime/scopes/replacement/compile.ts +59 -0
- package/src/runtime/scopes/replacement/propose.ts +42 -0
- package/src/runtime/scopes/resolve-reference.ts +347 -0
- package/src/runtime/scopes/review-bundle.ts +141 -0
- package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
- package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
- package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
- package/src/runtime/scopes/scope-kinds/field.ts +65 -0
- package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
- package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
- package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
- package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
- package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
- package/src/runtime/scopes/scope-kinds/table.ts +55 -0
- package/src/runtime/scopes/scope-range.ts +208 -0
- package/src/runtime/scopes/semantic-scope-types.ts +454 -0
- package/src/runtime/scopes/workflow-overlap.ts +92 -0
- package/src/runtime/selection/index.ts +1 -1
- package/src/runtime/structure-ops/fragment-insert.ts +1 -1
- package/src/runtime/structure-ops/index.ts +1 -1
- package/src/runtime/surface-projection.ts +232 -262
- package/src/runtime/units.ts +4 -2
- package/src/runtime/workflow/coordinator.ts +1348 -0
- package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
- package/src/runtime/workflow/index.ts +25 -0
- package/src/runtime/workflow/markup-mode-policy.ts +98 -0
- package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
- package/src/runtime/workflow/metadata-persistence.ts +306 -0
- package/src/runtime/workflow/metadata-writer.ts +123 -0
- package/src/runtime/workflow/overlay-store.ts +690 -0
- package/src/runtime/workflow/projector.ts +127 -0
- package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
- package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
- package/src/runtime/workflow/rail/types.ts +198 -0
- package/src/runtime/workflow/scope-rail-composer.ts +39 -0
- package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
- package/src/runtime/workflow/scope-writer.ts +188 -0
- package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
- package/src/runtime/workflow/visibility-policy.ts +129 -0
- package/src/session/_sync-legacy.ts +66 -0
- package/src/session/export/embedded-reconstitute.ts +104 -0
- package/src/session/export/export-diagnostics.ts +85 -0
- package/src/session/export/export-validation.ts +110 -0
- package/src/session/export/index.ts +34 -0
- package/src/session/export/preservation-reattach.ts +30 -0
- package/src/session/export/serialize-dispatch.ts +165 -0
- package/src/session/export/stateful-export-pipeline.ts +432 -0
- package/src/session/export/stateful-export.ts +684 -0
- package/src/session/import/canonical-assembly.ts +227 -0
- package/src/session/import/diagnostics-session.ts +54 -0
- package/src/session/import/embedded-discovery.ts +225 -0
- package/src/session/import/embedded-offload.ts +337 -0
- package/src/session/import/import-diagnostics.ts +69 -0
- package/src/session/import/loader-types.ts +313 -0
- package/src/session/import/loader.ts +1834 -0
- package/src/session/import/normalize.ts +195 -0
- package/src/session/import/package-parts.ts +217 -0
- package/src/session/import/package-read.ts +195 -0
- package/src/session/import/parse-orchestration.ts +105 -0
- package/src/session/import/part-constants.ts +70 -0
- package/src/session/import/part-discovery.ts +94 -0
- package/src/session/import/preservation-index.ts +46 -0
- package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
- package/src/session/import/review-import.ts +508 -0
- package/src/session/import/styles-consolidation.ts +281 -0
- package/src/session/import/workflow-scope-import.ts +256 -0
- package/src/session/index.ts +37 -0
- package/src/session/session-state.ts +69 -0
- package/src/session/session.ts +532 -0
- package/src/session/shared/protection.ts +228 -0
- package/src/session/shared/session-utils.ts +82 -0
- package/src/session/types.ts +499 -0
- package/src/shell/chart-snapshots.ts +96 -0
- package/src/shell/media-previews.ts +85 -0
- package/src/shell/overlay-anchor-bridge.ts +53 -0
- package/src/shell/paste-adapter.ts +23 -0
- package/src/shell/ref-commands.ts +1697 -0
- package/src/shell/ref-utilities.ts +48 -0
- package/src/shell/search.ts +51 -0
- package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
- package/src/shell/ui-subscriber-channels.ts +81 -0
- package/src/shell/use-collab-sync.ts +116 -0
- package/src/ui/WordReviewEditor.tsx +496 -2051
- package/src/ui/editor-shell-view.tsx +30 -1
- package/src/ui/editor-surface-controller.tsx +49 -1
- package/src/ui/headless/revision-decoration-model.ts +83 -0
- package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
- package/src/ui/headless/scoped-chrome-policy.ts +2 -2
- package/src/ui/headless/selection-tool-context.ts +1 -1
- package/src/ui/headless/selection-tool-resolver.ts +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +46 -1
- package/src/ui/ui-controller-factory.ts +221 -0
- package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
- package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
- package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
- package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
- package/src/ui-tailwind/chart/render/area.tsx +3 -3
- package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
- package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
- package/src/ui-tailwind/chart/render/combo.tsx +2 -2
- package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
- package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
- package/src/ui-tailwind/chart/render/line.tsx +3 -3
- package/src/ui-tailwind/chart/render/pie.tsx +6 -6
- package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
- package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
- package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
- package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
- package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
- package/src/ui-tailwind/debug/README.md +57 -0
- package/src/ui-tailwind/debug/index.ts +3 -0
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
- package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
- package/src/ui-tailwind/index.ts +0 -5
- package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
- package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
- package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
- package/src/ui-tailwind/review-workspace/types.ts +408 -0
- package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
- package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
- package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
- package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
- package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
- package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
- package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
- package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
- package/src/ui-tailwind/ui-api-context.tsx +43 -0
- package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
- package/src/validation/compatibility-engine.ts +6 -6
- package/src/runtime/styles-cascade.ts +0 -33
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
- /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
- /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
- /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 06 — Workflow overlay store.
|
|
3
|
+
*
|
|
4
|
+
* Owns the per-runtime workflow state that used to live as top-level
|
|
5
|
+
* closure variables inside `document-runtime.ts`:
|
|
6
|
+
* - workflow overlay (scopes + candidates + work items + active-work-item)
|
|
7
|
+
* - workflow metadata (definitions + entries)
|
|
8
|
+
* - marker-backed scope-id set (derived from canonical document)
|
|
9
|
+
* - shared workflow state (collab round-locked mode)
|
|
10
|
+
* - scope-chrome visibility (local UI state; never collab-replicated)
|
|
11
|
+
*
|
|
12
|
+
* Plus the pure-ish derivations the store alone can safely compute:
|
|
13
|
+
* - `normalizeOverlayForDocument` — converts overlay anchors into
|
|
14
|
+
* normalized range / node / detached shapes given the current
|
|
15
|
+
* canonical document. Cached per (document, overlay) pair.
|
|
16
|
+
* - `buildDetachedScopeWarnings` + `createInvalidatedScopeWarning` —
|
|
17
|
+
* synthesize the `workflow_scope_invalidated` warning when a
|
|
18
|
+
* previously marker-backed scope has been detached.
|
|
19
|
+
* - `mergeDetachedScopeWarnings` — diff desired-vs-existing detached
|
|
20
|
+
* warnings so the runtime can emit precise `warning_added` /
|
|
21
|
+
* `warning_cleared` deltas.
|
|
22
|
+
*
|
|
23
|
+
* What it does NOT own (by design — those belong to the coordinator):
|
|
24
|
+
* - interaction-guard / workflow-scope / workflow-markup snapshot
|
|
25
|
+
* caches (require runtime-scope deps like revisionToken, render
|
|
26
|
+
* snapshot, selection).
|
|
27
|
+
* - blocked-reason composition + scope-matching heuristics (require
|
|
28
|
+
* protection snapshot + viewState.documentMode).
|
|
29
|
+
* - rail / card composition (requires page graph + render frame).
|
|
30
|
+
*
|
|
31
|
+
* This file must satisfy Layer-06 purity contract W9: no imports from
|
|
32
|
+
* `document-runtime.ts`, `render`, `ui*`, `api/v*`, or `io`.
|
|
33
|
+
* Enforced by `scripts/ci-check-workflow-layer-purity.mjs`.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type {
|
|
37
|
+
EditorAnchorProjection,
|
|
38
|
+
ScopeChromeVisibilityState,
|
|
39
|
+
WorkflowMetadataDefinition,
|
|
40
|
+
WorkflowMetadataEntry,
|
|
41
|
+
WorkflowMetadataSnapshot,
|
|
42
|
+
WorkflowOverlay,
|
|
43
|
+
WorkflowScope,
|
|
44
|
+
} from "../../api/public-types.ts";
|
|
45
|
+
import {
|
|
46
|
+
type CanonicalDocumentEnvelope,
|
|
47
|
+
type EditorWarning as InternalEditorWarning,
|
|
48
|
+
} from "../../core/state/editor-state.ts";
|
|
49
|
+
import type { SharedWorkflowState } from "../collab/workflow-shared.ts";
|
|
50
|
+
import type { TelemetryBus } from "../debug/telemetry-bus.ts";
|
|
51
|
+
import { toInternalAnchorProjection } from "../../core/selection/anchor-conversion.ts";
|
|
52
|
+
import { buildDiagnosticFromLegacyWarningCode } from "../diagnostics/build-diagnostic.ts";
|
|
53
|
+
import { collectScopeLocations } from "./scope-resolver.ts";
|
|
54
|
+
import {
|
|
55
|
+
overlayVisibilityPoliciesEqual,
|
|
56
|
+
type OverlayKind,
|
|
57
|
+
type OverlayVisibilityPolicy,
|
|
58
|
+
} from "./visibility-policy.ts";
|
|
59
|
+
import {
|
|
60
|
+
workflowMarkupModePoliciesEqual,
|
|
61
|
+
type WorkflowMarkupModePolicy,
|
|
62
|
+
} from "./markup-mode-policy.ts";
|
|
63
|
+
|
|
64
|
+
export interface OverlayStoreConfig {
|
|
65
|
+
readonly initialOverlay: WorkflowOverlay | null;
|
|
66
|
+
readonly initialMetadataDefinitions: readonly WorkflowMetadataDefinition[];
|
|
67
|
+
readonly initialMetadataEntries: readonly WorkflowMetadataEntry[];
|
|
68
|
+
readonly initialVisibilityPolicies?: readonly OverlayVisibilityPolicy[];
|
|
69
|
+
/** X5 — class-A markup-mode policy. Single record; null = document
|
|
70
|
+
* predates the schema (consumer falls back to class-C default). */
|
|
71
|
+
readonly initialMarkupModePolicy?: WorkflowMarkupModePolicy | null;
|
|
72
|
+
readonly telemetryBus: TelemetryBus;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Result of `mergeDetachedScopeWarnings` — the existing `warnings`
|
|
76
|
+
* array replaced with the next-consistent state plus a per-warning
|
|
77
|
+
* delta suitable for emitting `warning_added` / `warning_cleared`
|
|
78
|
+
* events at the dispatch site. */
|
|
79
|
+
export interface MergeDetachedWarningsResult {
|
|
80
|
+
readonly nextWarnings: InternalEditorWarning[];
|
|
81
|
+
readonly added: InternalEditorWarning[];
|
|
82
|
+
readonly cleared: Array<{
|
|
83
|
+
readonly warningId: string;
|
|
84
|
+
readonly code: InternalEditorWarning["code"];
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface OverlayStore {
|
|
89
|
+
// ---- state getters ----
|
|
90
|
+
getOverlay(): WorkflowOverlay | null;
|
|
91
|
+
getMetadataDefinitions(): readonly WorkflowMetadataDefinition[];
|
|
92
|
+
getMetadataEntries(): readonly WorkflowMetadataEntry[];
|
|
93
|
+
getMarkerBackedScopeIds(): ReadonlySet<string>;
|
|
94
|
+
getSharedWorkflowState(): SharedWorkflowState | null;
|
|
95
|
+
getScopeChromeVisibility(): ScopeChromeVisibilityState;
|
|
96
|
+
/** W10 — class-A visibility policy for a single overlay kind. Returns
|
|
97
|
+
* `null` when no policy has been authored for that kind. */
|
|
98
|
+
getVisibilityPolicy(kind: OverlayKind): OverlayVisibilityPolicy | null;
|
|
99
|
+
/** W10 — full policy set (zero or more entries; never more than one
|
|
100
|
+
* per OverlayKind). Order is deterministic per `OVERLAY_KINDS`. */
|
|
101
|
+
getVisibilityPolicies(): readonly OverlayVisibilityPolicy[];
|
|
102
|
+
|
|
103
|
+
// ---- state mutations (invoked by coordinator dispatch branches) ----
|
|
104
|
+
/** Replace the overlay and sync marker-backed scope set. Invalidates
|
|
105
|
+
* the normalization cache. Returns nothing — the caller is
|
|
106
|
+
* responsible for cache invalidation downstream (guard / scope /
|
|
107
|
+
* markup) and for emitting the matching workflow_* events. */
|
|
108
|
+
replaceOverlay(
|
|
109
|
+
next: WorkflowOverlay | null,
|
|
110
|
+
document: CanonicalDocumentEnvelope,
|
|
111
|
+
): void;
|
|
112
|
+
replaceMetadataDefinitions(defs: readonly WorkflowMetadataDefinition[]): void;
|
|
113
|
+
replaceMetadataEntries(entries: readonly WorkflowMetadataEntry[]): void;
|
|
114
|
+
replaceSharedWorkflowState(state: SharedWorkflowState | null): void;
|
|
115
|
+
replaceScopeChromeVisibility(visibility: ScopeChromeVisibilityState): void;
|
|
116
|
+
/** W10 — set (or clear) a single overlay kind's visibility policy.
|
|
117
|
+
* Returns `true` when the policy set actually changed, `false` on
|
|
118
|
+
* no-op. Callers emit the matching workflow_* event + persist via
|
|
119
|
+
* customXml export only when this returns `true`. */
|
|
120
|
+
replaceVisibilityPolicy(
|
|
121
|
+
kind: OverlayKind,
|
|
122
|
+
policy: OverlayVisibilityPolicy | null,
|
|
123
|
+
): boolean;
|
|
124
|
+
/** W10 — bulk replace the policy set (e.g., on reload or collab
|
|
125
|
+
* state sync). Same semantics as per-kind but atomic. */
|
|
126
|
+
replaceVisibilityPolicies(
|
|
127
|
+
policies: readonly OverlayVisibilityPolicy[],
|
|
128
|
+
): boolean;
|
|
129
|
+
/** W10 — subscribe to any policy-set change (any kind). Listener
|
|
130
|
+
* fires after the store mutation lands, once per settled change.
|
|
131
|
+
* Returns an unsubscribe function. */
|
|
132
|
+
subscribeVisibilityPolicy(listener: () => void): () => void;
|
|
133
|
+
/** W10 — internal signal emitter. Coordinator/runtime invoke this
|
|
134
|
+
* AFTER a successful `replaceVisibilityPolicy` / `replaceVisibilityPolicies`
|
|
135
|
+
* call to notify subscribers. Kept on the store so the policy-notify
|
|
136
|
+
* pipeline stays self-contained (no external bus coupling). */
|
|
137
|
+
notifyVisibilityPolicyChanged(): void;
|
|
138
|
+
|
|
139
|
+
/** X5 — class-A markup-mode policy (single record; null = none). */
|
|
140
|
+
getMarkupModePolicy(): WorkflowMarkupModePolicy | null;
|
|
141
|
+
/** X5 — replace the policy record. Returns `true` on actual change. */
|
|
142
|
+
replaceMarkupModePolicy(policy: WorkflowMarkupModePolicy | null): boolean;
|
|
143
|
+
/** X5 — subscribe to markup-mode policy changes. */
|
|
144
|
+
subscribeMarkupModePolicy(listener: () => void): () => void;
|
|
145
|
+
/** X5 — internal signal emitter for markup-mode changes. */
|
|
146
|
+
notifyMarkupModePolicyChanged(): void;
|
|
147
|
+
|
|
148
|
+
// ---- derivations ----
|
|
149
|
+
/** Normalized overlay for a given document. Returns null when no
|
|
150
|
+
* overlay is set. Uses (document, overlay) reference equality to
|
|
151
|
+
* short-circuit. */
|
|
152
|
+
getNormalizedOverlay(document: CanonicalDocumentEnvelope): WorkflowOverlay | null;
|
|
153
|
+
|
|
154
|
+
/** Structured-clone metadata definitions + entries for API callers
|
|
155
|
+
* so downstream mutations don't leak back into the store. */
|
|
156
|
+
getMetadataSnapshot(): WorkflowMetadataSnapshot;
|
|
157
|
+
|
|
158
|
+
// ---- warning synthesis ----
|
|
159
|
+
/** Pure factory — builds a `workflow_scope_invalidated` warning for
|
|
160
|
+
* a detached scope, including diagnostic + llmMetadata for recovery. */
|
|
161
|
+
createInvalidatedScopeWarning(scope: WorkflowScope): InternalEditorWarning;
|
|
162
|
+
|
|
163
|
+
/** Diff the detached-scope warnings we *want* (one per detached
|
|
164
|
+
* scope in the overlay) against the warnings already in `existing`.
|
|
165
|
+
* Returns the next warning list plus a precise add/clear delta. */
|
|
166
|
+
mergeDetachedScopeWarnings(
|
|
167
|
+
overlay: WorkflowOverlay | null,
|
|
168
|
+
existing: readonly InternalEditorWarning[],
|
|
169
|
+
): MergeDetachedWarningsResult;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ---------------------------------------------------------------- *\
|
|
173
|
+
* Private pure helpers (not exported — internal to the store)
|
|
174
|
+
\* ---------------------------------------------------------------- */
|
|
175
|
+
|
|
176
|
+
function workflowAnchorsEqual(
|
|
177
|
+
left: EditorAnchorProjection,
|
|
178
|
+
right: EditorAnchorProjection,
|
|
179
|
+
): boolean {
|
|
180
|
+
if (left.kind !== right.kind) return false;
|
|
181
|
+
switch (left.kind) {
|
|
182
|
+
case "range":
|
|
183
|
+
return (
|
|
184
|
+
right.kind === "range" &&
|
|
185
|
+
left.from === right.from &&
|
|
186
|
+
left.to === right.to &&
|
|
187
|
+
left.assoc.start === right.assoc.start &&
|
|
188
|
+
left.assoc.end === right.assoc.end
|
|
189
|
+
);
|
|
190
|
+
case "node":
|
|
191
|
+
return right.kind === "node" && left.at === right.at;
|
|
192
|
+
case "detached":
|
|
193
|
+
return (
|
|
194
|
+
right.kind === "detached" &&
|
|
195
|
+
left.reason === right.reason &&
|
|
196
|
+
left.lastKnownRange.from === right.lastKnownRange.from &&
|
|
197
|
+
left.lastKnownRange.to === right.lastKnownRange.to
|
|
198
|
+
);
|
|
199
|
+
default:
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildWarningSignature(warning: InternalEditorWarning): string {
|
|
205
|
+
return JSON.stringify({
|
|
206
|
+
code: warning.code,
|
|
207
|
+
severity: warning.severity,
|
|
208
|
+
message: warning.message,
|
|
209
|
+
source: warning.source,
|
|
210
|
+
featureEntryId: warning.featureEntryId ?? null,
|
|
211
|
+
details: warning.details ?? null,
|
|
212
|
+
affectedAnchor: warning.affectedAnchor ?? null,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function emitMarkerBackedDelta(
|
|
217
|
+
bus: TelemetryBus,
|
|
218
|
+
prior: ReadonlySet<string>,
|
|
219
|
+
next: ReadonlySet<string>,
|
|
220
|
+
): void {
|
|
221
|
+
if (!bus.isEnabled("scope")) return;
|
|
222
|
+
const added: string[] = [];
|
|
223
|
+
const removed: string[] = [];
|
|
224
|
+
for (const id of next) if (!prior.has(id)) added.push(id);
|
|
225
|
+
for (const id of prior) if (!next.has(id)) removed.push(id);
|
|
226
|
+
if (added.length === 0 && removed.length === 0) return;
|
|
227
|
+
bus.emit({
|
|
228
|
+
channel: "scope",
|
|
229
|
+
type: "scope.marker_backed_delta",
|
|
230
|
+
t: 0,
|
|
231
|
+
payload: { added, removed, total: next.size },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ---------------------------------------------------------------- *\
|
|
236
|
+
* Factory
|
|
237
|
+
\* ---------------------------------------------------------------- */
|
|
238
|
+
|
|
239
|
+
export function createOverlayStore(config: OverlayStoreConfig): OverlayStore {
|
|
240
|
+
let overlay: WorkflowOverlay | null = config.initialOverlay
|
|
241
|
+
? structuredClone(config.initialOverlay)
|
|
242
|
+
: null;
|
|
243
|
+
let metadataDefinitions: WorkflowMetadataDefinition[] = structuredClone(
|
|
244
|
+
[...config.initialMetadataDefinitions],
|
|
245
|
+
);
|
|
246
|
+
let metadataEntries: WorkflowMetadataEntry[] = structuredClone(
|
|
247
|
+
[...config.initialMetadataEntries],
|
|
248
|
+
);
|
|
249
|
+
let markerBackedScopeIds: ReadonlySet<string> = new Set<string>();
|
|
250
|
+
let sharedWorkflowState: SharedWorkflowState | null = null;
|
|
251
|
+
let scopeChromeVisibility: ScopeChromeVisibilityState = { mode: "all" };
|
|
252
|
+
// W10 — class-A visibility policies keyed by OverlayKind. Cloned on
|
|
253
|
+
// read so callers cannot mutate stored records.
|
|
254
|
+
let visibilityPolicies: Map<OverlayKind, OverlayVisibilityPolicy> = new Map(
|
|
255
|
+
(config.initialVisibilityPolicies ?? []).map((policy) => [
|
|
256
|
+
policy.kind,
|
|
257
|
+
structuredClone(policy),
|
|
258
|
+
]),
|
|
259
|
+
);
|
|
260
|
+
const visibilityPolicyListeners = new Set<() => void>();
|
|
261
|
+
// X5 · markup-mode policy — single record, not per-kind.
|
|
262
|
+
let markupModePolicy: WorkflowMarkupModePolicy | null =
|
|
263
|
+
config.initialMarkupModePolicy
|
|
264
|
+
? structuredClone(config.initialMarkupModePolicy)
|
|
265
|
+
: null;
|
|
266
|
+
const markupModePolicyListeners = new Set<() => void>();
|
|
267
|
+
|
|
268
|
+
let normalizedOverlayCache:
|
|
269
|
+
| {
|
|
270
|
+
document: CanonicalDocumentEnvelope;
|
|
271
|
+
overlay: WorkflowOverlay;
|
|
272
|
+
normalized: WorkflowOverlay;
|
|
273
|
+
}
|
|
274
|
+
| undefined;
|
|
275
|
+
|
|
276
|
+
function syncMarkerBackedScopeIds(
|
|
277
|
+
document: CanonicalDocumentEnvelope,
|
|
278
|
+
currentOverlay: WorkflowOverlay | null,
|
|
279
|
+
): void {
|
|
280
|
+
const prior = markerBackedScopeIds;
|
|
281
|
+
const presentScopeIds = new Set(collectScopeLocations(document).keys());
|
|
282
|
+
if (!currentOverlay) {
|
|
283
|
+
markerBackedScopeIds = presentScopeIds;
|
|
284
|
+
emitMarkerBackedDelta(config.telemetryBus, prior, markerBackedScopeIds);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const overlayScopeIds = new Set(
|
|
288
|
+
currentOverlay.scopes.map((scope) => scope.scopeId),
|
|
289
|
+
);
|
|
290
|
+
const next = new Set<string>();
|
|
291
|
+
for (const scopeId of markerBackedScopeIds) {
|
|
292
|
+
if (overlayScopeIds.has(scopeId)) {
|
|
293
|
+
next.add(scopeId);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
for (const scopeId of presentScopeIds) {
|
|
297
|
+
if (overlayScopeIds.has(scopeId)) {
|
|
298
|
+
next.add(scopeId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
markerBackedScopeIds = next;
|
|
302
|
+
emitMarkerBackedDelta(config.telemetryBus, prior, next);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function normalizeOverlayForDocument(
|
|
306
|
+
document: CanonicalDocumentEnvelope,
|
|
307
|
+
source: WorkflowOverlay,
|
|
308
|
+
): WorkflowOverlay {
|
|
309
|
+
if (
|
|
310
|
+
normalizedOverlayCache &&
|
|
311
|
+
normalizedOverlayCache.document === document &&
|
|
312
|
+
normalizedOverlayCache.overlay === source
|
|
313
|
+
) {
|
|
314
|
+
return normalizedOverlayCache.normalized;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const scopeIdCounts = new Map<string, number>();
|
|
318
|
+
for (const scope of source.scopes) {
|
|
319
|
+
scopeIdCounts.set(scope.scopeId, (scopeIdCounts.get(scope.scopeId) ?? 0) + 1);
|
|
320
|
+
}
|
|
321
|
+
const locations = collectScopeLocations(document);
|
|
322
|
+
let changed = false;
|
|
323
|
+
const normalizedScopes = source.scopes.map((scope) => {
|
|
324
|
+
if ((scopeIdCounts.get(scope.scopeId) ?? 0) !== 1) {
|
|
325
|
+
return scope;
|
|
326
|
+
}
|
|
327
|
+
const location = locations.get(scope.scopeId);
|
|
328
|
+
const isMarkerBacked = markerBackedScopeIds.has(scope.scopeId);
|
|
329
|
+
let nextAnchor: EditorAnchorProjection | null = null;
|
|
330
|
+
if (
|
|
331
|
+
location &&
|
|
332
|
+
location.startPos !== undefined &&
|
|
333
|
+
location.endPos !== undefined
|
|
334
|
+
) {
|
|
335
|
+
// Coord-06 §13d — preserve the scope's original `assoc` when
|
|
336
|
+
// normalizing marker positions. Pre-§13d this hardcoded
|
|
337
|
+
// `{ start: -1, end: 1 }` (fixed), dropping the caller's
|
|
338
|
+
// `{ start: 1, end: -1 }` (greedy — default) or any other
|
|
339
|
+
// variant authored via `CreateScopeFromBlockIdInput.assoc`.
|
|
340
|
+
// Agents pin signature-block / template-slot scopes with
|
|
341
|
+
// `{ -1, 1 }` via the v3 primitive; that pin is meaningless
|
|
342
|
+
// if normalization erases it every render.
|
|
343
|
+
const preservedAssoc: { start: -1 | 1; end: -1 | 1 } =
|
|
344
|
+
scope.anchor.kind === "range"
|
|
345
|
+
? scope.anchor.assoc
|
|
346
|
+
: { start: -1, end: 1 };
|
|
347
|
+
nextAnchor = {
|
|
348
|
+
kind: "range",
|
|
349
|
+
from: Math.min(location.startPos, location.endPos),
|
|
350
|
+
to: Math.max(location.startPos, location.endPos),
|
|
351
|
+
assoc: preservedAssoc,
|
|
352
|
+
};
|
|
353
|
+
} else if (isMarkerBacked) {
|
|
354
|
+
const lastKnownRange =
|
|
355
|
+
scope.anchor.kind === "range"
|
|
356
|
+
? { from: scope.anchor.from, to: scope.anchor.to }
|
|
357
|
+
: scope.anchor.kind === "node"
|
|
358
|
+
? { from: scope.anchor.at, to: scope.anchor.at }
|
|
359
|
+
: scope.anchor.lastKnownRange;
|
|
360
|
+
nextAnchor = {
|
|
361
|
+
kind: "detached",
|
|
362
|
+
reason:
|
|
363
|
+
location &&
|
|
364
|
+
(location.startPos !== undefined || location.endPos !== undefined)
|
|
365
|
+
? "deleted"
|
|
366
|
+
: "invalidatedByStructureChange",
|
|
367
|
+
lastKnownRange,
|
|
368
|
+
};
|
|
369
|
+
} else {
|
|
370
|
+
return scope;
|
|
371
|
+
}
|
|
372
|
+
if (workflowAnchorsEqual(scope.anchor, nextAnchor)) {
|
|
373
|
+
return scope;
|
|
374
|
+
}
|
|
375
|
+
changed = true;
|
|
376
|
+
return {
|
|
377
|
+
...scope,
|
|
378
|
+
anchor: nextAnchor,
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const normalized = changed
|
|
383
|
+
? { ...source, scopes: normalizedScopes }
|
|
384
|
+
: source;
|
|
385
|
+
normalizedOverlayCache = {
|
|
386
|
+
document,
|
|
387
|
+
overlay: source,
|
|
388
|
+
normalized,
|
|
389
|
+
};
|
|
390
|
+
return normalized;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function createInvalidatedScopeWarning(
|
|
394
|
+
scope: WorkflowScope,
|
|
395
|
+
): InternalEditorWarning {
|
|
396
|
+
const anchor = scope.anchor.kind === "detached" ? scope.anchor : null;
|
|
397
|
+
const subject = scope.label
|
|
398
|
+
? `Workflow scope "${scope.label}" (${scope.scopeId})`
|
|
399
|
+
: `Workflow scope ${scope.scopeId}`;
|
|
400
|
+
const reasonPhrase =
|
|
401
|
+
anchor?.reason === "deleted"
|
|
402
|
+
? "its anchored text was deleted"
|
|
403
|
+
: anchor?.reason === "invalidatedByStructureChange"
|
|
404
|
+
? "document structure changed around it"
|
|
405
|
+
: "its anchor could not be resolved unambiguously";
|
|
406
|
+
const modePhrase =
|
|
407
|
+
scope.mode === "view"
|
|
408
|
+
? "read-only enforcement"
|
|
409
|
+
: `${scope.mode} enforcement`;
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
warningId: `warning:workflow-scope-invalidated:${scope.scopeId}`,
|
|
413
|
+
code: "workflow_scope_invalidated",
|
|
414
|
+
severity: "warning",
|
|
415
|
+
message: `${subject} was invalidated because ${reasonPhrase}. Reapply the scope before relying on ${modePhrase}.`,
|
|
416
|
+
source: "runtime",
|
|
417
|
+
// Internal* collapsed to public flat 2026-04-22 (see
|
|
418
|
+
// `docs/plans/cross-layer-coord-02.md §8`). `scope.anchor` is
|
|
419
|
+
// already public flat; no `toInternalAnchorProjection` wrap.
|
|
420
|
+
affectedAnchor: anchor ?? undefined,
|
|
421
|
+
diagnostic: buildDiagnosticFromLegacyWarningCode("workflow_scope_invalidated", {
|
|
422
|
+
diagnosticId: `warning-diag:workflow-scope-invalidated:${scope.scopeId}`,
|
|
423
|
+
technical: {
|
|
424
|
+
message: `${subject} lost its trusted anchor and is now detached.`,
|
|
425
|
+
source: "runtime",
|
|
426
|
+
},
|
|
427
|
+
details: {
|
|
428
|
+
scopeId: scope.scopeId,
|
|
429
|
+
label: scope.label,
|
|
430
|
+
mode: scope.mode,
|
|
431
|
+
reason: anchor?.reason,
|
|
432
|
+
lastKnownRange: anchor?.lastKnownRange,
|
|
433
|
+
storyTarget: scope.storyTarget,
|
|
434
|
+
reapplySuggested: true,
|
|
435
|
+
},
|
|
436
|
+
affectedAnchor: anchor ? scope.anchor : undefined,
|
|
437
|
+
llmMetadata: {
|
|
438
|
+
userSummary: `${subject} is no longer attached to trusted document content. Reapply the scope before relying on it.`,
|
|
439
|
+
remediation: {
|
|
440
|
+
kind: "fallback",
|
|
441
|
+
suggestion:
|
|
442
|
+
"Locate the intended text using warning.details.scopeId and warning.details.lastKnownRange, then call addScope again for the repaired range.",
|
|
443
|
+
},
|
|
444
|
+
recoveryClass: "requires-input",
|
|
445
|
+
echoedInput: {
|
|
446
|
+
scopeId: scope.scopeId,
|
|
447
|
+
lastKnownRange: anchor?.lastKnownRange,
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
}),
|
|
451
|
+
details: {
|
|
452
|
+
scopeId: scope.scopeId,
|
|
453
|
+
label: scope.label,
|
|
454
|
+
mode: scope.mode,
|
|
455
|
+
reason: anchor?.reason,
|
|
456
|
+
lastKnownRange: anchor?.lastKnownRange,
|
|
457
|
+
storyTarget: scope.storyTarget,
|
|
458
|
+
reapplySuggested: true,
|
|
459
|
+
actionabilityNote:
|
|
460
|
+
"Resolve the intended text again, then reapply the scope; the previous anchor is no longer trusted.",
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function mergeDetachedScopeWarnings(
|
|
466
|
+
normalizedOverlay: WorkflowOverlay | null,
|
|
467
|
+
existing: readonly InternalEditorWarning[],
|
|
468
|
+
): MergeDetachedWarningsResult {
|
|
469
|
+
const detachedScopesById = new Map<string, WorkflowScope>();
|
|
470
|
+
for (const scope of normalizedOverlay?.scopes ?? []) {
|
|
471
|
+
if (scope.anchor.kind === "detached") {
|
|
472
|
+
detachedScopesById.set(scope.scopeId, scope);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const retainedWarnings = existing.filter(
|
|
477
|
+
(warning) => warning.code !== "workflow_scope_invalidated",
|
|
478
|
+
);
|
|
479
|
+
const existingDetachedWarnings = existing.filter(
|
|
480
|
+
(warning) => warning.code === "workflow_scope_invalidated",
|
|
481
|
+
);
|
|
482
|
+
const existingById = new Map(
|
|
483
|
+
existingDetachedWarnings.map((warning) => [warning.warningId, warning] as const),
|
|
484
|
+
);
|
|
485
|
+
const desiredById = new Map(
|
|
486
|
+
[...detachedScopesById.values()].map((scope) => {
|
|
487
|
+
const warning = createInvalidatedScopeWarning(scope);
|
|
488
|
+
return [warning.warningId, warning] as const;
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
const added: InternalEditorWarning[] = [];
|
|
493
|
+
const cleared: Array<{
|
|
494
|
+
warningId: string;
|
|
495
|
+
code: InternalEditorWarning["code"];
|
|
496
|
+
}> = [];
|
|
497
|
+
|
|
498
|
+
for (const [warningId, existingWarning] of existingById) {
|
|
499
|
+
const desiredWarning = desiredById.get(warningId);
|
|
500
|
+
if (!desiredWarning) {
|
|
501
|
+
cleared.push({ warningId, code: existingWarning.code });
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
buildWarningSignature(existingWarning) !== buildWarningSignature(desiredWarning)
|
|
506
|
+
) {
|
|
507
|
+
cleared.push({ warningId, code: existingWarning.code });
|
|
508
|
+
added.push(desiredWarning);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
for (const [warningId, desiredWarning] of desiredById) {
|
|
513
|
+
if (!existingById.has(warningId)) {
|
|
514
|
+
added.push(desiredWarning);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
nextWarnings: [...retainedWarnings, ...desiredById.values()],
|
|
520
|
+
added,
|
|
521
|
+
cleared,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* -------------------- public store surface -------------------- */
|
|
526
|
+
|
|
527
|
+
// Pre-populate the marker-backed set — deferred until after the
|
|
528
|
+
// coordinator first calls `replaceOverlay` with the runtime's
|
|
529
|
+
// initial document; until then the set is empty. The coordinator
|
|
530
|
+
// also invokes `syncInitialMarkerBackedScopeIds` directly from the
|
|
531
|
+
// runtime's constructor so the first getSnapshot() sees a populated
|
|
532
|
+
// set even before any overlay commands dispatch.
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
getOverlay: () => overlay,
|
|
536
|
+
getMetadataDefinitions: () => metadataDefinitions,
|
|
537
|
+
getMetadataEntries: () => metadataEntries,
|
|
538
|
+
getMarkerBackedScopeIds: () => markerBackedScopeIds,
|
|
539
|
+
getSharedWorkflowState: () => sharedWorkflowState,
|
|
540
|
+
getScopeChromeVisibility: () => scopeChromeVisibility,
|
|
541
|
+
getVisibilityPolicy(kind) {
|
|
542
|
+
const entry = visibilityPolicies.get(kind);
|
|
543
|
+
return entry ? structuredClone(entry) : null;
|
|
544
|
+
},
|
|
545
|
+
getVisibilityPolicies() {
|
|
546
|
+
// Deterministic order by OverlayKind — needed for byte-identical
|
|
547
|
+
// re-export when unchanged. Stored keys are the canonical set.
|
|
548
|
+
return [...visibilityPolicies.values()].map((policy) => structuredClone(policy));
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
replaceOverlay(next, document) {
|
|
552
|
+
overlay = next ? structuredClone(next) : null;
|
|
553
|
+
normalizedOverlayCache = undefined;
|
|
554
|
+
syncMarkerBackedScopeIds(document, overlay);
|
|
555
|
+
},
|
|
556
|
+
replaceMetadataDefinitions(defs) {
|
|
557
|
+
metadataDefinitions = structuredClone([...defs]);
|
|
558
|
+
},
|
|
559
|
+
replaceMetadataEntries(entries) {
|
|
560
|
+
metadataEntries = structuredClone([...entries]);
|
|
561
|
+
},
|
|
562
|
+
replaceSharedWorkflowState(state) {
|
|
563
|
+
sharedWorkflowState = state;
|
|
564
|
+
},
|
|
565
|
+
replaceScopeChromeVisibility(visibility) {
|
|
566
|
+
scopeChromeVisibility = visibility;
|
|
567
|
+
},
|
|
568
|
+
replaceVisibilityPolicy(kind, policy) {
|
|
569
|
+
const existing = visibilityPolicies.get(kind) ?? null;
|
|
570
|
+
if (overlayVisibilityPoliciesEqual(existing, policy)) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
if (!policy) {
|
|
574
|
+
visibilityPolicies.delete(kind);
|
|
575
|
+
} else {
|
|
576
|
+
visibilityPolicies.set(kind, structuredClone(policy));
|
|
577
|
+
}
|
|
578
|
+
return true;
|
|
579
|
+
},
|
|
580
|
+
replaceVisibilityPolicies(next) {
|
|
581
|
+
// Fast equality via canonical-ordered serialization of the
|
|
582
|
+
// (kind, enforcement, defaultOn, authoredBy) tuple. Small set
|
|
583
|
+
// (≤ 6 kinds), so the allocation cost is negligible.
|
|
584
|
+
const currentKey = canonicalizePolicyMap(visibilityPolicies);
|
|
585
|
+
const nextMap = new Map<OverlayKind, OverlayVisibilityPolicy>();
|
|
586
|
+
for (const policy of next) {
|
|
587
|
+
nextMap.set(policy.kind, structuredClone(policy));
|
|
588
|
+
}
|
|
589
|
+
const nextKey = canonicalizePolicyMap(nextMap);
|
|
590
|
+
if (currentKey === nextKey) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
visibilityPolicies = nextMap;
|
|
594
|
+
return true;
|
|
595
|
+
},
|
|
596
|
+
subscribeVisibilityPolicy(listener) {
|
|
597
|
+
visibilityPolicyListeners.add(listener);
|
|
598
|
+
return () => {
|
|
599
|
+
visibilityPolicyListeners.delete(listener);
|
|
600
|
+
};
|
|
601
|
+
},
|
|
602
|
+
notifyVisibilityPolicyChanged() {
|
|
603
|
+
// Snapshot-and-iterate so an unsubscribe inside a listener
|
|
604
|
+
// doesn't mutate the set we're walking.
|
|
605
|
+
for (const listener of [...visibilityPolicyListeners]) {
|
|
606
|
+
try {
|
|
607
|
+
listener();
|
|
608
|
+
} catch {
|
|
609
|
+
// Isolate listener errors — one broken subscriber must not
|
|
610
|
+
// block the rest of the notification chain.
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
getMarkupModePolicy() {
|
|
615
|
+
return markupModePolicy ? structuredClone(markupModePolicy) : null;
|
|
616
|
+
},
|
|
617
|
+
replaceMarkupModePolicy(next) {
|
|
618
|
+
if (workflowMarkupModePoliciesEqual(markupModePolicy, next)) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
markupModePolicy = next ? structuredClone(next) : null;
|
|
622
|
+
return true;
|
|
623
|
+
},
|
|
624
|
+
subscribeMarkupModePolicy(listener) {
|
|
625
|
+
markupModePolicyListeners.add(listener);
|
|
626
|
+
return () => {
|
|
627
|
+
markupModePolicyListeners.delete(listener);
|
|
628
|
+
};
|
|
629
|
+
},
|
|
630
|
+
notifyMarkupModePolicyChanged() {
|
|
631
|
+
for (const listener of [...markupModePolicyListeners]) {
|
|
632
|
+
try {
|
|
633
|
+
listener();
|
|
634
|
+
} catch {
|
|
635
|
+
// Listener-throw isolation — same pattern as visibility policy.
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
getNormalizedOverlay(document) {
|
|
641
|
+
if (!overlay) return null;
|
|
642
|
+
return normalizeOverlayForDocument(document, overlay);
|
|
643
|
+
},
|
|
644
|
+
getMetadataSnapshot() {
|
|
645
|
+
return {
|
|
646
|
+
definitions: structuredClone(metadataDefinitions),
|
|
647
|
+
entries: structuredClone(metadataEntries),
|
|
648
|
+
};
|
|
649
|
+
},
|
|
650
|
+
|
|
651
|
+
createInvalidatedScopeWarning,
|
|
652
|
+
mergeDetachedScopeWarnings,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function canonicalizePolicyMap(
|
|
657
|
+
map: ReadonlyMap<OverlayKind, OverlayVisibilityPolicy>,
|
|
658
|
+
): string {
|
|
659
|
+
const sorted = [...map.values()].sort((a, b) => a.kind.localeCompare(b.kind));
|
|
660
|
+
return sorted
|
|
661
|
+
.map(
|
|
662
|
+
(policy) =>
|
|
663
|
+
`${policy.kind}|${policy.enforcement}|${policy.defaultOn}|${
|
|
664
|
+
policy.authoredBy ? `${policy.authoredBy.actorId}@${policy.authoredBy.at}` : ""
|
|
665
|
+
}`,
|
|
666
|
+
)
|
|
667
|
+
.join(";");
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Called once during `createDocumentRuntime` construction to seed the
|
|
672
|
+
* marker-backed scope-id set from the initial document. The store's
|
|
673
|
+
* factory intentionally leaves this empty so the first
|
|
674
|
+
* `replaceOverlay` from the coordinator is the sole writer of the set
|
|
675
|
+
* (simpler invariant). When no overlay is set initially, we still
|
|
676
|
+
* want the set populated so marker-only scopes are discoverable via
|
|
677
|
+
* `getMarkerBackedScopeIds()`. This helper performs that initial sync
|
|
678
|
+
* without firing a telemetry delta (prior set is definitionally empty).
|
|
679
|
+
*/
|
|
680
|
+
export function seedMarkerBackedScopeIds(
|
|
681
|
+
store: OverlayStore,
|
|
682
|
+
document: CanonicalDocumentEnvelope,
|
|
683
|
+
): void {
|
|
684
|
+
// The public surface only exposes `replaceOverlay` as the
|
|
685
|
+
// marker-backed mutator. Seeding without overlay replacement is
|
|
686
|
+
// achieved by calling replaceOverlay(currentOverlay, document) —
|
|
687
|
+
// idempotent on state but triggers the marker-backed sync.
|
|
688
|
+
const current = store.getOverlay();
|
|
689
|
+
store.replaceOverlay(current, document);
|
|
690
|
+
}
|