@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,170 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
EditorAnchorProjection,
|
|
5
|
+
ScopeIssueAction,
|
|
6
|
+
WorkflowScopeMode,
|
|
7
|
+
} from "../../api/public-types.ts";
|
|
8
|
+
import {
|
|
9
|
+
cycleScopeIndex,
|
|
10
|
+
shouldHandleScopeNavKey,
|
|
11
|
+
} from "../chrome-overlay/scope-keyboard-cycle";
|
|
12
|
+
|
|
13
|
+
export interface UseScopeCardStateOptions {
|
|
14
|
+
layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
|
|
15
|
+
/**
|
|
16
|
+
* Layer-06 workflow facet — canonical source of scope card models.
|
|
17
|
+
* Required by the keyboard-nav loop + Ask-agent handler after Slice 4
|
|
18
|
+
* rail-seam inversion removed those methods from `layoutFacet`.
|
|
19
|
+
*/
|
|
20
|
+
workflowFacet?: import("../../runtime/workflow/rail/types.ts").WorkflowFacet;
|
|
21
|
+
onScopeModeChangeRequested?: (payload: {
|
|
22
|
+
scopeId: string;
|
|
23
|
+
mode: WorkflowScopeMode;
|
|
24
|
+
}) => void;
|
|
25
|
+
onScopeIssueActionRequested?: (payload: {
|
|
26
|
+
scopeId: string;
|
|
27
|
+
issueId: string;
|
|
28
|
+
action: ScopeIssueAction;
|
|
29
|
+
}) => void;
|
|
30
|
+
onScopeAcceptSuggestionGroup?: (payload: {
|
|
31
|
+
scopeId: string;
|
|
32
|
+
groupId: string;
|
|
33
|
+
}) => void;
|
|
34
|
+
onScopeRejectSuggestionGroup?: (payload: {
|
|
35
|
+
scopeId: string;
|
|
36
|
+
groupId: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
onScopeAskAgent?: (payload: {
|
|
39
|
+
scopeId: string;
|
|
40
|
+
anchor?: EditorAnchorProjection;
|
|
41
|
+
}) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ScopeCardState {
|
|
45
|
+
activeScopeId: string | null;
|
|
46
|
+
handleScopeStripeClick: (segment: { scopeId: string }) => void;
|
|
47
|
+
handleScopeCardClose: () => void;
|
|
48
|
+
handleScopeCardModeChange: (scopeId: string, mode: WorkflowScopeMode) => void;
|
|
49
|
+
handleScopeCardIssueAction: (
|
|
50
|
+
scopeId: string,
|
|
51
|
+
issueId: string,
|
|
52
|
+
action: ScopeIssueAction,
|
|
53
|
+
) => void;
|
|
54
|
+
handleScopeCardAcceptSuggestionGroup: (scopeId: string, groupId: string) => void;
|
|
55
|
+
handleScopeCardRejectSuggestionGroup: (scopeId: string, groupId: string) => void;
|
|
56
|
+
handleScopeCardAskAgent: (scopeId: string) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Scope-card open/close state + keyboard navigation + all scope-card
|
|
61
|
+
* callback handlers. Extracted from `tw-review-workspace.tsx`.
|
|
62
|
+
*
|
|
63
|
+
* The card closes on click-outside, Escape, or a repeat click on its
|
|
64
|
+
* stripe. J / K cycle the active scope in document order; Enter opens
|
|
65
|
+
* the first scope when none is active (P3d). Pure helpers
|
|
66
|
+
* `shouldHandleScopeNavKey` + `cycleScopeIndex` back the keyboard
|
|
67
|
+
* logic so it remains unit-testable without a workspace mount.
|
|
68
|
+
*/
|
|
69
|
+
export function useScopeCardState(options: UseScopeCardStateOptions): ScopeCardState {
|
|
70
|
+
const {
|
|
71
|
+
layoutFacet,
|
|
72
|
+
workflowFacet,
|
|
73
|
+
onScopeModeChangeRequested,
|
|
74
|
+
onScopeIssueActionRequested,
|
|
75
|
+
onScopeAcceptSuggestionGroup,
|
|
76
|
+
onScopeRejectSuggestionGroup,
|
|
77
|
+
onScopeAskAgent,
|
|
78
|
+
} = options;
|
|
79
|
+
void layoutFacet;
|
|
80
|
+
|
|
81
|
+
const [activeScopeId, setActiveScopeId] = useState<string | null>(null);
|
|
82
|
+
|
|
83
|
+
const handleScopeStripeClick = useCallback(
|
|
84
|
+
(segment: { scopeId: string }) => {
|
|
85
|
+
setActiveScopeId((current) =>
|
|
86
|
+
current === segment.scopeId ? null : segment.scopeId,
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
[],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const handleScopeCardClose = useCallback(() => {
|
|
93
|
+
setActiveScopeId(null);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!workflowFacet) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const onKey = (event: KeyboardEvent) => {
|
|
101
|
+
if (!shouldHandleScopeNavKey(event)) return;
|
|
102
|
+
const models = workflowFacet.getAllScopeCardModels();
|
|
103
|
+
if (models.length === 0) return;
|
|
104
|
+
const ids = models.map((model) => model.scopeId);
|
|
105
|
+
const key = event.key.toLowerCase();
|
|
106
|
+
if (key === "enter") {
|
|
107
|
+
if (!activeScopeId) {
|
|
108
|
+
setActiveScopeId(ids[0] ?? null);
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const direction: 1 | -1 = key === "j" ? 1 : -1;
|
|
114
|
+
const next = cycleScopeIndex(activeScopeId, ids, direction);
|
|
115
|
+
setActiveScopeId(next);
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
};
|
|
118
|
+
window.addEventListener("keydown", onKey);
|
|
119
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
120
|
+
}, [workflowFacet, activeScopeId]);
|
|
121
|
+
|
|
122
|
+
const handleScopeCardModeChange = useCallback(
|
|
123
|
+
(scopeId: string, mode: WorkflowScopeMode) => {
|
|
124
|
+
onScopeModeChangeRequested?.({ scopeId, mode });
|
|
125
|
+
},
|
|
126
|
+
[onScopeModeChangeRequested],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const handleScopeCardIssueAction = useCallback(
|
|
130
|
+
(scopeId: string, issueId: string, action: ScopeIssueAction) => {
|
|
131
|
+
onScopeIssueActionRequested?.({ scopeId, issueId, action });
|
|
132
|
+
},
|
|
133
|
+
[onScopeIssueActionRequested],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const handleScopeCardAcceptSuggestionGroup = useCallback(
|
|
137
|
+
(scopeId: string, groupId: string) => {
|
|
138
|
+
onScopeAcceptSuggestionGroup?.({ scopeId, groupId });
|
|
139
|
+
},
|
|
140
|
+
[onScopeAcceptSuggestionGroup],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const handleScopeCardRejectSuggestionGroup = useCallback(
|
|
144
|
+
(scopeId: string, groupId: string) => {
|
|
145
|
+
onScopeRejectSuggestionGroup?.({ scopeId, groupId });
|
|
146
|
+
},
|
|
147
|
+
[onScopeRejectSuggestionGroup],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const handleScopeCardAskAgent = useCallback(
|
|
151
|
+
(scopeId: string) => {
|
|
152
|
+
const cardModel = workflowFacet
|
|
153
|
+
?.getAllScopeCardModels()
|
|
154
|
+
.find((m) => m.scopeId === scopeId);
|
|
155
|
+
onScopeAskAgent?.({ scopeId, anchor: cardModel?.anchor });
|
|
156
|
+
},
|
|
157
|
+
[onScopeAskAgent, workflowFacet],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
activeScopeId,
|
|
162
|
+
handleScopeStripeClick,
|
|
163
|
+
handleScopeCardClose,
|
|
164
|
+
handleScopeCardModeChange,
|
|
165
|
+
handleScopeCardIssueAction,
|
|
166
|
+
handleScopeCardAcceptSuggestionGroup,
|
|
167
|
+
handleScopeCardRejectSuggestionGroup,
|
|
168
|
+
handleScopeCardAskAgent,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ScrollRootCapture {
|
|
4
|
+
scrollRootRef: RefObject<HTMLDivElement | null>;
|
|
5
|
+
pageStackScrollRoot: HTMLElement | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* P8.11 — capture the scroll-root DOM element on mount so the chrome
|
|
10
|
+
* overlay's `TwPageStackChromeLayer` can measure per-page rects and
|
|
11
|
+
* observe DOM mutations. `scrollRootRef` is attached to the existing
|
|
12
|
+
* `[data-wre-scroll-root]` container; rely on a mount effect rather
|
|
13
|
+
* than a ref callback so render-time state stays cheap. The
|
|
14
|
+
* comparison guard in the effect keeps `setPageStackScrollRoot` from
|
|
15
|
+
* firing every commit.
|
|
16
|
+
*/
|
|
17
|
+
export function useScrollRootCapture(): ScrollRootCapture {
|
|
18
|
+
const scrollRootRef = useRef<HTMLDivElement | null>(null);
|
|
19
|
+
const [pageStackScrollRoot, setPageStackScrollRoot] = useState<HTMLElement | null>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (scrollRootRef.current !== pageStackScrollRoot) {
|
|
23
|
+
setPageStackScrollRoot(scrollRootRef.current);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return { scrollRootRef, pageStackScrollRoot };
|
|
28
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useMemo, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
import type { EditorViewStateSnapshot } from "../../api/public-types";
|
|
4
|
+
import type {
|
|
5
|
+
ActiveSelectionToolModel,
|
|
6
|
+
SelectionToolAnchor,
|
|
7
|
+
} from "../../ui/headless/selection-tool-types";
|
|
8
|
+
import { resolveSelectionToolPlacement } from "../chrome/tw-selection-tool-placement";
|
|
9
|
+
import { useUiApi } from "../ui-api-context";
|
|
10
|
+
|
|
11
|
+
import { resolveSelectionToolbarPlacement } from "./selection-toolbar-placement.ts";
|
|
12
|
+
|
|
13
|
+
export interface UseSelectionToolbarPlacementOptions {
|
|
14
|
+
gatedSelectionTool: ActiveSelectionToolModel | null;
|
|
15
|
+
selection: EditorViewStateSnapshot["selection"];
|
|
16
|
+
selectionToolAnchor: SelectionToolAnchor | null | undefined;
|
|
17
|
+
zoomScale: number;
|
|
18
|
+
renderFrameRevision: number;
|
|
19
|
+
selectionToolbarRootRef: RefObject<HTMLDivElement | null>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compute placement for the floating selection toolbar.
|
|
24
|
+
*
|
|
25
|
+
* Resolution order (flicker-remediation 2026-04-22):
|
|
26
|
+
*
|
|
27
|
+
* 1. Legacy `resolveSelectionToolbarPlacement` driven by the
|
|
28
|
+
* host-supplied `selectionToolAnchor` prop + the toolbar root's
|
|
29
|
+
* `getBoundingClientRect()`. This path correctly handles
|
|
30
|
+
* viewport-to-container translation + zoom scaling. It was the
|
|
31
|
+
* original pre-DS-C1 path, is demonstrably correct, and is
|
|
32
|
+
* populated every render by the shell — so it does not race with
|
|
33
|
+
* bridge publication order.
|
|
34
|
+
*
|
|
35
|
+
* 2. `ui.overlays.getAnchor({ kind: "selection" })` fallback when
|
|
36
|
+
* the host hasn't populated `selectionToolAnchor`. The UI API
|
|
37
|
+
* returns a frame-local rect; projecting it into container-local
|
|
38
|
+
* coordinates is a documented TODO (the bridge rect's
|
|
39
|
+
* `space: "frame"` is tracked but not yet translated in this
|
|
40
|
+
* placement path). Until that projection lands, this branch is
|
|
41
|
+
* best-effort for consumers that don't supply the legacy prop —
|
|
42
|
+
* e.g. future headless / Playwright drivers.
|
|
43
|
+
*
|
|
44
|
+
* DS-C1 (designsystem.md §8.8.1 "Selection toolbar" row) still routes
|
|
45
|
+
* anchor READS through the UI API in principle; the shell bridge
|
|
46
|
+
* (`useShellSelectionAnchorBridge`) continues publishing tool-aware
|
|
47
|
+
* rects so `ui.overlays.getAnchor({ kind: "selection" })` returns a
|
|
48
|
+
* meaningful rect for the debug service, Playwright, and the UI-API
|
|
49
|
+
* consumer tests. The placement hook's priority change is strictly a
|
|
50
|
+
* flicker-remediation concession: same source of truth, correct
|
|
51
|
+
* coordinate space.
|
|
52
|
+
*
|
|
53
|
+
* The memo re-runs when the geometry facet bumps `renderFrameRevision`
|
|
54
|
+
* so placement tracks new rects without a separate subscription here.
|
|
55
|
+
*/
|
|
56
|
+
export function useSelectionToolbarPlacement(
|
|
57
|
+
options: UseSelectionToolbarPlacementOptions,
|
|
58
|
+
): ReturnType<typeof resolveSelectionToolbarPlacement> {
|
|
59
|
+
const {
|
|
60
|
+
gatedSelectionTool,
|
|
61
|
+
selection,
|
|
62
|
+
selectionToolAnchor,
|
|
63
|
+
zoomScale,
|
|
64
|
+
renderFrameRevision,
|
|
65
|
+
selectionToolbarRootRef,
|
|
66
|
+
} = options;
|
|
67
|
+
const ui = useUiApi();
|
|
68
|
+
|
|
69
|
+
return useMemo(() => {
|
|
70
|
+
// Primary: legacy DOM-anchor path. Correct coord space; no bridge
|
|
71
|
+
// timing dependency.
|
|
72
|
+
const legacy = resolveSelectionToolbarPlacement(
|
|
73
|
+
selectionToolAnchor,
|
|
74
|
+
selectionToolbarRootRef.current,
|
|
75
|
+
zoomScale,
|
|
76
|
+
);
|
|
77
|
+
if (legacy) return legacy;
|
|
78
|
+
|
|
79
|
+
// Fallback: UI API anchor for consumers that don't populate
|
|
80
|
+
// `selectionToolAnchor`. Coord-space projection TODO — the bridge
|
|
81
|
+
// rect is frame-local, `resolveSelectionToolPlacement` assumes
|
|
82
|
+
// container-local. Best-effort until projection lands.
|
|
83
|
+
if (ui && gatedSelectionTool) {
|
|
84
|
+
const anchorRect = ui.overlays.getAnchor({ kind: "selection" });
|
|
85
|
+
if (anchorRect && selectionToolbarRootRef.current) {
|
|
86
|
+
const containerRect =
|
|
87
|
+
selectionToolbarRootRef.current.getBoundingClientRect();
|
|
88
|
+
const result = resolveSelectionToolPlacement({
|
|
89
|
+
anchor: {
|
|
90
|
+
leftPx: anchorRect.leftPx,
|
|
91
|
+
topPx: anchorRect.topPx,
|
|
92
|
+
widthPx: anchorRect.widthPx,
|
|
93
|
+
heightPx: anchorRect.heightPx,
|
|
94
|
+
},
|
|
95
|
+
container: {
|
|
96
|
+
widthPx: containerRect.width,
|
|
97
|
+
heightPx: containerRect.height,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (result) return result;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
+
}, [
|
|
106
|
+
ui,
|
|
107
|
+
selectionToolAnchor,
|
|
108
|
+
gatedSelectionTool,
|
|
109
|
+
selection,
|
|
110
|
+
zoomScale,
|
|
111
|
+
renderFrameRevision,
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell-side writer for the overlay-anchor bridge (DS-C1).
|
|
3
|
+
*
|
|
4
|
+
* Publishes a `getSelectionAnchor` closure on every render so that
|
|
5
|
+
* `ui.overlays.getAnchor({ kind: "selection" })` returns the current
|
|
6
|
+
* selection rect without re-creating the UI API or the controller.
|
|
7
|
+
*
|
|
8
|
+
* Pairs with `src/shell/overlay-anchor-bridge.ts` +
|
|
9
|
+
* `src/ui/ui-controller-factory.ts` + `OverlayAnchorBridgeProvider`.
|
|
10
|
+
* When no bridge provider is mounted (headless / tests), this hook is
|
|
11
|
+
* a no-op.
|
|
12
|
+
*
|
|
13
|
+
* The resolver is written during render (not from an effect) so
|
|
14
|
+
* readers that call `ui.overlays.getAnchor({ kind: "selection" })`
|
|
15
|
+
* inside the same render pass — specifically
|
|
16
|
+
* `useSelectionToolbarPlacement` — see the current-render inputs, not
|
|
17
|
+
* the previous render's. The bridge itself is a mutable ref-box; the
|
|
18
|
+
* write is idempotent and free of React-scheduling hazards.
|
|
19
|
+
*
|
|
20
|
+
* A `useEffect` cleanup runs on unmount to clear the bridge so the
|
|
21
|
+
* controller returns `null` (U4) after the tree tears down. The
|
|
22
|
+
* cleanup only fires on unmount (empty deps), never on re-render,
|
|
23
|
+
* because we rewrite the resolver on every render and React would
|
|
24
|
+
* otherwise clobber a live resolver mid-render.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { useEffect, useRef } from "react";
|
|
28
|
+
|
|
29
|
+
import type { GeometryRect } from "../../api/v3/ui/_types.ts";
|
|
30
|
+
import type {
|
|
31
|
+
EditorViewStateSnapshot,
|
|
32
|
+
GeometryFacet,
|
|
33
|
+
} from "../../api/public-types";
|
|
34
|
+
import type { ActiveSelectionToolModel } from "../../ui/headless/selection-tool-types";
|
|
35
|
+
import type { ShellOverlayAnchorBridge } from "../../shell/overlay-anchor-bridge.ts";
|
|
36
|
+
import { resolveSelectionAnchor } from "../chrome/tw-selection-anchor-resolver";
|
|
37
|
+
import { useOverlayAnchorBridge } from "../overlay-anchor-bridge-context.tsx";
|
|
38
|
+
|
|
39
|
+
export interface UseShellSelectionAnchorBridgeOptions {
|
|
40
|
+
/** Runtime geometry facet — kernel source for render-frame rects. */
|
|
41
|
+
geometryFacet?: GeometryFacet;
|
|
42
|
+
/** Current selection snapshot from the runtime view-state. */
|
|
43
|
+
selection: EditorViewStateSnapshot["selection"];
|
|
44
|
+
/** Current active (gated) selection tool. */
|
|
45
|
+
gatedSelectionTool: ActiveSelectionToolModel | null;
|
|
46
|
+
/**
|
|
47
|
+
* Bumps when the geometry facet emits a new render frame; the
|
|
48
|
+
* publication runs during render anyway, so this field is carried
|
|
49
|
+
* for API symmetry with the consumer hook (placement) whose memo
|
|
50
|
+
* tracks it.
|
|
51
|
+
*/
|
|
52
|
+
renderFrameRevision: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Publishes `bridge.getSelectionAnchor` synchronously during render.
|
|
57
|
+
* Consumers read through `ui.overlays.getAnchor({ kind: "selection" })`
|
|
58
|
+
* in the same render pass and see the current-render inputs.
|
|
59
|
+
*/
|
|
60
|
+
export function useShellSelectionAnchorBridge(
|
|
61
|
+
options: UseShellSelectionAnchorBridgeOptions,
|
|
62
|
+
): void {
|
|
63
|
+
const bridge = useOverlayAnchorBridge();
|
|
64
|
+
const { geometryFacet, selection, gatedSelectionTool } = options;
|
|
65
|
+
|
|
66
|
+
// Synchronous write during render. Mutating a ref-box here is safe:
|
|
67
|
+
// the bridge is not React state, the write is idempotent, and the
|
|
68
|
+
// bridge identity is stable across renders (the controller closed
|
|
69
|
+
// over the same object at API construction).
|
|
70
|
+
if (bridge) {
|
|
71
|
+
bridge.getSelectionAnchor = () => {
|
|
72
|
+
if (!geometryFacet) return null;
|
|
73
|
+
const anchor = resolveSelectionAnchor({
|
|
74
|
+
geometryFacet,
|
|
75
|
+
selection,
|
|
76
|
+
tool: gatedSelectionTool,
|
|
77
|
+
});
|
|
78
|
+
if (!anchor) return null;
|
|
79
|
+
// `resolveSelectionAnchor` returns kernel-frame-local px. Tag as
|
|
80
|
+
// `frame` space so overlay consumers project through the same
|
|
81
|
+
// transform L05 uses for id-keyed anchors.
|
|
82
|
+
return {
|
|
83
|
+
leftPx: anchor.leftPx,
|
|
84
|
+
topPx: anchor.topPx,
|
|
85
|
+
widthPx: anchor.widthPx,
|
|
86
|
+
heightPx: anchor.heightPx,
|
|
87
|
+
space: "frame",
|
|
88
|
+
} satisfies GeometryRect;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Clear on true unmount only. We track the bridge identity through
|
|
93
|
+
// a ref so the cleanup closure refers to the bridge that was live
|
|
94
|
+
// at cleanup time — not the bridge captured on the first render.
|
|
95
|
+
// This also guards the React StrictMode double-mount path: the
|
|
96
|
+
// post-mount cleanup previously nulled the resolver the render
|
|
97
|
+
// body had just written (StrictMode fires cleanup after the first
|
|
98
|
+
// mount, then re-mounts; without the ref guard the second mount
|
|
99
|
+
// would not re-run the render body and the resolver would stay
|
|
100
|
+
// null until the next render). The render-body assignment above
|
|
101
|
+
// runs every commit, so the ref write here never shadows a newer
|
|
102
|
+
// resolver unless `bridge` itself has changed identity.
|
|
103
|
+
const liveBridgeRef = useRef(bridge);
|
|
104
|
+
liveBridgeRef.current = bridge;
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
return () => {
|
|
107
|
+
const current = liveBridgeRef.current;
|
|
108
|
+
if (current) {
|
|
109
|
+
current.getSelectionAnchor = null;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
113
|
+
}, []);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Re-exported for consumers that want to peek at the bridge directly
|
|
118
|
+
* (tests, evidence runners). Not used by the placement hook.
|
|
119
|
+
*/
|
|
120
|
+
export type { ShellOverlayAnchorBridge };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
PublicMeasurementFidelity,
|
|
5
|
+
RuntimeRenderSnapshot,
|
|
6
|
+
} from "../../api/public-types.ts";
|
|
7
|
+
|
|
8
|
+
export interface StatusBarPageFacts {
|
|
9
|
+
displayPageNumber: number | null;
|
|
10
|
+
pageCount: number | null;
|
|
11
|
+
measurementFidelity: PublicMeasurementFidelity | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface UseStatusBarPageFactsOptions {
|
|
15
|
+
layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
|
|
16
|
+
selectionPosition: number;
|
|
17
|
+
activeStory: RuntimeRenderSnapshot["activeStory"];
|
|
18
|
+
renderFrameRevision: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* P5b — status-bar facts derived from the layout facet so the
|
|
23
|
+
* Page-N-of-M display + measurement-fidelity badge ("E" / "C" / "C+F")
|
|
24
|
+
* refresh on every layout-affecting facet event. `renderFrameRevision`
|
|
25
|
+
* (bumped by `useLayoutFacetRenderSignal`) drives the memo; no separate
|
|
26
|
+
* subscription here.
|
|
27
|
+
*/
|
|
28
|
+
export function useStatusBarPageFacts(
|
|
29
|
+
options: UseStatusBarPageFactsOptions,
|
|
30
|
+
): StatusBarPageFacts {
|
|
31
|
+
const { layoutFacet, selectionPosition, activeStory, renderFrameRevision } = options;
|
|
32
|
+
|
|
33
|
+
return useMemo(() => {
|
|
34
|
+
const facet = layoutFacet;
|
|
35
|
+
if (!facet) {
|
|
36
|
+
return {
|
|
37
|
+
displayPageNumber: null,
|
|
38
|
+
pageCount: null,
|
|
39
|
+
measurementFidelity: undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const pageRef = facet.getPageForOffset(selectionPosition, activeStory);
|
|
43
|
+
const displayPageNumber =
|
|
44
|
+
pageRef !== null && typeof pageRef.pageIndex === "number"
|
|
45
|
+
? facet.getDisplayPageNumber(pageRef.pageIndex) ?? pageRef.pageIndex + 1
|
|
46
|
+
: null;
|
|
47
|
+
const pageCount = facet.getPageCount();
|
|
48
|
+
return {
|
|
49
|
+
displayPageNumber,
|
|
50
|
+
pageCount,
|
|
51
|
+
measurementFidelity: facet.getMeasurementFidelity(),
|
|
52
|
+
};
|
|
53
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
54
|
+
}, [layoutFacet, selectionPosition, activeStory, renderFrameRevision]);
|
|
55
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { readViewportHeight, readViewportWidth } from "./page-shell-metrics.ts";
|
|
4
|
+
|
|
5
|
+
export interface ViewportDimensions {
|
|
6
|
+
viewportWidth: number | undefined;
|
|
7
|
+
viewportHeight: number | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Live window-dimension hook with SSR-safe initial read + `resize` listener.
|
|
12
|
+
* Extracted from `tw-review-workspace.tsx` so the viewport state is
|
|
13
|
+
* addressable on its own. The hook does not read DOM during a mutation;
|
|
14
|
+
* `readViewportWidth` / `readViewportHeight` return `undefined` under
|
|
15
|
+
* SSR, matching the prior inline behavior.
|
|
16
|
+
*/
|
|
17
|
+
export function useViewportDimensions(): ViewportDimensions {
|
|
18
|
+
const [viewportWidth, setViewportWidth] = useState<number | undefined>(
|
|
19
|
+
() => readViewportWidth(),
|
|
20
|
+
);
|
|
21
|
+
const [viewportHeight, setViewportHeight] = useState<number | undefined>(
|
|
22
|
+
() => readViewportHeight(),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (typeof window === "undefined") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const updateViewport = () => {
|
|
31
|
+
setViewportWidth(readViewportWidth());
|
|
32
|
+
setViewportHeight(readViewportHeight());
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
updateViewport();
|
|
36
|
+
window.addEventListener("resize", updateViewport);
|
|
37
|
+
return () => {
|
|
38
|
+
window.removeEventListener("resize", updateViewport);
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
return { viewportWidth, viewportHeight };
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createLocalSurfaceArbiter,
|
|
5
|
+
type LocalSurfaceArbiter,
|
|
6
|
+
} from "../chrome/local-surface-arbiter";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Phase F.2 — per-workspace local-surface arbiter. Every floating
|
|
10
|
+
* surface inside the workspace (selection toolbar, scope card,
|
|
11
|
+
* suggestion card, context menu, comment preview) requests through this
|
|
12
|
+
* shared instance.
|
|
13
|
+
*
|
|
14
|
+
* Stable identity via `useRef` — the arbiter is an event-emitter with
|
|
15
|
+
* its own internal state, so recreating it per render would drop active
|
|
16
|
+
* requests. Consumers read it through the `LocalSurfaceArbiterContext`
|
|
17
|
+
* provider mounted in the workspace return tree.
|
|
18
|
+
*/
|
|
19
|
+
export function useWorkspaceArbiter(): LocalSurfaceArbiter {
|
|
20
|
+
const ref = useRef<LocalSurfaceArbiter | null>(null);
|
|
21
|
+
if (ref.current === null) {
|
|
22
|
+
ref.current = createLocalSurfaceArbiter();
|
|
23
|
+
}
|
|
24
|
+
return ref.current;
|
|
25
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
resolveChromeComposition,
|
|
5
|
+
type ChromeCompositionInput,
|
|
6
|
+
} from "../../api/v3/ui";
|
|
7
|
+
import { useUiApi } from "../ui-api-context";
|
|
8
|
+
|
|
9
|
+
export type UseWorkspaceCompositionOptions = Pick<
|
|
10
|
+
ChromeCompositionInput,
|
|
11
|
+
| "chromePreset"
|
|
12
|
+
| "chromeOptions"
|
|
13
|
+
| "reviewMode"
|
|
14
|
+
| "role"
|
|
15
|
+
| "markupDisplay"
|
|
16
|
+
| "diagnosticsSignal"
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Memoized hook that returns the chrome composition for the workspace.
|
|
21
|
+
*
|
|
22
|
+
* When a `UiApiProvider` wraps the tree, this hook calls
|
|
23
|
+
* `ui.chrome.getComposition(input)` — the architectural path per
|
|
24
|
+
* architecture 10 §U5.b and designsystem §8.3 (the UI API owns
|
|
25
|
+
* composition decisions). When no provider is present, the hook falls
|
|
26
|
+
* back to `resolveChromeComposition` (the same function the UI API
|
|
27
|
+
* wraps) — so headless paths, the React harness, and tests behave
|
|
28
|
+
* identically without needing to mount a provider.
|
|
29
|
+
*
|
|
30
|
+
* The underlying function lives in layer 10 (`src/api/v3/ui/chrome-
|
|
31
|
+
* composition.ts`); the L11 re-export shim at
|
|
32
|
+
* `src/ui-tailwind/chrome/chrome-composition-model.ts` exists for other
|
|
33
|
+
* consumers mid-migration. This hook is the first mounted consumer to
|
|
34
|
+
* source from L10 directly (refactor/10 Slice 10).
|
|
35
|
+
*
|
|
36
|
+
* Memoization (perf invariant #4): `TwReviewWorkspace` re-renders on
|
|
37
|
+
* every PM transaction (`view.updateState()` wholesale replace in the
|
|
38
|
+
* inverted-truth architecture). `resolveChromeComposition` allocates
|
|
39
|
+
* new Sets + arrays on each call; `useMemo` keeps downstream
|
|
40
|
+
* `React.memo` consumers stable across commits.
|
|
41
|
+
*
|
|
42
|
+
* **Caller contract**: every option value passed into this hook MUST be
|
|
43
|
+
* referentially stable across renders that don't intend to trigger a
|
|
44
|
+
* re-composition — the memo's dep list compares by identity. In
|
|
45
|
+
* particular, `diagnosticsSignal` (built each render by
|
|
46
|
+
* `useDiagnosticsSignal`) and `chromeOptions` (host prop) must already
|
|
47
|
+
* be memoized by their source; passing a freshly-allocated object here
|
|
48
|
+
* thrashes the memo and cascades through every `React.memo` consumer
|
|
49
|
+
* downstream. Review M6 (2026-04-22) flagged this as a silent perf
|
|
50
|
+
* regression risk.
|
|
51
|
+
*
|
|
52
|
+
* `readOnly` is intentionally omitted from the option set: the
|
|
53
|
+
* composition seam is independent of read-only posture today; Phase E
|
|
54
|
+
* threads it through the interactionGuardSnapshot wiring once the rail
|
|
55
|
+
* owns the full posture composition.
|
|
56
|
+
*/
|
|
57
|
+
export function useWorkspaceComposition(
|
|
58
|
+
options: UseWorkspaceCompositionOptions,
|
|
59
|
+
): ReturnType<typeof resolveChromeComposition> {
|
|
60
|
+
const ui = useUiApi();
|
|
61
|
+
const {
|
|
62
|
+
chromePreset,
|
|
63
|
+
chromeOptions,
|
|
64
|
+
reviewMode,
|
|
65
|
+
role,
|
|
66
|
+
markupDisplay,
|
|
67
|
+
diagnosticsSignal,
|
|
68
|
+
} = options;
|
|
69
|
+
|
|
70
|
+
return useMemo(
|
|
71
|
+
() => {
|
|
72
|
+
const input: ChromeCompositionInput = {
|
|
73
|
+
chromePreset,
|
|
74
|
+
chromeOptions,
|
|
75
|
+
reviewMode,
|
|
76
|
+
role,
|
|
77
|
+
markupDisplay,
|
|
78
|
+
diagnosticsSignal,
|
|
79
|
+
};
|
|
80
|
+
return ui
|
|
81
|
+
? ui.chrome.getComposition(input)
|
|
82
|
+
: resolveChromeComposition(input);
|
|
83
|
+
},
|
|
84
|
+
[ui, chromePreset, chromeOptions, reviewMode, role, markupDisplay, diagnosticsSignal],
|
|
85
|
+
);
|
|
86
|
+
}
|