@beyondwork/docx-react-component 1.0.67 → 1.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -932
- package/package.json +26 -27
- package/src/api/anchor-conversion.ts +43 -0
- package/src/api/editor-state-types.ts +2 -1
- package/src/api/public-types.ts +504 -101
- package/src/api/session-state.ts +4 -0
- package/src/api/v3/README.md +91 -0
- package/src/api/v3/_create.ts +146 -0
- package/src/api/v3/_layer-metadata.ts +362 -0
- package/src/api/v3/_mocks.ts +84 -0
- package/src/api/v3/_runtime-handle.ts +162 -0
- package/src/api/v3/_ux-response.ts +73 -0
- package/src/api/v3/ai/_metadata-audit.ts +225 -0
- package/src/api/v3/ai/attach.ts +235 -0
- package/src/api/v3/ai/bundle.ts +132 -0
- package/src/api/v3/ai/explain.ts +144 -0
- package/src/api/v3/ai/export.ts +54 -0
- package/src/api/v3/ai/inspect.ts +118 -0
- package/src/api/v3/ai/policy.ts +77 -0
- package/src/api/v3/ai/replacement.ts +341 -0
- package/src/api/v3/ai/resolve.ts +133 -0
- package/src/api/v3/index.ts +79 -0
- package/src/api/v3/runtime/chart.ts +310 -0
- package/src/api/v3/runtime/clipboard.ts +81 -0
- package/src/api/v3/runtime/collab.ts +331 -0
- package/src/api/v3/runtime/content.ts +236 -0
- package/src/api/v3/runtime/document.ts +282 -0
- package/src/api/v3/runtime/formatting.ts +186 -0
- package/src/api/v3/runtime/geometry.ts +349 -0
- package/src/api/v3/runtime/layout.ts +108 -0
- package/src/api/v3/runtime/review.ts +129 -0
- package/src/api/v3/runtime/search.ts +74 -0
- package/src/api/v3/runtime/table.ts +63 -0
- package/src/api/v3/runtime/workflow.ts +434 -0
- package/src/api/v3/ui/_context.ts +86 -0
- package/src/api/v3/ui/_create.ts +65 -0
- package/src/api/v3/ui/_types.ts +520 -0
- package/src/api/v3/ui/chrome-composition.ts +342 -0
- package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
- package/src/api/v3/ui/chrome.ts +476 -0
- package/src/api/v3/ui/debug.ts +124 -0
- package/src/api/v3/ui/index.ts +64 -0
- package/src/api/v3/ui/overlays-visibility.ts +170 -0
- package/src/api/v3/ui/overlays.ts +427 -0
- package/src/api/v3/ui/scope.ts +71 -0
- package/src/api/v3/ui/session.ts +100 -0
- package/src/api/v3/ui/surface.ts +170 -0
- package/src/api/v3/ui/viewport.ts +303 -0
- package/src/core/commands/index.ts +28 -6
- package/src/core/commands/list-commands.ts +3 -2
- package/src/core/commands/section-layout-commands.ts +9 -8
- package/src/core/schema/text-schema.ts +16 -0
- package/src/core/selection/mapping.ts +33 -72
- package/src/core/state/editor-state.ts +96 -189
- package/src/index.ts +23 -4
- package/src/io/chart-preview-resolver.ts +1 -1
- package/src/io/docx-session.ts +36 -4797
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +1 -1
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +45 -0
- package/src/io/export/serialize-run-formatting.ts +17 -2
- package/src/io/export/twip.ts +1 -1
- package/src/io/normalize/normalize-text.ts +27 -20
- package/src/io/ooxml/chart/parse-series.ts +1 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +1 -1
- package/src/io/ooxml/classify-embedding.ts +83 -33
- package/src/io/ooxml/parse-fill.ts +1 -1
- package/src/io/ooxml/parse-main-document.ts +71 -1
- package/src/io/ooxml/parse-object.ts +14 -10
- package/src/io/ooxml/parse-run-formatting.ts +47 -1
- package/src/io/ooxml/property-grab-bag.ts +2 -2
- package/src/io/ooxml/units.ts +11 -0
- package/src/io/ooxml/workflow-payload.ts +282 -7
- package/src/model/anchor.ts +85 -0
- package/src/model/canonical-document.ts +351 -15
- package/src/model/chart-types.ts +1 -1
- package/src/model/layout/index.ts +83 -0
- package/src/model/layout/page-graph-types.ts +181 -0
- package/src/model/layout/page-layout-snapshot.ts +105 -0
- package/src/model/layout/resolved-layout-types.ts +47 -0
- package/src/model/layout/runtime-page-graph-types.ts +102 -0
- package/src/model/paragraph-scope-ids.ts +72 -0
- package/src/model/review/comment-types.ts +112 -0
- package/src/model/review/index.ts +2 -0
- package/src/model/review/revision-types.ts +215 -0
- package/src/model/snapshot.ts +32 -0
- package/src/review/store/comment-store.ts +21 -47
- package/src/review/store/revision-types.ts +40 -198
- package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
- package/src/runtime/collab/runtime-collab-sync.ts +13 -3
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
- package/src/runtime/debug/event-ring-buffer.ts +64 -0
- package/src/runtime/debug/probability-sampler.ts +18 -0
- package/src/runtime/debug/runtime-debug-facet.ts +67 -0
- package/src/runtime/debug/stage-tokens.ts +31 -0
- package/src/runtime/debug/telemetry-bus.ts +271 -0
- package/src/runtime/debug/types.ts +275 -0
- package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
- package/src/runtime/document-layout.ts +8 -6
- package/src/runtime/document-runtime.ts +843 -1141
- package/src/runtime/document-search.ts +1 -1
- package/src/runtime/edit-ops/index.ts +1 -1
- package/src/runtime/external-send-runtime.ts +1 -1
- package/src/runtime/formatting/document-lookup.ts +235 -0
- package/src/runtime/formatting/field/registry.ts +41 -0
- package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
- package/src/runtime/formatting/font-resolution.ts +83 -0
- package/src/runtime/formatting/formatting-context.ts +903 -0
- package/src/runtime/formatting/formatting-types.ts +157 -0
- package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
- package/src/runtime/formatting/index.ts +125 -0
- package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
- package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
- package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
- package/src/runtime/formatting/projector.ts +75 -0
- package/src/runtime/formatting/resolve-effective.ts +407 -0
- package/src/runtime/formatting/revision-display.ts +105 -0
- package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
- package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
- package/src/runtime/formatting/telemetry-bridge.ts +106 -0
- package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
- package/src/runtime/geometry/caret-geometry.ts +164 -0
- package/src/runtime/geometry/geometry-facet.ts +364 -0
- package/src/runtime/geometry/geometry-types.ts +256 -0
- package/src/runtime/geometry/hit-test.ts +125 -0
- package/src/runtime/geometry/index.ts +71 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
- package/src/runtime/geometry/invalidation.ts +35 -0
- package/src/runtime/geometry/object-handles.ts +77 -0
- package/src/runtime/geometry/overlay-rects.ts +85 -0
- package/src/runtime/geometry/project-anchors.ts +100 -0
- package/src/runtime/geometry/project-fragments.ts +216 -0
- package/src/runtime/geometry/projector.ts +129 -0
- package/src/runtime/geometry/replacement-envelope.ts +130 -0
- package/src/runtime/geometry/viewport.ts +218 -0
- package/src/runtime/layout/compat-input-ledger.ts +211 -0
- package/src/runtime/layout/index.ts +6 -1
- package/src/runtime/layout/inert-layout-facet.ts +12 -7
- package/src/runtime/layout/layout-engine-instance.ts +189 -11
- package/src/runtime/layout/layout-engine-version.ts +450 -1
- package/src/runtime/layout/layout-facet-types.ts +60 -0
- package/src/runtime/layout/layout-measurement-provider.ts +13 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
- package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
- package/src/runtime/layout/page-graph.ts +62 -209
- package/src/runtime/layout/page-story-resolver.ts +7 -12
- package/src/runtime/layout/paginated-layout-engine.ts +186 -11
- package/src/runtime/layout/project-block-fragments.ts +11 -0
- package/src/runtime/layout/projector.ts +90 -0
- package/src/runtime/layout/public-facet.ts +187 -442
- package/src/runtime/layout/resolved-formatting-state.ts +158 -26
- package/src/runtime/layout/table-render-plan.ts +1 -1
- package/src/runtime/prerender/cache-envelope.ts +6 -1
- package/src/runtime/prerender/prerender-document.ts +18 -23
- package/src/runtime/render/decoration-resolver.ts +1 -1
- package/src/runtime/render/render-frame-types.ts +20 -0
- package/src/runtime/render/render-kernel.ts +94 -25
- package/src/runtime/scopes/_formatting-seam.ts +262 -0
- package/src/runtime/scopes/_scope-dependencies.ts +49 -0
- package/src/runtime/scopes/action-validation.ts +356 -0
- package/src/runtime/scopes/attach-explanation.ts +102 -0
- package/src/runtime/scopes/audit-bundle.ts +71 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
- package/src/runtime/scopes/compile-scope.ts +262 -0
- package/src/runtime/scopes/compiler-service.ts +431 -0
- package/src/runtime/scopes/create-issue.ts +107 -0
- package/src/runtime/scopes/enumerate-scopes.ts +543 -0
- package/src/runtime/scopes/evidence.ts +233 -0
- package/src/runtime/scopes/index.ts +150 -0
- package/src/runtime/scopes/position-map.ts +214 -0
- package/src/runtime/scopes/preservation-boundary.ts +91 -0
- package/src/runtime/scopes/projector.ts +49 -0
- package/src/runtime/scopes/replaceability.ts +87 -0
- package/src/runtime/scopes/replacement/apply.ts +228 -0
- package/src/runtime/scopes/replacement/compile.ts +59 -0
- package/src/runtime/scopes/replacement/propose.ts +42 -0
- package/src/runtime/scopes/resolve-reference.ts +347 -0
- package/src/runtime/scopes/review-bundle.ts +141 -0
- package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
- package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
- package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
- package/src/runtime/scopes/scope-kinds/field.ts +65 -0
- package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
- package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
- package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
- package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
- package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
- package/src/runtime/scopes/scope-kinds/table.ts +55 -0
- package/src/runtime/scopes/scope-range.ts +208 -0
- package/src/runtime/scopes/semantic-scope-types.ts +454 -0
- package/src/runtime/scopes/workflow-overlap.ts +92 -0
- package/src/runtime/selection/index.ts +1 -1
- package/src/runtime/structure-ops/fragment-insert.ts +1 -1
- package/src/runtime/structure-ops/index.ts +1 -1
- package/src/runtime/surface-projection.ts +232 -262
- package/src/runtime/units.ts +4 -2
- package/src/runtime/workflow/coordinator.ts +1348 -0
- package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
- package/src/runtime/workflow/index.ts +25 -0
- package/src/runtime/workflow/markup-mode-policy.ts +98 -0
- package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
- package/src/runtime/workflow/metadata-persistence.ts +306 -0
- package/src/runtime/workflow/metadata-writer.ts +123 -0
- package/src/runtime/workflow/overlay-store.ts +690 -0
- package/src/runtime/workflow/projector.ts +127 -0
- package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
- package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
- package/src/runtime/workflow/rail/types.ts +198 -0
- package/src/runtime/workflow/scope-rail-composer.ts +39 -0
- package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
- package/src/runtime/workflow/scope-writer.ts +188 -0
- package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
- package/src/runtime/workflow/visibility-policy.ts +129 -0
- package/src/session/_sync-legacy.ts +66 -0
- package/src/session/export/embedded-reconstitute.ts +104 -0
- package/src/session/export/export-diagnostics.ts +85 -0
- package/src/session/export/export-validation.ts +110 -0
- package/src/session/export/index.ts +34 -0
- package/src/session/export/preservation-reattach.ts +30 -0
- package/src/session/export/serialize-dispatch.ts +165 -0
- package/src/session/export/stateful-export-pipeline.ts +432 -0
- package/src/session/export/stateful-export.ts +684 -0
- package/src/session/import/canonical-assembly.ts +227 -0
- package/src/session/import/diagnostics-session.ts +54 -0
- package/src/session/import/embedded-discovery.ts +225 -0
- package/src/session/import/embedded-offload.ts +337 -0
- package/src/session/import/import-diagnostics.ts +69 -0
- package/src/session/import/loader-types.ts +313 -0
- package/src/session/import/loader.ts +1834 -0
- package/src/session/import/normalize.ts +195 -0
- package/src/session/import/package-parts.ts +217 -0
- package/src/session/import/package-read.ts +195 -0
- package/src/session/import/parse-orchestration.ts +105 -0
- package/src/session/import/part-constants.ts +70 -0
- package/src/session/import/part-discovery.ts +94 -0
- package/src/session/import/preservation-index.ts +46 -0
- package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
- package/src/session/import/review-import.ts +508 -0
- package/src/session/import/styles-consolidation.ts +281 -0
- package/src/session/import/workflow-scope-import.ts +256 -0
- package/src/session/index.ts +37 -0
- package/src/session/session-state.ts +69 -0
- package/src/session/session.ts +532 -0
- package/src/session/shared/protection.ts +228 -0
- package/src/session/shared/session-utils.ts +82 -0
- package/src/session/types.ts +499 -0
- package/src/shell/chart-snapshots.ts +96 -0
- package/src/shell/media-previews.ts +85 -0
- package/src/shell/overlay-anchor-bridge.ts +53 -0
- package/src/shell/paste-adapter.ts +23 -0
- package/src/shell/ref-commands.ts +1697 -0
- package/src/shell/ref-utilities.ts +48 -0
- package/src/shell/search.ts +51 -0
- package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
- package/src/shell/ui-subscriber-channels.ts +81 -0
- package/src/shell/use-collab-sync.ts +116 -0
- package/src/ui/WordReviewEditor.tsx +496 -2051
- package/src/ui/editor-shell-view.tsx +30 -1
- package/src/ui/editor-surface-controller.tsx +49 -1
- package/src/ui/headless/revision-decoration-model.ts +83 -0
- package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
- package/src/ui/headless/scoped-chrome-policy.ts +2 -2
- package/src/ui/headless/selection-tool-context.ts +1 -1
- package/src/ui/headless/selection-tool-resolver.ts +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +46 -1
- package/src/ui/ui-controller-factory.ts +221 -0
- package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
- package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
- package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
- package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
- package/src/ui-tailwind/chart/render/area.tsx +3 -3
- package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
- package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
- package/src/ui-tailwind/chart/render/combo.tsx +2 -2
- package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
- package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
- package/src/ui-tailwind/chart/render/line.tsx +3 -3
- package/src/ui-tailwind/chart/render/pie.tsx +6 -6
- package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
- package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
- package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
- package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
- package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
- package/src/ui-tailwind/debug/README.md +57 -0
- package/src/ui-tailwind/debug/index.ts +3 -0
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
- package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
- package/src/ui-tailwind/index.ts +0 -5
- package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
- package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
- package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
- package/src/ui-tailwind/review-workspace/types.ts +408 -0
- package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
- package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
- package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
- package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
- package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
- package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
- package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
- package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
- package/src/ui-tailwind/ui-api-context.tsx +43 -0
- package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
- package/src/validation/compatibility-engine.ts +6 -6
- package/src/runtime/styles-cascade.ts +0 -33
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
- /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
- /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
- /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 3 — scope-bundle evidence composer.
|
|
3
|
+
*
|
|
4
|
+
* Given a compiled `SemanticScope` + live document, collect the review
|
|
5
|
+
* items, overlapping workflow scopes, and formatting summary that ride
|
|
6
|
+
* alongside the scope in a `ScopeBundle`.
|
|
7
|
+
*
|
|
8
|
+
* Compatibility flags are left empty at Slice 3 — they land with the
|
|
9
|
+
* unified `ValidationResult` contract in Slice 4. Keeping the field on
|
|
10
|
+
* the returned shape (rather than omitting it) avoids a breaking shape
|
|
11
|
+
* change when Slice 4 fills it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
CanonicalAnchor,
|
|
16
|
+
CanonicalDocument,
|
|
17
|
+
CommentThread,
|
|
18
|
+
RevisionRecord,
|
|
19
|
+
} from "../../model/canonical-document.ts";
|
|
20
|
+
import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
|
|
21
|
+
import type {
|
|
22
|
+
WorkflowMetadataEntry,
|
|
23
|
+
WorkflowOverlay,
|
|
24
|
+
} from "./_scope-dependencies.ts";
|
|
25
|
+
|
|
26
|
+
import { AI_EXPLANATION_METADATA_ID } from "./attach-explanation.ts";
|
|
27
|
+
import { AI_ISSUE_METADATA_ID } from "./create-issue.ts";
|
|
28
|
+
import type { EnumeratedScope } from "./enumerate-scopes.ts";
|
|
29
|
+
import { buildScopePositionMap, type ScopePositionRange } from "./position-map.ts";
|
|
30
|
+
import { rangesOverlap, resolveScopeRange } from "./scope-range.ts";
|
|
31
|
+
import type {
|
|
32
|
+
AIExplanationSummary,
|
|
33
|
+
AIIssueSummary,
|
|
34
|
+
ScopeBundleEvidence,
|
|
35
|
+
SemanticScope,
|
|
36
|
+
} from "./semantic-scope-types.ts";
|
|
37
|
+
|
|
38
|
+
function anchorRange(anchor: CanonicalAnchor): ScopePositionRange | null {
|
|
39
|
+
switch (anchor.kind) {
|
|
40
|
+
case "range":
|
|
41
|
+
return { from: anchor.range.from, to: anchor.range.to };
|
|
42
|
+
case "node":
|
|
43
|
+
return { from: anchor.at, to: anchor.at };
|
|
44
|
+
case "detached":
|
|
45
|
+
return anchor.lastKnownRange;
|
|
46
|
+
default:
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formattingSummaryOf(scope: SemanticScope): string {
|
|
52
|
+
const parts: string[] = [];
|
|
53
|
+
if (scope.formatting.paragraphStyleId) {
|
|
54
|
+
parts.push(`style=${scope.formatting.paragraphStyleId}`);
|
|
55
|
+
}
|
|
56
|
+
if (typeof scope.formatting.outlineLevel === "number") {
|
|
57
|
+
parts.push(`outline=${scope.formatting.outlineLevel}`);
|
|
58
|
+
}
|
|
59
|
+
if (scope.formatting.numbering) {
|
|
60
|
+
parts.push(
|
|
61
|
+
`numbering=${scope.formatting.numbering.numberingInstanceId}:${scope.formatting.numbering.level}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (scope.formatting.tableRole) {
|
|
65
|
+
parts.push(`role=${scope.formatting.tableRole}`);
|
|
66
|
+
}
|
|
67
|
+
if (scope.formatting.emphasis && scope.formatting.emphasis.length > 0) {
|
|
68
|
+
parts.push(`emphasis=${scope.formatting.emphasis.join("+")}`);
|
|
69
|
+
}
|
|
70
|
+
return parts.join(" ") || "(none)";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface EvidenceInputs {
|
|
74
|
+
readonly scope: SemanticScope;
|
|
75
|
+
readonly document: Pick<CanonicalDocument, "content" | "review"> | CanonicalDocumentEnvelope;
|
|
76
|
+
readonly overlay?: WorkflowOverlay | null;
|
|
77
|
+
/**
|
|
78
|
+
* Pre-enumerated scopes used to resolve the bundle's own scope range +
|
|
79
|
+
* overlap with other scopes. When absent, the composer re-enumerates.
|
|
80
|
+
*/
|
|
81
|
+
readonly scopes: readonly EnumeratedScope[];
|
|
82
|
+
/**
|
|
83
|
+
* Workflow-metadata entries to project into the bundle as
|
|
84
|
+
* `aiExplanations` + `aiIssues`. When absent, those fields are omitted.
|
|
85
|
+
* Callers that have a live runtime typically pass
|
|
86
|
+
* `runtime.getWorkflowMetadataSnapshot().entries` through here.
|
|
87
|
+
*/
|
|
88
|
+
readonly workflowMetadataEntries?: readonly WorkflowMetadataEntry[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeSeverity(raw: unknown): AIIssueSummary["severity"] {
|
|
92
|
+
if (raw === "warning" || raw === "error") return raw;
|
|
93
|
+
return "info";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeStatus(raw: unknown): AIIssueSummary["status"] {
|
|
97
|
+
if (raw === "resolved") return raw;
|
|
98
|
+
return "open";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function projectAIExplanation(
|
|
102
|
+
entry: WorkflowMetadataEntry,
|
|
103
|
+
): AIExplanationSummary | null {
|
|
104
|
+
const value = entry.value as
|
|
105
|
+
| { explanationId?: string; text?: string; createdAtUtc?: string }
|
|
106
|
+
| undefined;
|
|
107
|
+
if (!value || typeof value.text !== "string") return null;
|
|
108
|
+
return {
|
|
109
|
+
explanationId:
|
|
110
|
+
typeof value.explanationId === "string" && value.explanationId.length > 0
|
|
111
|
+
? value.explanationId
|
|
112
|
+
: entry.entryId,
|
|
113
|
+
text: value.text,
|
|
114
|
+
...(typeof value.createdAtUtc === "string"
|
|
115
|
+
? { createdAtUtc: value.createdAtUtc }
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function projectAIIssue(
|
|
121
|
+
entry: WorkflowMetadataEntry,
|
|
122
|
+
): AIIssueSummary | null {
|
|
123
|
+
const value = entry.value as
|
|
124
|
+
| {
|
|
125
|
+
issueId?: string;
|
|
126
|
+
summary?: string;
|
|
127
|
+
severity?: unknown;
|
|
128
|
+
status?: unknown;
|
|
129
|
+
createdAtUtc?: string;
|
|
130
|
+
}
|
|
131
|
+
| undefined;
|
|
132
|
+
if (!value || typeof value.summary !== "string") return null;
|
|
133
|
+
return {
|
|
134
|
+
issueId:
|
|
135
|
+
typeof value.issueId === "string" && value.issueId.length > 0
|
|
136
|
+
? value.issueId
|
|
137
|
+
: entry.entryId,
|
|
138
|
+
summary: value.summary,
|
|
139
|
+
severity: normalizeSeverity(value.severity),
|
|
140
|
+
status: normalizeStatus(value.status),
|
|
141
|
+
...(typeof value.createdAtUtc === "string"
|
|
142
|
+
? { createdAtUtc: value.createdAtUtc }
|
|
143
|
+
: {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findEntryForScope(
|
|
148
|
+
scope: SemanticScope,
|
|
149
|
+
scopes: readonly EnumeratedScope[],
|
|
150
|
+
): EnumeratedScope | null {
|
|
151
|
+
return scopes.find((entry) => entry.handle.scopeId === scope.handle.scopeId) ?? null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function composeEvidence(inputs: EvidenceInputs): ScopeBundleEvidence {
|
|
155
|
+
const { scope, document, overlay, scopes } = inputs;
|
|
156
|
+
const positionMap = buildScopePositionMap(document);
|
|
157
|
+
const entry = findEntryForScope(scope, scopes);
|
|
158
|
+
const selfRange = entry
|
|
159
|
+
? resolveScopeRange(entry, scope.handle, positionMap)
|
|
160
|
+
: null;
|
|
161
|
+
|
|
162
|
+
const reviewItemIds: string[] = [];
|
|
163
|
+
const overlappingWorkflowScopeIds: string[] = [];
|
|
164
|
+
const compatibilityFlags: string[] = [];
|
|
165
|
+
|
|
166
|
+
if (selfRange) {
|
|
167
|
+
const review = (document as {
|
|
168
|
+
review?: {
|
|
169
|
+
comments?: Record<string, CommentThread>;
|
|
170
|
+
revisions?: Record<string, RevisionRecord>;
|
|
171
|
+
};
|
|
172
|
+
}).review;
|
|
173
|
+
if (review) {
|
|
174
|
+
for (const thread of Object.values(review.comments ?? {})) {
|
|
175
|
+
const range = anchorRange(thread.anchor);
|
|
176
|
+
if (range && rangesOverlap(selfRange, range)) {
|
|
177
|
+
reviewItemIds.push(thread.commentId);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const rev of Object.values(review.revisions ?? {})) {
|
|
181
|
+
const range = anchorRange(rev.anchor);
|
|
182
|
+
if (range && rangesOverlap(selfRange, range)) {
|
|
183
|
+
reviewItemIds.push(rev.changeId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (overlay?.scopes) {
|
|
189
|
+
for (const other of overlay.scopes) {
|
|
190
|
+
if (other.scopeId === scope.handle.scopeId) continue;
|
|
191
|
+
const markerRange = positionMap.markerScopes.get(other.scopeId);
|
|
192
|
+
if (markerRange && rangesOverlap(selfRange, markerRange)) {
|
|
193
|
+
overlappingWorkflowScopeIds.push(other.scopeId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
reviewItemIds.sort();
|
|
200
|
+
overlappingWorkflowScopeIds.sort();
|
|
201
|
+
|
|
202
|
+
// Adversarial-close item 1 — read-side join for ai.explanation + ai.issue.
|
|
203
|
+
// Filter workflow-metadata entries by scopeId + metadataId; project
|
|
204
|
+
// into narrow summary shapes. Empty arrays are OK to include (the
|
|
205
|
+
// consumer sees "no explanations yet" as explicit rather than "field
|
|
206
|
+
// not supported").
|
|
207
|
+
const aiExplanations: AIExplanationSummary[] = [];
|
|
208
|
+
const aiIssues: AIIssueSummary[] = [];
|
|
209
|
+
if (inputs.workflowMetadataEntries) {
|
|
210
|
+
for (const entry of inputs.workflowMetadataEntries) {
|
|
211
|
+
if (entry.scopeId !== scope.handle.scopeId) continue;
|
|
212
|
+
if (entry.metadataId === AI_EXPLANATION_METADATA_ID) {
|
|
213
|
+
const projected = projectAIExplanation(entry);
|
|
214
|
+
if (projected) aiExplanations.push(projected);
|
|
215
|
+
} else if (entry.metadataId === AI_ISSUE_METADATA_ID) {
|
|
216
|
+
const projected = projectAIIssue(entry);
|
|
217
|
+
if (projected) aiIssues.push(projected);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
aiExplanations.sort((a, b) => a.explanationId.localeCompare(b.explanationId));
|
|
222
|
+
aiIssues.sort((a, b) => a.issueId.localeCompare(b.issueId));
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
formattingSummary: formattingSummaryOf(scope),
|
|
226
|
+
reviewItemIds,
|
|
227
|
+
overlappingWorkflowScopeIds,
|
|
228
|
+
compatibilityFlags,
|
|
229
|
+
...(inputs.workflowMetadataEntries
|
|
230
|
+
? { aiExplanations, aiIssues }
|
|
231
|
+
: {}),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 08 — Semantic Scope Compiler · barrel.
|
|
3
|
+
*
|
|
4
|
+
* See `docs/architecture/08-semantic-scope-compiler.md` for the end-state
|
|
5
|
+
* spec. Slice 1 ships:
|
|
6
|
+
*
|
|
7
|
+
* - core types (`semantic-scope-types.ts`)
|
|
8
|
+
* - compile dispatcher + paragraph/heading/list-item kind files
|
|
9
|
+
* - enumerate-scopes over the main-story paragraph stream
|
|
10
|
+
* - replaceability first cut
|
|
11
|
+
* - debug projector (producer-only; snapshot wiring is a follow-up)
|
|
12
|
+
*
|
|
13
|
+
* Purity invariant: this module must not import from src/api/v3, src/ui-,
|
|
14
|
+
* src/ui/, or src/io. Enforced by
|
|
15
|
+
* scripts/ci-check-scope-compiler-purity.mjs.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { isStructuredReplacementContent } from "./semantic-scope-types.ts";
|
|
19
|
+
export type {
|
|
20
|
+
AIExplanationSummary,
|
|
21
|
+
AIIssueSummary,
|
|
22
|
+
StructuredReplacementContent,
|
|
23
|
+
GeometryRect,
|
|
24
|
+
Replaceability,
|
|
25
|
+
ReplaceabilityLevel,
|
|
26
|
+
ReplacementOperationKind,
|
|
27
|
+
ReplacementPreservePolicy,
|
|
28
|
+
ReplacementScope,
|
|
29
|
+
RuntimeOperationPlan,
|
|
30
|
+
RuntimeOperationStep,
|
|
31
|
+
RuntimeOperationStepKind,
|
|
32
|
+
ScopeActionAudit,
|
|
33
|
+
ScopeBundle,
|
|
34
|
+
ScopeBundleEvidence,
|
|
35
|
+
ScopeBundleNeighborhood,
|
|
36
|
+
ScopeCompilerDebugEntry,
|
|
37
|
+
ScopeHandle,
|
|
38
|
+
ScopeProvenance,
|
|
39
|
+
ScopeStableRef,
|
|
40
|
+
SemanticScope,
|
|
41
|
+
SemanticScopeAudit,
|
|
42
|
+
SemanticScopeContent,
|
|
43
|
+
SemanticScopeFormatting,
|
|
44
|
+
SemanticScopeGeometry,
|
|
45
|
+
SemanticScopeKind,
|
|
46
|
+
SemanticScopeLayout,
|
|
47
|
+
SemanticScopeLineSpan,
|
|
48
|
+
SemanticScopeNumbering,
|
|
49
|
+
SemanticScopeWorkflow,
|
|
50
|
+
ValidationApproval,
|
|
51
|
+
ValidationIssue,
|
|
52
|
+
ValidationResult,
|
|
53
|
+
} from "./semantic-scope-types.ts";
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
buildParagraphIndexMap,
|
|
57
|
+
buildSectionIndexByBlockIndex,
|
|
58
|
+
compileScope,
|
|
59
|
+
type CompileScopeOptions,
|
|
60
|
+
} from "./compile-scope.ts";
|
|
61
|
+
export {
|
|
62
|
+
compileScopeBundle,
|
|
63
|
+
compileScopeBundleById,
|
|
64
|
+
type ScopeBundleInputs,
|
|
65
|
+
} from "./compile-scope-bundle.ts";
|
|
66
|
+
export {
|
|
67
|
+
enumerateScopes,
|
|
68
|
+
type CommentThreadEnumeratedScope,
|
|
69
|
+
type EnumerateScopesInputs,
|
|
70
|
+
type EnumeratedScope,
|
|
71
|
+
type FieldEnumeratedScope,
|
|
72
|
+
type ParagraphLikeEnumeratedScope,
|
|
73
|
+
type RevisionEnumeratedScope,
|
|
74
|
+
type TableCellEnumeratedScope,
|
|
75
|
+
type TableEnumeratedScope,
|
|
76
|
+
type TableRowEnumeratedScope,
|
|
77
|
+
} from "./enumerate-scopes.ts";
|
|
78
|
+
export { composeEvidence, type EvidenceInputs } from "./evidence.ts";
|
|
79
|
+
export {
|
|
80
|
+
buildScopePositionMap,
|
|
81
|
+
computeBlockPositions,
|
|
82
|
+
type BlockPositionEntry,
|
|
83
|
+
type ScopePositionMap,
|
|
84
|
+
type ScopePositionRange,
|
|
85
|
+
} from "./position-map.ts";
|
|
86
|
+
export {
|
|
87
|
+
composeReviewBundle,
|
|
88
|
+
type ComposeReviewBundleInputs,
|
|
89
|
+
type ReviewBundle,
|
|
90
|
+
type ReviewBundleCommentSummary,
|
|
91
|
+
type ReviewBundleRevisionSummary,
|
|
92
|
+
} from "./review-bundle.ts";
|
|
93
|
+
export {
|
|
94
|
+
resolveReference,
|
|
95
|
+
type ResolveReferenceInputs,
|
|
96
|
+
type ResolveReferenceResult,
|
|
97
|
+
type ScopeReference,
|
|
98
|
+
} from "./resolve-reference.ts";
|
|
99
|
+
export { deriveReplaceability } from "./replaceability.ts";
|
|
100
|
+
export {
|
|
101
|
+
composeScopeValidation,
|
|
102
|
+
type ComposeScopeValidationInputs,
|
|
103
|
+
} from "./action-validation.ts";
|
|
104
|
+
export {
|
|
105
|
+
computePreservationVerdict,
|
|
106
|
+
type PreservationVerdict,
|
|
107
|
+
} from "./preservation-boundary.ts";
|
|
108
|
+
export { projectScopeDebugEntry, type ProjectScopeOptions } from "./projector.ts";
|
|
109
|
+
export {
|
|
110
|
+
buildScopeActionAudit,
|
|
111
|
+
emitScopeActionAudit,
|
|
112
|
+
type EmitScopeActionAuditInputs,
|
|
113
|
+
} from "./audit-bundle.ts";
|
|
114
|
+
export {
|
|
115
|
+
proposeReplacement,
|
|
116
|
+
type ReplacementProposalInput,
|
|
117
|
+
} from "./replacement/propose.ts";
|
|
118
|
+
export {
|
|
119
|
+
compileReplacement,
|
|
120
|
+
type CompileReplacementInputs,
|
|
121
|
+
} from "./replacement/compile.ts";
|
|
122
|
+
export {
|
|
123
|
+
applyScopeReplacement,
|
|
124
|
+
type ApplyScopeReplacementInputs,
|
|
125
|
+
type ApplyScopeReplacementResult,
|
|
126
|
+
type ApplyScopeReplacementSink,
|
|
127
|
+
} from "./replacement/apply.ts";
|
|
128
|
+
export {
|
|
129
|
+
attachExplanation,
|
|
130
|
+
AI_EXPLANATION_METADATA_ID,
|
|
131
|
+
type AttachExplanationInput,
|
|
132
|
+
type AttachExplanationResult,
|
|
133
|
+
} from "./attach-explanation.ts";
|
|
134
|
+
export {
|
|
135
|
+
createIssue,
|
|
136
|
+
AI_ISSUE_METADATA_ID,
|
|
137
|
+
type CreateIssueInput,
|
|
138
|
+
type CreateIssueResult,
|
|
139
|
+
type IssueSeverity,
|
|
140
|
+
type IssueStatus,
|
|
141
|
+
} from "./create-issue.ts";
|
|
142
|
+
export {
|
|
143
|
+
createScopeCompilerService,
|
|
144
|
+
type ApplyReplacementRequest,
|
|
145
|
+
type AttachExplanationRequest,
|
|
146
|
+
type CompileScopeByIdResult,
|
|
147
|
+
type CompilerServiceRuntime,
|
|
148
|
+
type CreateIssueRequest,
|
|
149
|
+
type ScopeCompilerService,
|
|
150
|
+
} from "./compiler-service.ts";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 3 — scope position map.
|
|
3
|
+
*
|
|
4
|
+
* Computes a `{ from, to }` range in PM-style document coordinates for every
|
|
5
|
+
* enumerated scope. Offset / range / marker-backed overlap lookups consume
|
|
6
|
+
* it. The algorithm mirrors `src/runtime/scope-resolver.ts::walkParagraphs`
|
|
7
|
+
* so derived and marker-backed positions line up byte-for-byte.
|
|
8
|
+
*
|
|
9
|
+
* Two precision tiers shipped here:
|
|
10
|
+
* - Block ranges — one `[from, to]` per document-root block, matching the
|
|
11
|
+
* canonical walker (`scope-resolver.ts::walkParagraphs`). Used for
|
|
12
|
+
* paragraph-like and table-like kinds.
|
|
13
|
+
* - Inline ranges — one `[from, to]` per paragraph-child slot. Used for
|
|
14
|
+
* nested inline kinds (fields) so scope resolution can snap to the
|
|
15
|
+
* actual field span inside a paragraph rather than the whole paragraph.
|
|
16
|
+
*
|
|
17
|
+
* Pagination / geometric enrichment lives in layers 04 + 05; this map is
|
|
18
|
+
* purely canonical walk arithmetic.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
CanonicalDocument,
|
|
23
|
+
DocumentRootNode,
|
|
24
|
+
InlineNode,
|
|
25
|
+
ParagraphNode,
|
|
26
|
+
TableNode,
|
|
27
|
+
} from "../../model/canonical-document.ts";
|
|
28
|
+
import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
|
|
29
|
+
|
|
30
|
+
import { collectScopeLocations } from "../workflow/scope-resolver.ts";
|
|
31
|
+
|
|
32
|
+
export interface ScopePositionRange {
|
|
33
|
+
readonly from: number;
|
|
34
|
+
readonly to: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function inlineLength(node: InlineNode): number {
|
|
38
|
+
switch (node.type) {
|
|
39
|
+
case "text":
|
|
40
|
+
return Array.from(node.text).length;
|
|
41
|
+
case "hyperlink":
|
|
42
|
+
case "field":
|
|
43
|
+
return node.children.reduce(
|
|
44
|
+
(total, child) => total + inlineLength(child as InlineNode),
|
|
45
|
+
0,
|
|
46
|
+
);
|
|
47
|
+
case "bookmark_start":
|
|
48
|
+
case "bookmark_end":
|
|
49
|
+
case "scope_marker_start":
|
|
50
|
+
case "scope_marker_end":
|
|
51
|
+
return 0;
|
|
52
|
+
default:
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BlockPositionEntry {
|
|
58
|
+
readonly blockIndex: number;
|
|
59
|
+
readonly from: number;
|
|
60
|
+
readonly to: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Walk the root's block children and compute each block's start/end
|
|
65
|
+
* position. Matches the walker inside `scope-resolver.ts` — cursor starts
|
|
66
|
+
* at 0, each paragraph's content length adds to the cursor, and every
|
|
67
|
+
* non-last block adds 1 for its boundary.
|
|
68
|
+
*/
|
|
69
|
+
export function computeBlockPositions(
|
|
70
|
+
document: Pick<CanonicalDocument, "content"> | CanonicalDocumentEnvelope,
|
|
71
|
+
): BlockPositionEntry[] {
|
|
72
|
+
const envelope = document as CanonicalDocumentEnvelope;
|
|
73
|
+
const root: DocumentRootNode =
|
|
74
|
+
"content" in envelope
|
|
75
|
+
? (envelope.content as DocumentRootNode)
|
|
76
|
+
: (document as unknown as DocumentRootNode);
|
|
77
|
+
|
|
78
|
+
const out: BlockPositionEntry[] = [];
|
|
79
|
+
let cursor = 0;
|
|
80
|
+
for (let index = 0; index < root.children.length; index += 1) {
|
|
81
|
+
const block = root.children[index];
|
|
82
|
+
const from = cursor;
|
|
83
|
+
let length = 0;
|
|
84
|
+
if (block && block.type === "paragraph") {
|
|
85
|
+
length = (block as ParagraphNode).children.reduce(
|
|
86
|
+
(total, child) => total + inlineLength(child as InlineNode),
|
|
87
|
+
0,
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
// Tables + other block atoms occupy one slot in the document walker.
|
|
91
|
+
length = 1;
|
|
92
|
+
}
|
|
93
|
+
cursor += length;
|
|
94
|
+
const to = cursor;
|
|
95
|
+
out.push({ blockIndex: index, from, to });
|
|
96
|
+
if (index < root.children.length - 1) {
|
|
97
|
+
cursor += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build inline-offset ranges for every child of every paragraph. The key
|
|
105
|
+
* convention matches the `field` enumeration in `enumerate-scopes.ts`:
|
|
106
|
+
* `${blockIndex}:${inlineIndex}`. Zero-width inlines (bookmarks, markers)
|
|
107
|
+
* get `[cursor, cursor]`; real inlines get `[cursor, cursor + length]`.
|
|
108
|
+
*/
|
|
109
|
+
export function computeInlinePositions(
|
|
110
|
+
document: Pick<CanonicalDocument, "content"> | CanonicalDocumentEnvelope,
|
|
111
|
+
blocks: readonly BlockPositionEntry[],
|
|
112
|
+
): Map<string, ScopePositionRange> {
|
|
113
|
+
const envelope = document as CanonicalDocumentEnvelope;
|
|
114
|
+
const root: DocumentRootNode =
|
|
115
|
+
"content" in envelope
|
|
116
|
+
? (envelope.content as DocumentRootNode)
|
|
117
|
+
: (document as unknown as DocumentRootNode);
|
|
118
|
+
const out = new Map<string, ScopePositionRange>();
|
|
119
|
+
for (let idx = 0; idx < root.children.length; idx += 1) {
|
|
120
|
+
const block = root.children[idx];
|
|
121
|
+
if (!block || block.type !== "paragraph") continue;
|
|
122
|
+
const blockEntry = blocks[idx];
|
|
123
|
+
if (!blockEntry) continue;
|
|
124
|
+
let cursor = blockEntry.from;
|
|
125
|
+
const paragraph = block as ParagraphNode;
|
|
126
|
+
for (let i = 0; i < paragraph.children.length; i += 1) {
|
|
127
|
+
const child = paragraph.children[i] as InlineNode;
|
|
128
|
+
const length = inlineLength(child);
|
|
129
|
+
out.set(`${idx}:${i}`, { from: cursor, to: cursor + length });
|
|
130
|
+
cursor += length;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Range for a single table row, computed as a proportional subrange of the
|
|
138
|
+
* table's 1-slot canonical range. Tables are collapsed into a single walker
|
|
139
|
+
* slot (`+1`) so precise row/cell offsets do not exist in canonical
|
|
140
|
+
* coordinates; we synthesize deterministic non-overlapping subranges here
|
|
141
|
+
* using a scaled `[0, 1)` partitioning against `table.rows.length`.
|
|
142
|
+
*
|
|
143
|
+
* The returned range carries fractional `from` / `to`. Callers that need
|
|
144
|
+
* integer positions should treat the row's container block as authoritative
|
|
145
|
+
* and rely on specificity ordering (see `resolve-reference.ts`). These
|
|
146
|
+
* fractional ranges exist so `rangesOverlap` can distinguish one row from
|
|
147
|
+
* another within the same table.
|
|
148
|
+
*/
|
|
149
|
+
export function computeTableRowRange(
|
|
150
|
+
table: TableNode,
|
|
151
|
+
rowIndex: number,
|
|
152
|
+
tableRange: ScopePositionRange,
|
|
153
|
+
): ScopePositionRange {
|
|
154
|
+
const rowCount = Math.max(1, table.rows.length);
|
|
155
|
+
const span = tableRange.to - tableRange.from;
|
|
156
|
+
if (span <= 0) {
|
|
157
|
+
// Degenerate — fall back to identity. This preserves the pre-fix
|
|
158
|
+
// behaviour (tie on range) but marks each row with a unique offset
|
|
159
|
+
// within the tie via `scopeSpecificity`.
|
|
160
|
+
return tableRange;
|
|
161
|
+
}
|
|
162
|
+
const step = span / rowCount;
|
|
163
|
+
const from = tableRange.from + step * rowIndex;
|
|
164
|
+
const to = tableRange.from + step * (rowIndex + 1);
|
|
165
|
+
return { from, to };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Range for a single cell, computed as a proportional subrange of the
|
|
170
|
+
* enclosing row's range.
|
|
171
|
+
*/
|
|
172
|
+
export function computeTableCellRange(
|
|
173
|
+
row: TableNode["rows"][number],
|
|
174
|
+
cellIndex: number,
|
|
175
|
+
rowRange: ScopePositionRange,
|
|
176
|
+
): ScopePositionRange {
|
|
177
|
+
const cellCount = Math.max(1, row.cells.length);
|
|
178
|
+
const span = rowRange.to - rowRange.from;
|
|
179
|
+
if (span <= 0) return rowRange;
|
|
180
|
+
const step = span / cellCount;
|
|
181
|
+
const from = rowRange.from + step * cellIndex;
|
|
182
|
+
const to = rowRange.from + step * (cellIndex + 1);
|
|
183
|
+
return { from, to };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface ScopePositionMap {
|
|
187
|
+
/** Marker-backed scopeId → PM-position range. */
|
|
188
|
+
readonly markerScopes: ReadonlyMap<string, ScopePositionRange>;
|
|
189
|
+
/** Derived block scopeId → PM-position range. `blockIndex` is the key. */
|
|
190
|
+
readonly blocks: ReadonlyMap<number, ScopePositionRange>;
|
|
191
|
+
/** Inline-position ranges, keyed `${blockIndex}:${inlineIndex}`. */
|
|
192
|
+
readonly inlines: ReadonlyMap<string, ScopePositionRange>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function buildScopePositionMap(
|
|
196
|
+
document: Pick<CanonicalDocument, "content"> | CanonicalDocumentEnvelope,
|
|
197
|
+
): ScopePositionMap {
|
|
198
|
+
const locations = collectScopeLocations(document);
|
|
199
|
+
const markerScopes = new Map<string, ScopePositionRange>();
|
|
200
|
+
for (const [scopeId, loc] of locations) {
|
|
201
|
+
if (loc.startPos === undefined || loc.endPos === undefined) continue;
|
|
202
|
+
markerScopes.set(scopeId, {
|
|
203
|
+
from: Math.min(loc.startPos, loc.endPos),
|
|
204
|
+
to: Math.max(loc.startPos, loc.endPos),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const blockEntries = computeBlockPositions(document);
|
|
208
|
+
const blocks = new Map<number, ScopePositionRange>();
|
|
209
|
+
for (const entry of blockEntries) {
|
|
210
|
+
blocks.set(entry.blockIndex, { from: entry.from, to: entry.to });
|
|
211
|
+
}
|
|
212
|
+
const inlines = computeInlinePositions(document, blockEntries);
|
|
213
|
+
return { markerScopes, blocks, inlines };
|
|
214
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 08 — preservation-boundary helper (Slice 4 interim).
|
|
3
|
+
*
|
|
4
|
+
* Computes whether a scope's canonical range crosses any preserve-only
|
|
5
|
+
* boundary that would be destroyed by a replace/split/insert operation.
|
|
6
|
+
* Sources consulted:
|
|
7
|
+
*
|
|
8
|
+
* - `document.preservation.opaqueFragments` — `OpaqueFragmentRecord[]`
|
|
9
|
+
* with `lastKnownRange` + `featureClass: "preserve-only"`. Any
|
|
10
|
+
* overlap is a blocker.
|
|
11
|
+
* - `document.preservation.packageParts` — package-level parts
|
|
12
|
+
* (image blobs, embed parts). Not range-keyed; their presence is
|
|
13
|
+
* not by itself a scope-level blocker — the opaque-fragment records
|
|
14
|
+
* are the scope-visible projection of package-level preservation.
|
|
15
|
+
* - scope markers inside the target range (handled via the scope's own
|
|
16
|
+
* range computation — scope markers that bracket THIS scope are the
|
|
17
|
+
* scope's identity; markers *inside* the range would be destroyed by
|
|
18
|
+
* a replace, and are surfaced here).
|
|
19
|
+
*
|
|
20
|
+
* Scope-local, plain-value, synchronous. Purity allowlist
|
|
21
|
+
* (`scripts/ci-check-scope-compiler-purity.mjs`) already permits imports
|
|
22
|
+
* from `src/model/**`.
|
|
23
|
+
*
|
|
24
|
+
* Slice 7 can extract this helper into `src/preservation/**` if a
|
|
25
|
+
* cross-layer home makes sense. Until then it stays scope-local so the
|
|
26
|
+
* composer has no cross-lane dependency.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { CanonicalDocument } from "../../model/canonical-document.ts";
|
|
30
|
+
import { findOpaqueFragmentsIntersectingRange } from "../../preservation/store.ts";
|
|
31
|
+
|
|
32
|
+
import type { ScopePositionMap, ScopePositionRange } from "./position-map.ts";
|
|
33
|
+
|
|
34
|
+
export interface PreservationVerdict {
|
|
35
|
+
readonly replaceable: boolean;
|
|
36
|
+
readonly reasons: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return the preservation verdict for a scope's canonical range. The
|
|
41
|
+
* returned `reasons` array carries stable suffix keys that the composer
|
|
42
|
+
* prefixes with `preserve:` before surfacing them on
|
|
43
|
+
* `ValidationResult.blockedReasons`.
|
|
44
|
+
*
|
|
45
|
+
* Callers without a position map or without a resolved range get a
|
|
46
|
+
* permissive verdict — the composer treats that as "preservation signal
|
|
47
|
+
* unavailable, no blockers from this source". The architecture contract
|
|
48
|
+
* (S4 — validate before apply) still requires callers to have a resolved
|
|
49
|
+
* scope range before any apply; this helper is not the place to enforce
|
|
50
|
+
* that, it's the composer's.
|
|
51
|
+
*/
|
|
52
|
+
export function computePreservationVerdict(
|
|
53
|
+
document: CanonicalDocument | null | undefined,
|
|
54
|
+
range: ScopePositionRange | null,
|
|
55
|
+
positionMap: ScopePositionMap | null | undefined,
|
|
56
|
+
): PreservationVerdict {
|
|
57
|
+
if (!document || !range) {
|
|
58
|
+
return { replaceable: true, reasons: [] };
|
|
59
|
+
}
|
|
60
|
+
const reasons: string[] = [];
|
|
61
|
+
|
|
62
|
+
const fragments = findOpaqueFragmentsIntersectingRange(
|
|
63
|
+
document.preservation,
|
|
64
|
+
{ from: range.from, to: range.to },
|
|
65
|
+
);
|
|
66
|
+
for (const fragment of fragments) {
|
|
67
|
+
reasons.push(
|
|
68
|
+
`opaque-fragment:${fragment.fragmentId}:${fragment.payloadKind}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (positionMap) {
|
|
73
|
+
for (const [scopeId, markerRange] of positionMap.markerScopes) {
|
|
74
|
+
// A marker range strictly inside the target range would be
|
|
75
|
+
// destroyed by replace. Equality (marker pair bracketing the scope
|
|
76
|
+
// itself) is *not* a blocker — that marker IS the scope's identity.
|
|
77
|
+
if (
|
|
78
|
+
markerRange.from > range.from &&
|
|
79
|
+
markerRange.to < range.to
|
|
80
|
+
) {
|
|
81
|
+
reasons.push(`scope-marker-inside:${scopeId}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
reasons.sort();
|
|
87
|
+
return {
|
|
88
|
+
replaceable: reasons.length === 0,
|
|
89
|
+
reasons: Object.freeze(reasons),
|
|
90
|
+
};
|
|
91
|
+
}
|