@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
|
@@ -6,12 +6,23 @@ import type {
|
|
|
6
6
|
SurfaceDrawingAnchor,
|
|
7
7
|
SurfaceInlineSegment,
|
|
8
8
|
} from "../../api/public-types.ts";
|
|
9
|
-
import type { WordReviewEditorLayoutFacet } from "../../
|
|
9
|
+
import type { WordReviewEditorLayoutFacet } from "../../api/public-types.ts";
|
|
10
10
|
import { storyTargetKey } from "../../runtime/story-targeting.ts";
|
|
11
|
-
import { EMU_PER_PX } from "../../
|
|
11
|
+
import { EMU_PER_PX, TWIPS_PER_PX } from "../../api/public-types.ts";
|
|
12
12
|
import type { PageOverlayRect } from "../chrome-overlay/tw-page-stack-overlay-layer.tsx";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Default twip → pixel conversion at 96 dpi / 100% zoom.
|
|
16
|
+
* `pxPerTwip = 96 / 1440 = 1 / TWIPS_PER_PX`.
|
|
17
|
+
*
|
|
18
|
+
* Callers that render the page stack at a non-baseline zoom (e.g.
|
|
19
|
+
* 75% / 125% / 150%) must pass `pxPerTwip` derived from
|
|
20
|
+
* `geometryFacet.getRenderZoom()?.pxPerTwip` so floating-image rects
|
|
21
|
+
* track the rendered page frame. Otherwise the overlay math drifts
|
|
22
|
+
* — a 150%-zoomed page is 1.5× bigger, but floats computed with the
|
|
23
|
+
* 96 dpi baseline land at 66% of the target position.
|
|
24
|
+
*/
|
|
25
|
+
const BASELINE_PX_PER_TWIP = 1 / TWIPS_PER_PX;
|
|
15
26
|
|
|
16
27
|
export interface FloatingImagePreviewDescriptor {
|
|
17
28
|
src: string;
|
|
@@ -66,12 +77,22 @@ export function collectFloatingImageOverlayItems(input: {
|
|
|
66
77
|
facet: WordReviewEditorLayoutFacet;
|
|
67
78
|
pageRects: readonly PageOverlayRect[];
|
|
68
79
|
mediaPreviews?: Record<string, FloatingImagePreviewDescriptor>;
|
|
80
|
+
/**
|
|
81
|
+
* Live `pxPerTwip` from the geometry facet
|
|
82
|
+
* (`geometryFacet.getRenderZoom()?.pxPerTwip`). When absent, defaults
|
|
83
|
+
* to the 96 dpi baseline (100% zoom). Supply this whenever the page
|
|
84
|
+
* stack is rendered at a non-baseline zoom — otherwise floating-image
|
|
85
|
+
* rects drift off the rendered page frame.
|
|
86
|
+
*/
|
|
87
|
+
pxPerTwip?: number;
|
|
69
88
|
}): FloatingImageOverlayItem[] {
|
|
70
89
|
const { surface, activeStory, facet } = input;
|
|
71
90
|
if (!surface) {
|
|
72
91
|
return [];
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
const pxPerTwip = input.pxPerTwip ?? BASELINE_PX_PER_TWIP;
|
|
95
|
+
|
|
75
96
|
const rectByPageIndex = new Map<number, PageOverlayRect>(
|
|
76
97
|
input.pageRects.map((rect) => [rect.pageIndex, rect]),
|
|
77
98
|
);
|
|
@@ -88,7 +109,7 @@ export function collectFloatingImageOverlayItems(input: {
|
|
|
88
109
|
if (!pageRect) {
|
|
89
110
|
continue;
|
|
90
111
|
}
|
|
91
|
-
const localRect = resolveFloatingImageLocalRect(page, activeStory, segment);
|
|
112
|
+
const localRect = resolveFloatingImageLocalRect(page, activeStory, segment, pxPerTwip);
|
|
92
113
|
if (!localRect) {
|
|
93
114
|
continue;
|
|
94
115
|
}
|
|
@@ -164,6 +185,7 @@ function resolveFloatingImageLocalRect(
|
|
|
164
185
|
page: PublicPageNode,
|
|
165
186
|
activeStory: EditorStoryTarget,
|
|
166
187
|
segment: Extract<SurfaceInlineSegment, { kind: "image" }>,
|
|
188
|
+
pxPerTwip: number,
|
|
167
189
|
): {
|
|
168
190
|
topPx: number;
|
|
169
191
|
leftPx: number;
|
|
@@ -175,18 +197,18 @@ function resolveFloatingImageLocalRect(
|
|
|
175
197
|
return null;
|
|
176
198
|
}
|
|
177
199
|
|
|
178
|
-
const widthPx = Math.max(24, Math.round(anchor.extent.widthEmu
|
|
179
|
-
const heightPx = Math.max(24, Math.round(anchor.extent.heightEmu
|
|
180
|
-
const horizontalSpace = resolveHorizontalSpace(page, activeStory, anchor);
|
|
181
|
-
const verticalSpace = resolveVerticalSpace(page, activeStory, anchor);
|
|
200
|
+
const widthPx = Math.max(24, Math.round(emuToPx(anchor.extent.widthEmu, pxPerTwip)));
|
|
201
|
+
const heightPx = Math.max(24, Math.round(emuToPx(anchor.extent.heightEmu, pxPerTwip)));
|
|
202
|
+
const horizontalSpace = resolveHorizontalSpace(page, activeStory, anchor, pxPerTwip);
|
|
203
|
+
const verticalSpace = resolveVerticalSpace(page, activeStory, anchor, pxPerTwip);
|
|
182
204
|
|
|
183
205
|
if (!horizontalSpace || !verticalSpace) {
|
|
184
206
|
return null;
|
|
185
207
|
}
|
|
186
208
|
|
|
187
209
|
return {
|
|
188
|
-
topPx: resolveAxisPosition(verticalSpace, heightPx, anchor.positionV, page, "vertical"),
|
|
189
|
-
leftPx: resolveAxisPosition(horizontalSpace, widthPx, anchor.positionH, page, "horizontal"),
|
|
210
|
+
topPx: resolveAxisPosition(verticalSpace, heightPx, anchor.positionV, page, "vertical", pxPerTwip),
|
|
211
|
+
leftPx: resolveAxisPosition(horizontalSpace, widthPx, anchor.positionH, page, "horizontal", pxPerTwip),
|
|
190
212
|
widthPx,
|
|
191
213
|
heightPx,
|
|
192
214
|
};
|
|
@@ -196,9 +218,10 @@ function resolveHorizontalSpace(
|
|
|
196
218
|
page: PublicPageNode,
|
|
197
219
|
activeStory: EditorStoryTarget,
|
|
198
220
|
anchor: SurfaceDrawingAnchor,
|
|
221
|
+
pxPerTwip: number,
|
|
199
222
|
): { startPx: number; sizePx: number } | null {
|
|
200
|
-
const pageWidthPx = twipsToPx(page.layout.pageWidth);
|
|
201
|
-
const storyHost = resolveStoryHostSpace(page, activeStory);
|
|
223
|
+
const pageWidthPx = twipsToPx(page.layout.pageWidth, pxPerTwip);
|
|
224
|
+
const storyHost = resolveStoryHostSpace(page, activeStory, pxPerTwip);
|
|
202
225
|
switch (anchor.positionH?.relativeFrom) {
|
|
203
226
|
case "page":
|
|
204
227
|
return { startPx: 0, sizePx: pageWidthPx };
|
|
@@ -213,9 +236,10 @@ function resolveVerticalSpace(
|
|
|
213
236
|
page: PublicPageNode,
|
|
214
237
|
activeStory: EditorStoryTarget,
|
|
215
238
|
anchor: SurfaceDrawingAnchor,
|
|
239
|
+
pxPerTwip: number,
|
|
216
240
|
): { startPx: number; sizePx: number } | null {
|
|
217
|
-
const pageHeightPx = twipsToPx(page.layout.pageHeight);
|
|
218
|
-
const storyHost = resolveStoryHostSpace(page, activeStory);
|
|
241
|
+
const pageHeightPx = twipsToPx(page.layout.pageHeight, pxPerTwip);
|
|
242
|
+
const storyHost = resolveStoryHostSpace(page, activeStory, pxPerTwip);
|
|
219
243
|
switch (anchor.positionV?.relativeFrom) {
|
|
220
244
|
case "page":
|
|
221
245
|
return { startPx: 0, sizePx: pageHeightPx };
|
|
@@ -229,31 +253,32 @@ function resolveVerticalSpace(
|
|
|
229
253
|
function resolveStoryHostSpace(
|
|
230
254
|
page: PublicPageNode,
|
|
231
255
|
activeStory: EditorStoryTarget,
|
|
256
|
+
pxPerTwip: number,
|
|
232
257
|
): { topPx: number; leftPx: number; widthPx: number; heightPx: number } | null {
|
|
233
258
|
switch (activeStory.kind) {
|
|
234
259
|
case "main":
|
|
235
260
|
return {
|
|
236
|
-
topPx: twipsToPx(page.regions.body.originTwips),
|
|
237
|
-
leftPx: twipsToPx(page.layout.marginLeft),
|
|
238
|
-
widthPx: twipsToPx(page.regions.body.widthTwips),
|
|
239
|
-
heightPx: twipsToPx(page.regions.body.heightTwips),
|
|
261
|
+
topPx: twipsToPx(page.regions.body.originTwips, pxPerTwip),
|
|
262
|
+
leftPx: twipsToPx(page.layout.marginLeft, pxPerTwip),
|
|
263
|
+
widthPx: twipsToPx(page.regions.body.widthTwips, pxPerTwip),
|
|
264
|
+
heightPx: twipsToPx(page.regions.body.heightTwips, pxPerTwip),
|
|
240
265
|
};
|
|
241
266
|
case "header":
|
|
242
267
|
return page.regions.header
|
|
243
268
|
? {
|
|
244
|
-
topPx: twipsToPx(page.regions.header.originTwips),
|
|
245
|
-
leftPx: twipsToPx(page.layout.marginLeft),
|
|
246
|
-
widthPx: twipsToPx(page.regions.header.widthTwips),
|
|
247
|
-
heightPx: twipsToPx(page.regions.header.heightTwips),
|
|
269
|
+
topPx: twipsToPx(page.regions.header.originTwips, pxPerTwip),
|
|
270
|
+
leftPx: twipsToPx(page.layout.marginLeft, pxPerTwip),
|
|
271
|
+
widthPx: twipsToPx(page.regions.header.widthTwips, pxPerTwip),
|
|
272
|
+
heightPx: twipsToPx(page.regions.header.heightTwips, pxPerTwip),
|
|
248
273
|
}
|
|
249
274
|
: null;
|
|
250
275
|
case "footer":
|
|
251
276
|
return page.regions.footer
|
|
252
277
|
? {
|
|
253
|
-
topPx: twipsToPx(page.regions.footer.originTwips),
|
|
254
|
-
leftPx: twipsToPx(page.layout.marginLeft),
|
|
255
|
-
widthPx: twipsToPx(page.regions.footer.widthTwips),
|
|
256
|
-
heightPx: twipsToPx(page.regions.footer.heightTwips),
|
|
278
|
+
topPx: twipsToPx(page.regions.footer.originTwips, pxPerTwip),
|
|
279
|
+
leftPx: twipsToPx(page.layout.marginLeft, pxPerTwip),
|
|
280
|
+
widthPx: twipsToPx(page.regions.footer.widthTwips, pxPerTwip),
|
|
281
|
+
heightPx: twipsToPx(page.regions.footer.heightTwips, pxPerTwip),
|
|
257
282
|
}
|
|
258
283
|
: null;
|
|
259
284
|
default:
|
|
@@ -269,6 +294,7 @@ function resolveAxisPosition(
|
|
|
269
294
|
| undefined,
|
|
270
295
|
page: PublicPageNode,
|
|
271
296
|
orientation: "horizontal" | "vertical",
|
|
297
|
+
pxPerTwip: number,
|
|
272
298
|
): number {
|
|
273
299
|
if (!axis) {
|
|
274
300
|
return space.startPx;
|
|
@@ -277,7 +303,7 @@ function resolveAxisPosition(
|
|
|
277
303
|
return alignAxisPosition(space, objectSizePx, axis.align, page, orientation);
|
|
278
304
|
}
|
|
279
305
|
if (axis.offset !== undefined) {
|
|
280
|
-
return space.startPx + Math.round(axis.offset
|
|
306
|
+
return space.startPx + Math.round(emuToPx(axis.offset, pxPerTwip));
|
|
281
307
|
}
|
|
282
308
|
return space.startPx;
|
|
283
309
|
}
|
|
@@ -314,6 +340,17 @@ function alignAxisPosition(
|
|
|
314
340
|
}
|
|
315
341
|
}
|
|
316
342
|
|
|
317
|
-
function twipsToPx(value: number): number {
|
|
318
|
-
return value *
|
|
343
|
+
function twipsToPx(value: number, pxPerTwip: number): number {
|
|
344
|
+
return value * pxPerTwip;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Convert EMU → zoom-aware CSS pixels. The baseline is 96 dpi
|
|
349
|
+
* (`EMU_PER_PX = 9525`); at zoom `z`, a 1-inch object renders as
|
|
350
|
+
* `z * 96 px`. We scale by `pxPerTwip / BASELINE_PX_PER_TWIP`,
|
|
351
|
+
* which equals the zoom factor.
|
|
352
|
+
*/
|
|
353
|
+
function emuToPx(emu: number, pxPerTwip: number): number {
|
|
354
|
+
const zoomFactor = pxPerTwip / BASELINE_PX_PER_TWIP;
|
|
355
|
+
return (emu / EMU_PER_PX) * zoomFactor;
|
|
319
356
|
}
|
|
@@ -4,7 +4,10 @@ import type {
|
|
|
4
4
|
EditorStoryTarget,
|
|
5
5
|
RuntimeRenderSnapshot,
|
|
6
6
|
} from "../../api/public-types.ts";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
GeometryFacet,
|
|
9
|
+
WordReviewEditorLayoutFacet,
|
|
10
|
+
} from "../../api/public-types";
|
|
8
11
|
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection.ts";
|
|
9
12
|
import {
|
|
10
13
|
measureWidgetsViaBoundingRect,
|
|
@@ -19,6 +22,14 @@ import {
|
|
|
19
22
|
|
|
20
23
|
export interface TwFloatingImageLayerProps {
|
|
21
24
|
facet: WordReviewEditorLayoutFacet;
|
|
25
|
+
/**
|
|
26
|
+
* Geometry facet supplies live `pxPerTwip` so floating-image rects
|
|
27
|
+
* track the rendered page frame at non-baseline zoom (75% / 125% /
|
|
28
|
+
* 150% / …). When absent, the overlay falls back to the 96 dpi
|
|
29
|
+
* baseline (100% zoom) — safe for tests and for consumers that
|
|
30
|
+
* haven't migrated yet, but visibly drifts in the zoomed UI.
|
|
31
|
+
*/
|
|
32
|
+
geometryFacet?: GeometryFacet;
|
|
22
33
|
scrollRoot: HTMLElement | null;
|
|
23
34
|
renderFrameRevision: number;
|
|
24
35
|
visiblePageIndexRange?: VisiblePageIndexRange | null;
|
|
@@ -35,6 +46,7 @@ export interface TwFloatingImageLayerProps {
|
|
|
35
46
|
|
|
36
47
|
export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
37
48
|
facet,
|
|
49
|
+
geometryFacet,
|
|
38
50
|
scrollRoot,
|
|
39
51
|
renderFrameRevision,
|
|
40
52
|
visiblePageIndexRange,
|
|
@@ -154,17 +166,28 @@ export const TwFloatingImageLayer: React.FC<TwFloatingImageLayerProps> = ({
|
|
|
154
166
|
}, [refreshPageRects, scrollRoot]);
|
|
155
167
|
|
|
156
168
|
const items = React.useMemo(() => {
|
|
169
|
+
const pxPerTwip = geometryFacet?.getRenderZoom()?.pxPerTwip;
|
|
157
170
|
const allItems = collectFloatingImageOverlayItems({
|
|
158
171
|
surface: snapshot.surface,
|
|
159
172
|
activeStory: snapshot.activeStory,
|
|
160
173
|
facet,
|
|
161
174
|
pageRects,
|
|
162
175
|
mediaPreviews,
|
|
176
|
+
pxPerTwip,
|
|
163
177
|
});
|
|
164
178
|
return allItems.filter((item) =>
|
|
165
179
|
plane === "behind" ? item.behindDoc : !item.behindDoc,
|
|
166
180
|
);
|
|
167
|
-
}, [
|
|
181
|
+
}, [
|
|
182
|
+
facet,
|
|
183
|
+
geometryFacet,
|
|
184
|
+
mediaPreviews,
|
|
185
|
+
pageRects,
|
|
186
|
+
plane,
|
|
187
|
+
renderFrameRevision,
|
|
188
|
+
snapshot.activeStory,
|
|
189
|
+
snapshot.surface,
|
|
190
|
+
]);
|
|
168
191
|
|
|
169
192
|
return (
|
|
170
193
|
<div
|
|
@@ -131,6 +131,18 @@ function renderAttachmentImage(
|
|
|
131
131
|
return <span key={key}>{alt || attachment?.displayName || ""}</span>;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// SEC-UI-02 (2026-04-23): scheme allowlist on comment-renderer hrefs.
|
|
135
|
+
// Defense-in-depth against the sanitizer. Any `target` that does not match
|
|
136
|
+
// one of the safe schemes renders as a plain <span>, never as a clickable
|
|
137
|
+
// <a href>. Prevents `javascript:alert(1)`, `data:text/html,<script>...`,
|
|
138
|
+
// and similar vectors from leaking through the renderer even if the
|
|
139
|
+
// sanitizer regresses.
|
|
140
|
+
const SAFE_LINK_SCHEME_RE = /^(?:https?|mailto):/i;
|
|
141
|
+
|
|
142
|
+
function isSafeHref(target: string): boolean {
|
|
143
|
+
return SAFE_LINK_SCHEME_RE.test(target);
|
|
144
|
+
}
|
|
145
|
+
|
|
134
146
|
function renderLinkOrMention(
|
|
135
147
|
key: number,
|
|
136
148
|
label: string,
|
|
@@ -147,6 +159,9 @@ function renderLinkOrMention(
|
|
|
147
159
|
</span>
|
|
148
160
|
);
|
|
149
161
|
}
|
|
162
|
+
if (!isSafeHref(target)) {
|
|
163
|
+
return <span key={key}>{label}</span>;
|
|
164
|
+
}
|
|
150
165
|
return (
|
|
151
166
|
<a key={key} href={target} target="_blank" rel="noopener noreferrer">
|
|
152
167
|
{label}
|
|
@@ -11,20 +11,25 @@ import type {
|
|
|
11
11
|
RuntimeContextAnalyticsSnapshot,
|
|
12
12
|
TrackedChangesSnapshot,
|
|
13
13
|
TrackedChangeEntrySnapshot,
|
|
14
|
+
WorkflowBlockedCommandReason,
|
|
14
15
|
} from "../../api/public-types";
|
|
15
|
-
import type { ScopeRailSegment } from "../../
|
|
16
|
+
import type { ScopeRailSegment } from "../../api/public-types";
|
|
16
17
|
import type { MarkupDisplay } from "../../ui/headless/comment-decoration-model";
|
|
17
18
|
import { TwCommentSidebar } from "./tw-comment-sidebar";
|
|
18
19
|
import { TwRevisionSidebar } from "./tw-revision-sidebar";
|
|
19
20
|
import { TwWorkflowTab } from "./tw-workflow-tab";
|
|
21
|
+
import { TwHealthPanel } from "./tw-health-panel";
|
|
20
22
|
import {
|
|
21
23
|
TwReviewRailFooter,
|
|
22
24
|
type TwReviewRailFooterProps,
|
|
23
25
|
} from "./tw-review-rail-footer";
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* Review rail with
|
|
27
|
-
*
|
|
28
|
+
* Review rail with up to four tabs (Workflow / Comments / Changes / Health).
|
|
29
|
+
* Health is opt-in via `showHealthTab` — Phase E of the chrome-composition
|
|
30
|
+
* refactor routes diagnostics detail into the rail so the toolbar can shrink
|
|
31
|
+
* to a signal-only chip. When `showHealthTab` is absent the rail retains the
|
|
32
|
+
* shipped three-tab layout for back-compat.
|
|
28
33
|
*
|
|
29
34
|
* The Workflow tab reads `scopeRailSegments` from the runtime facet — that
|
|
30
35
|
* stays the default path. For hosts that need to override the Workflow card
|
|
@@ -40,7 +45,7 @@ import {
|
|
|
40
45
|
* - `railFooter?: TwReviewRailFooterProps` mounts the SEARCH / HELP footer.
|
|
41
46
|
*/
|
|
42
47
|
|
|
43
|
-
export type ReviewRailTab = "comments" | "changes" | "workflow";
|
|
48
|
+
export type ReviewRailTab = "comments" | "changes" | "workflow" | "health";
|
|
44
49
|
|
|
45
50
|
export interface TwReviewRailProps {
|
|
46
51
|
activeTab: ReviewRailTab;
|
|
@@ -81,6 +86,23 @@ export interface TwReviewRailProps {
|
|
|
81
86
|
intelligenceHeader?: boolean;
|
|
82
87
|
/** Utility footer with SEARCH / HELP links. Hides when unset. */
|
|
83
88
|
railFooter?: TwReviewRailFooterProps;
|
|
89
|
+
/**
|
|
90
|
+
* Phase E — mount the fourth "Health" tab with the diagnostics panel
|
|
91
|
+
* inside. Hosts typically derive this from
|
|
92
|
+
* `composition.rail.visibleTabs.has("health")`. Omit (default `false`)
|
|
93
|
+
* to preserve the shipped three-tab layout.
|
|
94
|
+
*/
|
|
95
|
+
showHealthTab?: boolean;
|
|
96
|
+
/** Count rendered as the Health tab's badge when > 0. */
|
|
97
|
+
healthIssueCount?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Severity tier of the diagnostics signal — drives the Health tab
|
|
100
|
+
* badge tone via `designsystem.md §3.8` tokens (info / warning /
|
|
101
|
+
* error / success). Defaults to `"warning"` when omitted.
|
|
102
|
+
*/
|
|
103
|
+
healthSeverity?: "info" | "warning" | "blocked";
|
|
104
|
+
/** Forwarded to the Health tab's `TwHealthPanel` to surface blocked reasons. */
|
|
105
|
+
workflowBlockedReasons?: WorkflowBlockedCommandReason[];
|
|
84
106
|
|
|
85
107
|
onActiveTabChange: (tab: ReviewRailTab) => void;
|
|
86
108
|
onOpenComment?: (thread: CommentSidebarThreadSnapshot) => void;
|
|
@@ -177,6 +199,7 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
177
199
|
>
|
|
178
200
|
<Tabs.Trigger
|
|
179
201
|
value="workflow"
|
|
202
|
+
data-rail-tab="workflow"
|
|
180
203
|
className={editorial ? UNDERLINE_TRIGGER_CLASS : PILL_TRIGGER_CLASS}
|
|
181
204
|
>
|
|
182
205
|
{editorial ? "Workflow" : "Workflow "}
|
|
@@ -188,6 +211,7 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
188
211
|
</Tabs.Trigger>
|
|
189
212
|
<Tabs.Trigger
|
|
190
213
|
value="comments"
|
|
214
|
+
data-rail-tab="comments"
|
|
191
215
|
className={editorial ? UNDERLINE_TRIGGER_CLASS : PILL_TRIGGER_CLASS}
|
|
192
216
|
>
|
|
193
217
|
{editorial ? "Comments" : "Comments "}
|
|
@@ -199,6 +223,7 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
199
223
|
</Tabs.Trigger>
|
|
200
224
|
<Tabs.Trigger
|
|
201
225
|
value="changes"
|
|
226
|
+
data-rail-tab="changes"
|
|
202
227
|
className={editorial ? UNDERLINE_TRIGGER_CLASS : PILL_TRIGGER_CLASS}
|
|
203
228
|
>
|
|
204
229
|
{editorial ? "Changes" : "Changes "}
|
|
@@ -208,6 +233,25 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
208
233
|
</span>
|
|
209
234
|
) : null}
|
|
210
235
|
</Tabs.Trigger>
|
|
236
|
+
{props.showHealthTab ? (
|
|
237
|
+
<Tabs.Trigger
|
|
238
|
+
value="health"
|
|
239
|
+
data-rail-tab="health"
|
|
240
|
+
className={editorial ? UNDERLINE_TRIGGER_CLASS : PILL_TRIGGER_CLASS}
|
|
241
|
+
>
|
|
242
|
+
{editorial ? "Health" : "Health "}
|
|
243
|
+
{props.healthIssueCount !== undefined &&
|
|
244
|
+
props.healthIssueCount > 0 ? (
|
|
245
|
+
<span
|
|
246
|
+
className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full px-1.5 py-px text-[10px] font-medium"
|
|
247
|
+
data-severity={props.healthSeverity ?? "warning"}
|
|
248
|
+
style={resolveHealthBadgeStyle(props.healthSeverity)}
|
|
249
|
+
>
|
|
250
|
+
{props.healthIssueCount}
|
|
251
|
+
</span>
|
|
252
|
+
) : null}
|
|
253
|
+
</Tabs.Trigger>
|
|
254
|
+
) : null}
|
|
211
255
|
</Tabs.List>
|
|
212
256
|
|
|
213
257
|
<ScrollArea.Root className="flex-1 min-h-0">
|
|
@@ -247,6 +291,18 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
247
291
|
onRejectAllChanges={props.onRejectAllChanges}
|
|
248
292
|
/>
|
|
249
293
|
</Tabs.Content>
|
|
294
|
+
|
|
295
|
+
{props.showHealthTab ? (
|
|
296
|
+
<Tabs.Content value="health" className="p-3 outline-none">
|
|
297
|
+
<TwHealthPanel
|
|
298
|
+
compatibility={props.compatibility}
|
|
299
|
+
warnings={props.warnings}
|
|
300
|
+
{...(props.workflowBlockedReasons
|
|
301
|
+
? { blockedReasons: props.workflowBlockedReasons }
|
|
302
|
+
: {})}
|
|
303
|
+
/>
|
|
304
|
+
</Tabs.Content>
|
|
305
|
+
) : null}
|
|
250
306
|
</ScrollArea.Viewport>
|
|
251
307
|
<ScrollArea.Scrollbar
|
|
252
308
|
orientation="vertical"
|
|
@@ -262,6 +318,35 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
262
318
|
);
|
|
263
319
|
}
|
|
264
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Health tab badge tones per `designsystem.md §3.8` severity ladder.
|
|
323
|
+
* - `info` → semantic.info soft/solid
|
|
324
|
+
* - `warning` → semantic.warning soft/solid (default)
|
|
325
|
+
* - `blocked` → semantic.error soft/solid
|
|
326
|
+
*/
|
|
327
|
+
function resolveHealthBadgeStyle(
|
|
328
|
+
severity: "info" | "warning" | "blocked" | undefined,
|
|
329
|
+
): React.CSSProperties {
|
|
330
|
+
switch (severity) {
|
|
331
|
+
case "info":
|
|
332
|
+
return {
|
|
333
|
+
backgroundColor: "var(--color-semantic-info-soft)",
|
|
334
|
+
color: "var(--color-semantic-info)",
|
|
335
|
+
};
|
|
336
|
+
case "blocked":
|
|
337
|
+
return {
|
|
338
|
+
backgroundColor: "var(--color-semantic-error-soft)",
|
|
339
|
+
color: "var(--color-semantic-error)",
|
|
340
|
+
};
|
|
341
|
+
case "warning":
|
|
342
|
+
default:
|
|
343
|
+
return {
|
|
344
|
+
backgroundColor: "var(--color-semantic-warning-soft)",
|
|
345
|
+
color: "var(--color-semantic-warning)",
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
265
350
|
function resolveHeaderTitle(props: TwReviewRailProps): string {
|
|
266
351
|
if (props.workflowScopesTitle) {
|
|
267
352
|
return props.workflowScopesTitle;
|
|
@@ -275,5 +360,8 @@ function resolveHeaderTitle(props: TwReviewRailProps): string {
|
|
|
275
360
|
if (props.activeTab === "changes") {
|
|
276
361
|
return "Tracked Changes";
|
|
277
362
|
}
|
|
363
|
+
if (props.activeTab === "health") {
|
|
364
|
+
return "Document Health";
|
|
365
|
+
}
|
|
278
366
|
return "Review";
|
|
279
367
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React from "react";
|
|
12
|
-
import type { ScopeRailSegment, ScopeRailPosture } from "../../
|
|
12
|
+
import type { ScopeRailSegment, ScopeRailPosture } from "../../api/public-types";
|
|
13
13
|
|
|
14
14
|
export interface TwWorkflowTabProps {
|
|
15
15
|
segments: readonly ScopeRailSegment[];
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
DocumentNavigationSnapshot,
|
|
5
|
+
RuntimeRenderSnapshot,
|
|
6
|
+
} from "../../api/public-types";
|
|
7
|
+
import { DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP } from "../../runtime/page-layout-estimation.ts";
|
|
8
|
+
import { computeLineMarkersIfEnabled } from "../page-chrome-model.ts";
|
|
9
|
+
|
|
10
|
+
export interface PageChromeModel {
|
|
11
|
+
lineNumberingEnabled: boolean;
|
|
12
|
+
gutterWidthPx: number;
|
|
13
|
+
lineMarkers: Array<{ id: string; label: string; topPx: number }>;
|
|
14
|
+
showPageBorder: boolean;
|
|
15
|
+
pageBorderDisplay: string;
|
|
16
|
+
pageBorderStyle: CSSProperties | undefined;
|
|
17
|
+
documentGridType: string;
|
|
18
|
+
documentGridStyle: CSSProperties | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const EMPTY_PAGE_CHROME_MODEL: PageChromeModel = {
|
|
22
|
+
lineNumberingEnabled: false,
|
|
23
|
+
gutterWidthPx: 0,
|
|
24
|
+
lineMarkers: [],
|
|
25
|
+
showPageBorder: false,
|
|
26
|
+
pageBorderDisplay: "none",
|
|
27
|
+
pageBorderStyle: undefined,
|
|
28
|
+
documentGridType: "none",
|
|
29
|
+
documentGridStyle: undefined,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function buildPageChromeModel(
|
|
33
|
+
surface: RuntimeRenderSnapshot["surface"] | undefined,
|
|
34
|
+
pageLayout: RuntimeRenderSnapshot["pageLayout"] | undefined,
|
|
35
|
+
navigation: DocumentNavigationSnapshot | undefined,
|
|
36
|
+
activeStory: RuntimeRenderSnapshot["activeStory"],
|
|
37
|
+
): PageChromeModel {
|
|
38
|
+
if (!surface || !pageLayout || !navigation || activeStory.kind !== "main") {
|
|
39
|
+
return EMPTY_PAGE_CHROME_MODEL;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lineMarkers = computeLineMarkersIfEnabled({
|
|
43
|
+
pageLayout,
|
|
44
|
+
surfaceBlocks: surface.blocks,
|
|
45
|
+
pages: navigation.pages,
|
|
46
|
+
});
|
|
47
|
+
const lineNumberingEnabled =
|
|
48
|
+
Boolean(pageLayout.lineNumbering) && lineMarkers.length > 0;
|
|
49
|
+
const distance = pageLayout.lineNumbering?.distance ?? 0;
|
|
50
|
+
const gutterWidthPx = lineNumberingEnabled
|
|
51
|
+
? Math.max(40, Math.min(88, 24 + Math.round(distance * DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP)))
|
|
52
|
+
: 0;
|
|
53
|
+
const showPageBorder = shouldRenderPageBorder(
|
|
54
|
+
pageLayout,
|
|
55
|
+
navigation.pages,
|
|
56
|
+
navigation.activePageIndex,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
lineNumberingEnabled,
|
|
61
|
+
gutterWidthPx,
|
|
62
|
+
lineMarkers,
|
|
63
|
+
showPageBorder,
|
|
64
|
+
pageBorderDisplay: pageLayout.pageBorders?.display ?? "none",
|
|
65
|
+
pageBorderStyle: showPageBorder ? buildPageBorderStyle(pageLayout) : undefined,
|
|
66
|
+
documentGridType: pageLayout.documentGrid?.type ?? "none",
|
|
67
|
+
documentGridStyle: buildDocumentGridStyle(pageLayout.documentGrid),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function shouldRenderPageBorder(
|
|
72
|
+
pageLayout: RuntimeRenderSnapshot["pageLayout"],
|
|
73
|
+
pages: ReadonlyArray<DocumentNavigationSnapshot["pages"][number]>,
|
|
74
|
+
activePageIndex: number,
|
|
75
|
+
): boolean {
|
|
76
|
+
const display = pageLayout?.pageBorders?.display ?? "allPages";
|
|
77
|
+
const activePage = pages[activePageIndex];
|
|
78
|
+
if (!pageLayout?.pageBorders || !activePage) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
switch (display) {
|
|
83
|
+
case "firstPage":
|
|
84
|
+
return activePage.pageInSection === 0;
|
|
85
|
+
case "notFirstPage":
|
|
86
|
+
return activePage.pageInSection > 0;
|
|
87
|
+
default:
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildPageBorderStyle(
|
|
93
|
+
pageLayout: NonNullable<RuntimeRenderSnapshot["pageLayout"]>,
|
|
94
|
+
): CSSProperties | undefined {
|
|
95
|
+
const pageBorders = pageLayout.pageBorders;
|
|
96
|
+
if (!pageBorders) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const leftInset = createInsetValue(
|
|
101
|
+
pageBorders.left?.space,
|
|
102
|
+
pageBorders.offsetFrom === "text"
|
|
103
|
+
? (pageLayout.marginLeft / Math.max(1, pageLayout.pageWidth)) * 100
|
|
104
|
+
: 1.25,
|
|
105
|
+
);
|
|
106
|
+
const rightInset = createInsetValue(
|
|
107
|
+
pageBorders.right?.space,
|
|
108
|
+
pageBorders.offsetFrom === "text"
|
|
109
|
+
? (pageLayout.marginRight / Math.max(1, pageLayout.pageWidth)) * 100
|
|
110
|
+
: 1.25,
|
|
111
|
+
);
|
|
112
|
+
const topInset = createInsetValue(
|
|
113
|
+
pageBorders.top?.space,
|
|
114
|
+
pageBorders.offsetFrom === "text"
|
|
115
|
+
? (pageLayout.marginTop / Math.max(1, pageLayout.pageHeight)) * 100
|
|
116
|
+
: 1.5,
|
|
117
|
+
);
|
|
118
|
+
const bottomInset = createInsetValue(
|
|
119
|
+
pageBorders.bottom?.space,
|
|
120
|
+
pageBorders.offsetFrom === "text"
|
|
121
|
+
? (pageLayout.marginBottom / Math.max(1, pageLayout.pageHeight)) * 100
|
|
122
|
+
: 1.5,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
top: topInset,
|
|
127
|
+
right: rightInset,
|
|
128
|
+
bottom: bottomInset,
|
|
129
|
+
left: leftInset,
|
|
130
|
+
borderTop: toBorderCss(pageBorders.top),
|
|
131
|
+
borderRight: toBorderCss(pageBorders.right),
|
|
132
|
+
borderBottom: toBorderCss(pageBorders.bottom),
|
|
133
|
+
borderLeft: toBorderCss(pageBorders.left),
|
|
134
|
+
boxSizing: "border-box",
|
|
135
|
+
mixBlendMode: pageBorders.zOrder === "back" ? "multiply" : undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildDocumentGridStyle(
|
|
140
|
+
documentGrid: NonNullable<RuntimeRenderSnapshot["pageLayout"]>["documentGrid"] | undefined,
|
|
141
|
+
): CSSProperties | undefined {
|
|
142
|
+
if (!documentGrid || !documentGrid.type || documentGrid.type === "default") {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const linePitchPx = Math.max(
|
|
147
|
+
18,
|
|
148
|
+
Math.round((documentGrid.linePitch ?? 360) * DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP),
|
|
149
|
+
);
|
|
150
|
+
const charSpacePx = Math.max(
|
|
151
|
+
12,
|
|
152
|
+
Math.round((documentGrid.charSpace ?? 204) * DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP),
|
|
153
|
+
);
|
|
154
|
+
const gridColor = "rgba(15, 23, 42, 0.06)";
|
|
155
|
+
const backgrounds: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
documentGrid.type === "lines" ||
|
|
159
|
+
documentGrid.type === "linesAndChars" ||
|
|
160
|
+
documentGrid.type === "snapToChars"
|
|
161
|
+
) {
|
|
162
|
+
backgrounds.push(
|
|
163
|
+
`repeating-linear-gradient(to bottom, ${gridColor} 0, ${gridColor} 1px, transparent 1px, transparent ${linePitchPx}px)`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (
|
|
167
|
+
documentGrid.type === "linesAndChars" ||
|
|
168
|
+
documentGrid.type === "snapToChars"
|
|
169
|
+
) {
|
|
170
|
+
backgrounds.push(
|
|
171
|
+
`repeating-linear-gradient(to right, rgba(15, 23, 42, 0.04) 0, rgba(15, 23, 42, 0.04) 1px, transparent 1px, transparent ${charSpacePx}px)`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (backgrounds.length === 0) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
backgroundImage: backgrounds.join(", "),
|
|
181
|
+
backgroundOrigin: "content-box",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function createInsetValue(spaceTwips: number | undefined, percent: number): string {
|
|
186
|
+
const spacingPx = Math.max(0, Math.round((spaceTwips ?? 0) * DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP));
|
|
187
|
+
return `calc(${percent.toFixed(2)}% + ${spacingPx}px)`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function toBorderCss(
|
|
191
|
+
border:
|
|
192
|
+
| NonNullable<NonNullable<RuntimeRenderSnapshot["pageLayout"]>["pageBorders"]>["top"]
|
|
193
|
+
| undefined,
|
|
194
|
+
): string | undefined {
|
|
195
|
+
if (!border || border.value === "none" || border.value === "nil") {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const width = border.size ? `${Math.max(1, Math.round(border.size / 8))}px` : "1px";
|
|
200
|
+
const style =
|
|
201
|
+
border.value === "double"
|
|
202
|
+
? "double"
|
|
203
|
+
: border.value === "dotted"
|
|
204
|
+
? "dotted"
|
|
205
|
+
: border.value === "dashed" || border.value === "dashSmallGap"
|
|
206
|
+
? "dashed"
|
|
207
|
+
: "solid";
|
|
208
|
+
const color = border.color && border.color !== "auto" ? `#${border.color}` : "rgba(31, 31, 31, 0.28)";
|
|
209
|
+
return `${width} ${style} ${color}`;
|
|
210
|
+
}
|