@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,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope-local formatting seam — **bounded semantic-summary projection**,
|
|
3
|
+
* NOT formatting semantics.
|
|
4
|
+
*
|
|
5
|
+
* ## Contract (tightened 2026-04-22)
|
|
6
|
+
*
|
|
7
|
+
* Layer 08 does NOT own formatting. Layer 03 owns formatting semantics
|
|
8
|
+
* (style cascade, theme colour resolution, run-level accurate mark
|
|
9
|
+
* computation, numbering-instance evaluation, etc.). This seam's job
|
|
10
|
+
* is narrow:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Delegate the cascade-sensitive fields** — `paragraphStyleId`,
|
|
13
|
+
* `outlineLevel`, resolved `numbering.label` — to Layer 03's
|
|
14
|
+
* `resolveEffectiveFormatting` (`src/runtime/formatting/`).
|
|
15
|
+
* When `doc` is threaded, the seam is a pure pass-through for
|
|
16
|
+
* these fields. If Layer 03 disagrees with this seam on any of
|
|
17
|
+
* them, Layer 03 wins.
|
|
18
|
+
*
|
|
19
|
+
* 2. **Compute a scope-level summary** the compiler adds on top — and
|
|
20
|
+
* ONLY on top. Specifically:
|
|
21
|
+
* - `emphasis` — majority-emphasis aggregation across the
|
|
22
|
+
* paragraph's runs (scope-level; Layer 03 only emits per-run
|
|
23
|
+
* accurate marks).
|
|
24
|
+
* - `tableRole` (via `deriveTableCellRole`) — structural role
|
|
25
|
+
* derived from the row's `isHeader` + position.
|
|
26
|
+
* These summaries do NOT re-derive the cascade; they compose
|
|
27
|
+
* scope-level signal from canonical structure Layer 03 already
|
|
28
|
+
* resolves.
|
|
29
|
+
*
|
|
30
|
+
* Anything else — font resolution, character-pair widths, theme
|
|
31
|
+
* colours, list-item run marks, numbering instance advancement — is
|
|
32
|
+
* out of scope. Callers needing those go through Layer 03 directly.
|
|
33
|
+
*
|
|
34
|
+
* ## Fallback posture
|
|
35
|
+
*
|
|
36
|
+
* When the caller only has a bare `ParagraphNode` (no `doc` / no
|
|
37
|
+
* `paragraphIndex`), the seam returns a honest degraded projection
|
|
38
|
+
* built from paragraph fields only. The `formatting` fields the scope
|
|
39
|
+
* then carries are narrower but never wrong. Callers that require the
|
|
40
|
+
* full cascade-accurate projection must thread `doc` + `paragraphIndex`.
|
|
41
|
+
*
|
|
42
|
+
* ## Contracts it honors:
|
|
43
|
+
* F1 — single Layer 03 entry. We call `resolveEffectiveFormatting`
|
|
44
|
+
* for `{kind: "paragraph", blockId}` when `doc` is available.
|
|
45
|
+
* F2 — no state stored here. Every call is pure over its inputs.
|
|
46
|
+
* S9 — returns plain immutable values. No runtime-instance leakage.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import type {
|
|
50
|
+
CanonicalDocument,
|
|
51
|
+
ParagraphNode,
|
|
52
|
+
TableCellNode,
|
|
53
|
+
TableNode,
|
|
54
|
+
TableRowNode,
|
|
55
|
+
} from "../../model/canonical-document.ts";
|
|
56
|
+
import { resolveEffectiveFormatting } from "../formatting/resolve-effective.ts";
|
|
57
|
+
|
|
58
|
+
export interface EffectiveNumbering {
|
|
59
|
+
readonly numberingInstanceId: string;
|
|
60
|
+
readonly level: number;
|
|
61
|
+
readonly label?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface EffectiveFormatting {
|
|
65
|
+
readonly paragraphStyleId?: string;
|
|
66
|
+
readonly emphasis: readonly string[];
|
|
67
|
+
readonly numbering?: EffectiveNumbering;
|
|
68
|
+
readonly outlineLevel?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ScopeFormattingInputs {
|
|
72
|
+
readonly paragraph: ParagraphNode;
|
|
73
|
+
/**
|
|
74
|
+
* Full canonical document. When present, the seam delegates cascade-
|
|
75
|
+
* sensitive fields (`paragraphStyleId`, `outlineLevel`, resolved
|
|
76
|
+
* `numbering.label`) to Layer 03's production entry. When absent, the
|
|
77
|
+
* seam returns the degraded projection built from bare paragraph
|
|
78
|
+
* fields only — still honest, just narrower.
|
|
79
|
+
*/
|
|
80
|
+
readonly document?: CanonicalDocument;
|
|
81
|
+
/**
|
|
82
|
+
* Paragraph-type index matching Layer 03's `blockId` synthesis
|
|
83
|
+
* convention (`paragraph-${N}` counts paragraphs in `doc.content.children`
|
|
84
|
+
* order, not all blocks). Required alongside `document` — the seam
|
|
85
|
+
* calls `resolveEffectiveFormatting(doc, { kind: "paragraph", blockId })`.
|
|
86
|
+
*/
|
|
87
|
+
readonly paragraphIndex?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Majority-emphasis summary across the paragraph's text runs. This is a
|
|
92
|
+
* scope-level aggregation the AI context prompt reads ("is this paragraph
|
|
93
|
+
* mostly bold?"); Layer 03 owns the per-run cascade and does NOT express
|
|
94
|
+
* paragraph-wide emphasis as a derived field. Keeping this local is
|
|
95
|
+
* architecturally correct — it does not duplicate cascade logic.
|
|
96
|
+
*/
|
|
97
|
+
function deriveEmphasisSummary(paragraph: ParagraphNode): string[] {
|
|
98
|
+
const counts = new Map<string, number>();
|
|
99
|
+
let runCount = 0;
|
|
100
|
+
for (const child of paragraph.children) {
|
|
101
|
+
if (child.type !== "text") continue;
|
|
102
|
+
runCount += 1;
|
|
103
|
+
if (!child.marks) continue;
|
|
104
|
+
for (const mark of child.marks) {
|
|
105
|
+
if (
|
|
106
|
+
mark.type === "bold" ||
|
|
107
|
+
mark.type === "italic" ||
|
|
108
|
+
mark.type === "underline" ||
|
|
109
|
+
mark.type === "strikethrough"
|
|
110
|
+
) {
|
|
111
|
+
counts.set(mark.type, (counts.get(mark.type) ?? 0) + 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (runCount === 0) return [];
|
|
116
|
+
const majority: string[] = [];
|
|
117
|
+
for (const [mark, count] of counts) {
|
|
118
|
+
if (count * 2 >= runCount) majority.push(mark);
|
|
119
|
+
}
|
|
120
|
+
return majority.sort();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Degraded-path outline level. Used ONLY when no canonical document is
|
|
125
|
+
* threaded — in that case the cascade-resolved outline level is not
|
|
126
|
+
* reachable; we expose the paragraph's own `outlineLevel` or a bounded
|
|
127
|
+
* `Heading\d` fallback parsed from the styleId. This is narrower than
|
|
128
|
+
* the Layer-03 cascade result (which follows style inheritance chains),
|
|
129
|
+
* and documented as such so downstream consumers can reason about the
|
|
130
|
+
* loss when `document` is omitted.
|
|
131
|
+
*/
|
|
132
|
+
function degradedOutlineLevel(paragraph: ParagraphNode): number | undefined {
|
|
133
|
+
if (typeof paragraph.outlineLevel === "number") return paragraph.outlineLevel;
|
|
134
|
+
const match = /^heading(\d)$/i.exec(paragraph.styleId ?? "");
|
|
135
|
+
if (match) return Math.max(0, Number(match[1]) - 1);
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveViaLayer03(
|
|
140
|
+
doc: CanonicalDocument,
|
|
141
|
+
paragraph: ParagraphNode,
|
|
142
|
+
paragraphIndex: number,
|
|
143
|
+
): Pick<EffectiveFormatting, "paragraphStyleId" | "outlineLevel" | "numbering"> {
|
|
144
|
+
const eff = resolveEffectiveFormatting(doc, {
|
|
145
|
+
kind: "paragraph",
|
|
146
|
+
blockId: `paragraph-${paragraphIndex}`,
|
|
147
|
+
});
|
|
148
|
+
if (eff.kind !== "paragraph") {
|
|
149
|
+
// Defensive: the façade always returns a paragraph branch for a
|
|
150
|
+
// paragraph nodeRef. If the shape drifts, degrade rather than crash.
|
|
151
|
+
return {
|
|
152
|
+
...(paragraph.styleId ? { paragraphStyleId: paragraph.styleId } : {}),
|
|
153
|
+
...(typeof paragraph.outlineLevel === "number"
|
|
154
|
+
? { outlineLevel: paragraph.outlineLevel }
|
|
155
|
+
: {}),
|
|
156
|
+
...(paragraph.numbering
|
|
157
|
+
? {
|
|
158
|
+
numbering: {
|
|
159
|
+
numberingInstanceId: paragraph.numbering.numberingInstanceId,
|
|
160
|
+
level: paragraph.numbering.level,
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
: {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// `EffectiveParagraphFormatting` extends `CanonicalParagraphFormatting`
|
|
167
|
+
// (pPr body) — it does NOT expose `styleId` directly because a paragraph's
|
|
168
|
+
// styleId is a tree field on `ParagraphNode`, not a pPr property. The
|
|
169
|
+
// canonical source of the paragraph style id is `paragraph.styleId`; the
|
|
170
|
+
// cascade uses that id as an input, not an output. We surface the
|
|
171
|
+
// canonical field directly here.
|
|
172
|
+
const paragraphStyleId = paragraph.styleId;
|
|
173
|
+
const outlineLevel =
|
|
174
|
+
typeof eff.paragraph.outlineLevel === "number"
|
|
175
|
+
? eff.paragraph.outlineLevel
|
|
176
|
+
: degradedOutlineLevel(paragraph);
|
|
177
|
+
// Numbering identity (instanceId + level) comes from the canonical
|
|
178
|
+
// paragraph field — Layer 03's `EffectiveNumbering` carries the resolved
|
|
179
|
+
// marker glyph + indentation, not the instance id. We combine the two
|
|
180
|
+
// here: identity from canonical, label from Layer 03's cascade.
|
|
181
|
+
let numbering: EffectiveNumbering | undefined;
|
|
182
|
+
const canonicalNum = paragraph.numbering;
|
|
183
|
+
if (canonicalNum) {
|
|
184
|
+
const label = eff.paragraph.numbering?.marker;
|
|
185
|
+
numbering = {
|
|
186
|
+
numberingInstanceId: canonicalNum.numberingInstanceId,
|
|
187
|
+
level: canonicalNum.level,
|
|
188
|
+
...(label ? { label } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
...(paragraphStyleId ? { paragraphStyleId } : {}),
|
|
193
|
+
...(typeof outlineLevel === "number" ? { outlineLevel } : {}),
|
|
194
|
+
...(numbering ? { numbering } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Narrow paragraph-level projection used by paragraph / heading / list-item
|
|
200
|
+
* / clause scope kinds. Delegates cascade-sensitive fields to Layer 03
|
|
201
|
+
* when `inputs.document` is present; falls back to bare paragraph fields
|
|
202
|
+
* otherwise. Either way, `emphasis` is computed as a scope-level
|
|
203
|
+
* majority-summary — not a cascade result.
|
|
204
|
+
*
|
|
205
|
+
* Callers that pass `document` + `paragraphIndex` receive the cascade-
|
|
206
|
+
* accurate `paragraphStyleId` / `outlineLevel` / `numbering.label`.
|
|
207
|
+
* Callers that omit them receive a projection marked implicitly narrower
|
|
208
|
+
* (the caller is expected to set `partial: true` on the resulting
|
|
209
|
+
* `SemanticScope`).
|
|
210
|
+
*/
|
|
211
|
+
export function resolveEffectiveFormattingForScope(
|
|
212
|
+
paragraphOrInputs: ParagraphNode | ScopeFormattingInputs,
|
|
213
|
+
): EffectiveFormatting {
|
|
214
|
+
const inputs: ScopeFormattingInputs =
|
|
215
|
+
"paragraph" in paragraphOrInputs
|
|
216
|
+
? paragraphOrInputs
|
|
217
|
+
: { paragraph: paragraphOrInputs };
|
|
218
|
+
const { paragraph, document, paragraphIndex } = inputs;
|
|
219
|
+
const emphasis = deriveEmphasisSummary(paragraph);
|
|
220
|
+
if (document && typeof paragraphIndex === "number") {
|
|
221
|
+
const base = resolveViaLayer03(document, paragraph, paragraphIndex);
|
|
222
|
+
return { emphasis, ...base };
|
|
223
|
+
}
|
|
224
|
+
const outlineLevel = degradedOutlineLevel(paragraph);
|
|
225
|
+
return {
|
|
226
|
+
emphasis,
|
|
227
|
+
...(paragraph.styleId ? { paragraphStyleId: paragraph.styleId } : {}),
|
|
228
|
+
...(paragraph.numbering
|
|
229
|
+
? {
|
|
230
|
+
numbering: {
|
|
231
|
+
numberingInstanceId: paragraph.numbering.numberingInstanceId,
|
|
232
|
+
level: paragraph.numbering.level,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
: {}),
|
|
236
|
+
...(outlineLevel !== undefined ? { outlineLevel } : {}),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface TableCellRoleInput {
|
|
241
|
+
readonly row: TableRowNode;
|
|
242
|
+
readonly rowIndex: number;
|
|
243
|
+
readonly table: TableNode;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Structural role for a table cell. This is NOT a formatting cascade
|
|
248
|
+
* question — role derives from the row's `isHeader` flag + positional
|
|
249
|
+
* convention (first row → header, last row → footer, middle rows →
|
|
250
|
+
* body). Keeping the derivation local is architecturally correct;
|
|
251
|
+
* Layer 03's `resolveEffectiveFormatting` for `kind: "table-cell"`
|
|
252
|
+
* exposes cascade-resolved paragraph + run formatting, not row role.
|
|
253
|
+
*/
|
|
254
|
+
export function deriveTableCellRole(
|
|
255
|
+
input: TableCellRoleInput,
|
|
256
|
+
_cell: TableCellNode,
|
|
257
|
+
): "header" | "body" | "footer" {
|
|
258
|
+
if (input.row.isHeader === true) return "header";
|
|
259
|
+
if (input.rowIndex === 0) return "header";
|
|
260
|
+
if (input.rowIndex === input.table.rows.length - 1) return "footer";
|
|
261
|
+
return "body";
|
|
262
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 08 · dependency bridge — upstream types from the shared
|
|
3
|
+
* vocabulary.
|
|
4
|
+
*
|
|
5
|
+
* The adversarial-close pass (2026-04-22) flagged that the compiler was
|
|
6
|
+
* reaching directly into `src/api/public-types.ts` from many files.
|
|
7
|
+
* Honest fix: **every upstream type Layer 08 depends on crosses through
|
|
8
|
+
* this single bridge**. All other files under `src/runtime/scopes/**`
|
|
9
|
+
* import from this module. The compiler-purity guard now enforces the
|
|
10
|
+
* choke point — only this file is allowed to import from
|
|
11
|
+
* `src/api/public-types.ts`.
|
|
12
|
+
*
|
|
13
|
+
* This does NOT eliminate the upward dependency (a full move of these
|
|
14
|
+
* shared-vocabulary types into a runtime-internal home would ripple
|
|
15
|
+
* through ~30 runtime files outside Layer 08's scope; see `docs/plans/
|
|
16
|
+
* cross-layer-coord-08.md` item 5 for the deferred refactor path). It
|
|
17
|
+
* DOES:
|
|
18
|
+
*
|
|
19
|
+
* 1. Reduce the leak from ~11 import sites to 1.
|
|
20
|
+
* 2. Document explicitly which shared-vocabulary types Layer 08 depends on.
|
|
21
|
+
* 3. Make a future type-migration a one-file change.
|
|
22
|
+
*
|
|
23
|
+
* If a new type is needed from `src/api/public-types.ts`, add the
|
|
24
|
+
* re-export here and nowhere else. If a new type CAN be defined in
|
|
25
|
+
* runtime-internal code, prefer that.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type {
|
|
29
|
+
CompatibilityFeatureEntry,
|
|
30
|
+
CompatibilityReport,
|
|
31
|
+
EditorAnchorProjection,
|
|
32
|
+
EditorStoryTarget,
|
|
33
|
+
EffectiveSelectionMode,
|
|
34
|
+
InteractionGuardSnapshot,
|
|
35
|
+
WorkflowBlockedCommandReason,
|
|
36
|
+
WorkflowMetadataEntry,
|
|
37
|
+
WorkflowMetadataSnapshot,
|
|
38
|
+
WorkflowOverlay,
|
|
39
|
+
WorkflowScope,
|
|
40
|
+
WorkflowScopeMetadataField,
|
|
41
|
+
} from "../../api/public-types.ts";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The session-state shape the compiler-service reads. Declared as a
|
|
45
|
+
* Pick rather than re-exporting `EditorSessionState` whole so the
|
|
46
|
+
* dependency surface stays narrow to exactly the one field used.
|
|
47
|
+
*/
|
|
48
|
+
import type { EditorSessionState } from "../../api/public-types.ts";
|
|
49
|
+
export type ScopeSessionState = Pick<EditorSessionState, "documentId">;
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 08 · Slice 4 — unified action-validation composer.
|
|
3
|
+
*
|
|
4
|
+
* Composes four independent policy surfaces into one `ValidationResult`
|
|
5
|
+
* verdict that callers read before applying a replacement:
|
|
6
|
+
*
|
|
7
|
+
* 1. workflow guard — `runtime.getInteractionGuardSnapshot()` plus
|
|
8
|
+
* the scope's overlay-overlap projection
|
|
9
|
+
* (`scope.workflow.effectiveMode` +
|
|
10
|
+
* `scope.workflow.blockedReasons`). Scope-level
|
|
11
|
+
* posture is authoritative because guard-snapshot
|
|
12
|
+
* is selection-scoped; the scope may live in a
|
|
13
|
+
* view-mode overlay even when the cursor does not.
|
|
14
|
+
* 2. preservation — `computePreservationVerdict` reads the document
|
|
15
|
+
* preservation store + scope markers inside the
|
|
16
|
+
* range. Any overlap is a blocker.
|
|
17
|
+
* 3. compatibility — `runtime.getCompatibilityReport()` — current
|
|
18
|
+
* `unsupported-fatal` feature entries surface as
|
|
19
|
+
* warnings (`source: "compat"`); `blockExport`
|
|
20
|
+
* surfaces as a blocker so apply never produces
|
|
21
|
+
* an undeliverable document.
|
|
22
|
+
* 4. AI-action policy — `getAIActionPolicy(actionId)` — `blocked` /
|
|
23
|
+
* `unsupported` support is a blocker; `high` /
|
|
24
|
+
* `critical` risk or `userConfirmation`
|
|
25
|
+
* requirements surface as `approval.required`.
|
|
26
|
+
*
|
|
27
|
+
* Contract highlights (see architecture 08 §"Contracts" + Slice 4
|
|
28
|
+
* handover):
|
|
29
|
+
* - `safe === true` iff `blockedReasons.length === 0`. Warnings do
|
|
30
|
+
* NOT degrade safety — they surface as advisory.
|
|
31
|
+
* - `approval.required` is orthogonal to `safe`. A low-risk action can
|
|
32
|
+
* still need approval; a blocked action can still
|
|
33
|
+
* report `approval: undefined` (the block is the
|
|
34
|
+
* primary signal).
|
|
35
|
+
* - `blockedReasons` strings use a stable prefix grammar:
|
|
36
|
+
* `guard:…` / `preserve:…` / `compat:…` / `policy:…`. UIs and BW
|
|
37
|
+
* workblocks routing keys match on the prefix; grammar is a
|
|
38
|
+
* compatibility event.
|
|
39
|
+
* - Every `ValidationIssue` carries a `source` tag so consumers can
|
|
40
|
+
* route warnings by origin.
|
|
41
|
+
* - Deterministic (S3): no wall-clock reads, no PRNG, no state mutation.
|
|
42
|
+
*
|
|
43
|
+
* Slice 4 ships the composer + v3 adapter graduation for
|
|
44
|
+
* `ai.validateReplacementScope`. Slice 5 will consume `composeScopeValidation`
|
|
45
|
+
* from `replacement/apply.ts` before dispatching any runtime plan.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { CanonicalDocument } from "../../model/canonical-document.ts";
|
|
49
|
+
import type {
|
|
50
|
+
CompatibilityReport,
|
|
51
|
+
InteractionGuardSnapshot,
|
|
52
|
+
} from "./_scope-dependencies.ts";
|
|
53
|
+
import { getAIActionPolicy, type AIAction } from "../workflow/ai-action-policy.ts";
|
|
54
|
+
|
|
55
|
+
import type { EnumeratedScope } from "./enumerate-scopes.ts";
|
|
56
|
+
import {
|
|
57
|
+
buildScopePositionMap,
|
|
58
|
+
type ScopePositionMap,
|
|
59
|
+
} from "./position-map.ts";
|
|
60
|
+
import {
|
|
61
|
+
computePreservationVerdict,
|
|
62
|
+
} from "./preservation-boundary.ts";
|
|
63
|
+
import { resolveScopeRange } from "./scope-range.ts";
|
|
64
|
+
import type {
|
|
65
|
+
ReplacementOperationKind,
|
|
66
|
+
ReplacementScope,
|
|
67
|
+
SemanticScope,
|
|
68
|
+
ValidationApproval,
|
|
69
|
+
ValidationIssue,
|
|
70
|
+
ValidationResult,
|
|
71
|
+
} from "./semantic-scope-types.ts";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Minimal runtime surface the composer reads. Declared inline rather
|
|
75
|
+
* than `Pick<RuntimeApiHandle, …>` because the scope compiler must not
|
|
76
|
+
* import from `src/api/v3/**` (purity guard). The v3 `RuntimeApiHandle`
|
|
77
|
+
* naturally satisfies this interface because its members carry the
|
|
78
|
+
* same `InteractionGuardSnapshot` / `CompatibilityReport` shapes from
|
|
79
|
+
* `src/api/public-types.ts`.
|
|
80
|
+
*/
|
|
81
|
+
export interface ScopeValidationRuntime {
|
|
82
|
+
getInteractionGuardSnapshot(): InteractionGuardSnapshot;
|
|
83
|
+
getCompatibilityReport(): CompatibilityReport;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ComposeScopeValidationInputs {
|
|
87
|
+
readonly scope: SemanticScope;
|
|
88
|
+
readonly operation: ReplacementOperationKind;
|
|
89
|
+
readonly proposedContent: ReplacementScope["proposedContent"];
|
|
90
|
+
readonly runtime: ScopeValidationRuntime;
|
|
91
|
+
/**
|
|
92
|
+
* Canonical document — enables preservation-boundary check. Omitting
|
|
93
|
+
* this field skips the preservation step (no blockers contributed).
|
|
94
|
+
*/
|
|
95
|
+
readonly document?: CanonicalDocument;
|
|
96
|
+
/**
|
|
97
|
+
* Optional precomputed position map — if omitted, built from
|
|
98
|
+
* `document` (allocation cost) or skipped when document is absent.
|
|
99
|
+
*/
|
|
100
|
+
readonly positionMap?: ScopePositionMap;
|
|
101
|
+
/**
|
|
102
|
+
* Optional enumerated-scope entry, used to resolve the scope's
|
|
103
|
+
* canonical range when the scope was compiled with a position map the
|
|
104
|
+
* caller no longer has. Omit to use `scope.handle` + position-map
|
|
105
|
+
* marker lookup only.
|
|
106
|
+
*/
|
|
107
|
+
readonly enumeratedScope?: EnumeratedScope;
|
|
108
|
+
/**
|
|
109
|
+
* AI-action-policy id. Overrides the operation-derived default. When
|
|
110
|
+
* the id is unknown to `getAIActionPolicy`, the policy step contributes
|
|
111
|
+
* no blockers / warnings / approval.
|
|
112
|
+
*/
|
|
113
|
+
readonly actionId?: AIAction;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Default action-id derivation when the caller did not provide one.
|
|
118
|
+
* Maps replacement-operation + proposed-content shape to the closest
|
|
119
|
+
* entry in the 37-op policy matrix.
|
|
120
|
+
*/
|
|
121
|
+
function inferActionId(
|
|
122
|
+
operation: ReplacementOperationKind,
|
|
123
|
+
content: ReplacementScope["proposedContent"],
|
|
124
|
+
): AIAction {
|
|
125
|
+
switch (operation) {
|
|
126
|
+
case "replace":
|
|
127
|
+
return content.kind === "text" ? "rewrite_paragraph" : "generate_text";
|
|
128
|
+
case "insert-before":
|
|
129
|
+
case "insert-after":
|
|
130
|
+
return "generate_text";
|
|
131
|
+
case "split":
|
|
132
|
+
return "rewrite_paragraph";
|
|
133
|
+
case "annotate":
|
|
134
|
+
return "suggest_comment_response";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Scope-level guard step — consults both the scope's own overlay-overlap
|
|
140
|
+
* projection (deepest, most specific signal) and the runtime's
|
|
141
|
+
* selection-scoped `InteractionGuardSnapshot` (catches global posture
|
|
142
|
+
* overrides like read-only + document-mode). The most restrictive signal
|
|
143
|
+
* produces the blocker.
|
|
144
|
+
*/
|
|
145
|
+
function collectGuardVerdict(
|
|
146
|
+
scope: SemanticScope,
|
|
147
|
+
runtime: ComposeScopeValidationInputs["runtime"],
|
|
148
|
+
blockedReasons: string[],
|
|
149
|
+
warnings: ValidationIssue[],
|
|
150
|
+
): void {
|
|
151
|
+
const scopeMode = scope.workflow.effectiveMode;
|
|
152
|
+
if (scopeMode === "view") {
|
|
153
|
+
blockedReasons.push("guard:view-mode-active");
|
|
154
|
+
warnings.push({
|
|
155
|
+
code: "guard:view-mode-active",
|
|
156
|
+
message: "scope is inside a view-mode workflow overlay",
|
|
157
|
+
source: "guard",
|
|
158
|
+
});
|
|
159
|
+
} else if (scopeMode === "blocked") {
|
|
160
|
+
// Coord-06 §13e — `guard:blocked` becomes typed `guard:block-<reason>`
|
|
161
|
+
// so agents can route intelligently. This path is a defensive catchall
|
|
162
|
+
// (scope.workflow.effectiveMode is normally edit/suggest/comment/view;
|
|
163
|
+
// the overlay-compiler doesn't emit "blocked"). Mark with the
|
|
164
|
+
// specific sub-reason so the grammar is consistent across both
|
|
165
|
+
// emission sites.
|
|
166
|
+
blockedReasons.push("guard:block-scope-overlay");
|
|
167
|
+
warnings.push({
|
|
168
|
+
code: "guard:block-scope-overlay",
|
|
169
|
+
message: "scope is inside a blocked workflow overlay",
|
|
170
|
+
source: "guard",
|
|
171
|
+
});
|
|
172
|
+
} else if (scopeMode === "comment") {
|
|
173
|
+
blockedReasons.push("guard:comment-only");
|
|
174
|
+
warnings.push({
|
|
175
|
+
code: "guard:comment-only",
|
|
176
|
+
message: "scope is inside a comment-only workflow overlay",
|
|
177
|
+
source: "guard",
|
|
178
|
+
});
|
|
179
|
+
} else if (scopeMode === "suggest") {
|
|
180
|
+
// Suggest is not a blocker — the call site converts the replacement
|
|
181
|
+
// into a tracked-change in Slice 5. Still emit a warning so the
|
|
182
|
+
// caller can switch to a suggest-shaped apply without surprise.
|
|
183
|
+
warnings.push({
|
|
184
|
+
code: "guard:suggest-mode",
|
|
185
|
+
message:
|
|
186
|
+
"scope is in suggest mode; replacement must be issued as a tracked change",
|
|
187
|
+
source: "guard",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
for (const reason of scope.workflow.blockedReasons ?? []) {
|
|
191
|
+
warnings.push({
|
|
192
|
+
code: `guard:${reason}`,
|
|
193
|
+
message: `workflow overlay signal: ${reason}`,
|
|
194
|
+
source: "guard",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const guard = runtime.getInteractionGuardSnapshot();
|
|
199
|
+
if (
|
|
200
|
+
guard.effectiveMode === "view" &&
|
|
201
|
+
!blockedReasons.includes("guard:view-mode-active")
|
|
202
|
+
) {
|
|
203
|
+
blockedReasons.push("guard:view-mode-active");
|
|
204
|
+
}
|
|
205
|
+
if (guard.effectiveMode === "blocked") {
|
|
206
|
+
// Coord-06 §13e — promote the bare `guard:blocked` blocker to a typed
|
|
207
|
+
// `guard:block-<reason>` suffix so agents can route intelligently on
|
|
208
|
+
// boundary-paragraph / system-paragraph / read-only / protected-range
|
|
209
|
+
// situations. The specific sub-reason is the first code on
|
|
210
|
+
// `guard.blockedReasons` (coordinator's `evaluateBlockedReasons`
|
|
211
|
+
// output). When the array is empty (defensive — shouldn't happen;
|
|
212
|
+
// coordinator sets `effectiveMode: "blocked"` only when at least
|
|
213
|
+
// one blockedReason fires) fall back to `guard:block-unspecified`.
|
|
214
|
+
const primaryCode = guard.blockedReasons?.[0]?.code;
|
|
215
|
+
const suffix = typeof primaryCode === "string" && primaryCode.length > 0
|
|
216
|
+
? primaryCode
|
|
217
|
+
: "unspecified";
|
|
218
|
+
const typedBlocker = `guard:block-${suffix}`;
|
|
219
|
+
if (!blockedReasons.some((existing) => existing === typedBlocker)) {
|
|
220
|
+
blockedReasons.push(typedBlocker);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const reason of guard.blockedReasons ?? []) {
|
|
224
|
+
// `reason` is a `WorkflowBlockedCommandReason` object — the legacy
|
|
225
|
+
// template emitted the bare object here, producing `guard:selection-
|
|
226
|
+
// [object Object]` in serialized warnings. Use `.code` so downstream
|
|
227
|
+
// receivers see a stable string identifier (§13e / coord-09 routing).
|
|
228
|
+
const code = typeof reason === "object" && reason !== null && "code" in reason
|
|
229
|
+
? (reason as { code: unknown }).code
|
|
230
|
+
: reason;
|
|
231
|
+
const codeStr = typeof code === "string" ? code : JSON.stringify(code);
|
|
232
|
+
warnings.push({
|
|
233
|
+
code: `guard:selection-${codeStr}`,
|
|
234
|
+
message: `selection guard signal: ${codeStr}`,
|
|
235
|
+
source: "guard",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function collectPreservationVerdict(
|
|
241
|
+
inputs: ComposeScopeValidationInputs,
|
|
242
|
+
blockedReasons: string[],
|
|
243
|
+
): void {
|
|
244
|
+
const { document, scope, positionMap } = inputs;
|
|
245
|
+
if (!document) return;
|
|
246
|
+
const pm = positionMap ?? buildScopePositionMap(document);
|
|
247
|
+
const range = inputs.enumeratedScope
|
|
248
|
+
? resolveScopeRange(inputs.enumeratedScope, scope.handle, pm)
|
|
249
|
+
: scope.handle.stableRef.kind === "scope-id"
|
|
250
|
+
? (pm.markerScopes.get(scope.handle.stableRef.value) ?? null)
|
|
251
|
+
: null;
|
|
252
|
+
const verdict = computePreservationVerdict(document, range, pm);
|
|
253
|
+
if (!verdict.replaceable) {
|
|
254
|
+
for (const reason of verdict.reasons) {
|
|
255
|
+
blockedReasons.push(`preserve:${reason}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function collectCompatibilityVerdict(
|
|
261
|
+
runtime: ComposeScopeValidationInputs["runtime"],
|
|
262
|
+
blockedReasons: string[],
|
|
263
|
+
warnings: ValidationIssue[],
|
|
264
|
+
): void {
|
|
265
|
+
const report = runtime.getCompatibilityReport();
|
|
266
|
+
if (report.blockExport) {
|
|
267
|
+
blockedReasons.push("compat:block-export");
|
|
268
|
+
}
|
|
269
|
+
for (const entry of report.featureEntries) {
|
|
270
|
+
if (entry.featureClass === "unsupported-fatal") {
|
|
271
|
+
warnings.push({
|
|
272
|
+
code: `compat:unsupported-fatal-${entry.featureKey}`,
|
|
273
|
+
message: entry.message,
|
|
274
|
+
source: "compat",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function collectPolicyVerdict(
|
|
281
|
+
actionId: AIAction,
|
|
282
|
+
blockedReasons: string[],
|
|
283
|
+
warnings: ValidationIssue[],
|
|
284
|
+
): ValidationApproval | undefined {
|
|
285
|
+
// `getAIActionPolicy` throws on unknown actions. The composer absorbs
|
|
286
|
+
// that as "no policy signal" rather than propagating — the caller's
|
|
287
|
+
// actionId choice is a best-effort mapping, not a contract.
|
|
288
|
+
let policy;
|
|
289
|
+
try {
|
|
290
|
+
policy = getAIActionPolicy(actionId);
|
|
291
|
+
} catch {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (policy.support === "blocked") {
|
|
296
|
+
blockedReasons.push(`policy:blocked-${policy.action}`);
|
|
297
|
+
warnings.push({
|
|
298
|
+
code: `policy:blocked-${policy.action}`,
|
|
299
|
+
message: policy.rationale,
|
|
300
|
+
source: "policy",
|
|
301
|
+
});
|
|
302
|
+
} else if (policy.support === "unsupported") {
|
|
303
|
+
blockedReasons.push(`policy:unsupported-${policy.action}`);
|
|
304
|
+
warnings.push({
|
|
305
|
+
code: `policy:unsupported-${policy.action}`,
|
|
306
|
+
message: policy.rationale,
|
|
307
|
+
source: "policy",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Note: `policy.constraints.preservedContentRespect` and
|
|
312
|
+
// `compatibilityValidation` are *enforcement requirements* — the
|
|
313
|
+
// composer already enforces both (preservation step + compatibility
|
|
314
|
+
// step emit the actual blockers/warnings when they fire). Surfacing
|
|
315
|
+
// them as always-on policy advisories would double-report, so we
|
|
316
|
+
// consult them implicitly through the other steps rather than
|
|
317
|
+
// emitting a `policy:*` warning here.
|
|
318
|
+
|
|
319
|
+
const needsApproval =
|
|
320
|
+
policy.risk === "high" ||
|
|
321
|
+
policy.risk === "critical" ||
|
|
322
|
+
policy.requirements.userConfirmation;
|
|
323
|
+
if (needsApproval) {
|
|
324
|
+
return { required: true, reason: policy.rationale };
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Compose a single validation verdict for a proposed scope replacement.
|
|
331
|
+
* Synchronous, deterministic, free of side effects. Safe to call in hot
|
|
332
|
+
* paths; the composer reuses a single position-map when the caller
|
|
333
|
+
* provides one.
|
|
334
|
+
*/
|
|
335
|
+
export function composeScopeValidation(
|
|
336
|
+
inputs: ComposeScopeValidationInputs,
|
|
337
|
+
): ValidationResult {
|
|
338
|
+
const blockedReasons: string[] = [];
|
|
339
|
+
const warnings: ValidationIssue[] = [];
|
|
340
|
+
|
|
341
|
+
collectGuardVerdict(inputs.scope, inputs.runtime, blockedReasons, warnings);
|
|
342
|
+
collectPreservationVerdict(inputs, blockedReasons);
|
|
343
|
+
collectCompatibilityVerdict(inputs.runtime, blockedReasons, warnings);
|
|
344
|
+
|
|
345
|
+
const actionId =
|
|
346
|
+
inputs.actionId ?? inferActionId(inputs.operation, inputs.proposedContent);
|
|
347
|
+
const approval = collectPolicyVerdict(actionId, blockedReasons, warnings);
|
|
348
|
+
|
|
349
|
+
const safe = blockedReasons.length === 0;
|
|
350
|
+
return {
|
|
351
|
+
safe,
|
|
352
|
+
blockedReasons: Object.freeze([...blockedReasons]),
|
|
353
|
+
warnings: Object.freeze([...warnings]),
|
|
354
|
+
...(approval ? { approval } : {}),
|
|
355
|
+
};
|
|
356
|
+
}
|