@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
|
+
/**
|
|
2
|
+
* @endStateApi v3 — `ui.overlays.getVisibility / setLocalPreference /
|
|
3
|
+
* resetLocalPreference / subscribeVisibility` (U9, state-classes X3).
|
|
4
|
+
*
|
|
5
|
+
* The single composition site for overlay visibility. Merges:
|
|
6
|
+
* - class-A policy → `handle.getVisibilityPolicy(kind)` (L06 X1).
|
|
7
|
+
* - class-C preference → per-UI-API-instance Map in the factory closure.
|
|
8
|
+
*
|
|
9
|
+
* No renderer, chrome surface, or presentation component duplicates
|
|
10
|
+
* this logic — `ui.overlays.getVisibility` is the only path per
|
|
11
|
+
* architecture 10 §U9.
|
|
12
|
+
*
|
|
13
|
+
* Storage shape (closure-local, per `createUiApi(handle)` instance):
|
|
14
|
+
* - `localPrefs: Map<OverlayKind, boolean>` — user's explicit pref.
|
|
15
|
+
* - `subscribers: Map<OverlayKind, Set<UiListener<OverlayVisibility>>>` —
|
|
16
|
+
* listeners fired on pref changes.
|
|
17
|
+
*
|
|
18
|
+
* Composition pseudocode (architecture 10 §U9 + `00-overview.md §9.1`):
|
|
19
|
+
*
|
|
20
|
+
* policy = handle.getVisibilityPolicy(kind) // may be null
|
|
21
|
+
* pref = localPrefs.get(kind) // may be undefined
|
|
22
|
+
*
|
|
23
|
+
* # debug-panel special case: debugMode gate wins over everything.
|
|
24
|
+
* if kind === "debug-panel" and debugMode === "off":
|
|
25
|
+
* → { state: "forced-off", reason: "policy-enforcement" }
|
|
26
|
+
*
|
|
27
|
+
* # Policy enforcement is a hard override.
|
|
28
|
+
* if policy.enforcement === "always":
|
|
29
|
+
* → { state: "forced-on", reason: "policy-enforcement" }
|
|
30
|
+
* if policy.enforcement === "never":
|
|
31
|
+
* → { state: "forced-off", reason: "policy-enforcement" }
|
|
32
|
+
*
|
|
33
|
+
* # "authored-default" cedes to local preference.
|
|
34
|
+
* if pref === true: → { state: "visible", reason: "user-preference" }
|
|
35
|
+
* if pref === false: → { state: "hidden", reason: "user-preference" }
|
|
36
|
+
*
|
|
37
|
+
* # No preference → use policy default (or intrinsic default if no policy).
|
|
38
|
+
* defaultOn = policy.defaultOn ?? INTRINSIC_DEFAULTS[kind]
|
|
39
|
+
* → defaultOn ? visible : hidden, reason: "policy-default"
|
|
40
|
+
*
|
|
41
|
+
* Subscribe fan-out is local-preference-only in this slice. Policy
|
|
42
|
+
* changes and debugMode changes do not wake listeners yet — consumers
|
|
43
|
+
* that depend on either re-read visibility on their own cadence.
|
|
44
|
+
* Follow-up slice will subscribe to the L06 policy telemetry bus and
|
|
45
|
+
* fan out on `workflow.visibility_policy_changed`.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { RuntimeApiHandle } from "../_runtime-handle.ts";
|
|
49
|
+
import type { OverlayKind, OverlayVisibilityPolicy } from "../../public-types.ts";
|
|
50
|
+
import type {
|
|
51
|
+
OverlayVisibility,
|
|
52
|
+
UiListener,
|
|
53
|
+
UiUnsubscribe,
|
|
54
|
+
} from "./_types.ts";
|
|
55
|
+
import type { UiApiContext } from "./_context.ts";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Intrinsic defaults — used when no class-A policy has been authored for
|
|
59
|
+
* a kind. Mirrors the docs/wiki convention: scope/comment/tracked-changes
|
|
60
|
+
* on; suggestions + debug-panel off; presence on (collab default).
|
|
61
|
+
*/
|
|
62
|
+
const INTRINSIC_DEFAULT_ON: Readonly<Record<OverlayKind, boolean>> = {
|
|
63
|
+
"scope-rail": true,
|
|
64
|
+
"comments": true,
|
|
65
|
+
"tracked-changes": true,
|
|
66
|
+
"suggestions": false,
|
|
67
|
+
"debug-panel": false,
|
|
68
|
+
"presence": true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Per-instance visibility state. `createUiApi(handle)` constructs one of
|
|
73
|
+
* these; it closes over the overlays family.
|
|
74
|
+
*/
|
|
75
|
+
export interface OverlaysVisibilityState {
|
|
76
|
+
readonly localPrefs: Map<OverlayKind, boolean>;
|
|
77
|
+
readonly subscribers: Map<OverlayKind, Set<UiListener<OverlayVisibility>>>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createOverlaysVisibilityState(): OverlaysVisibilityState {
|
|
81
|
+
return {
|
|
82
|
+
localPrefs: new Map(),
|
|
83
|
+
subscribers: new Map(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read the current effective debugMode from the bound controller's host
|
|
89
|
+
* posture slice. Mirrors the default handling in `chrome.ts::getPosture`
|
|
90
|
+
* — unbound / no hook / no debugMode field all resolve to `"off"` per
|
|
91
|
+
* U6 + CLAUDE.md Protected Invariants.
|
|
92
|
+
*/
|
|
93
|
+
function readDebugMode(ctx: UiApiContext): "off" | "top-bar" | "full" {
|
|
94
|
+
return ctx.binding?.controller.getHostPosture?.()?.debugMode ?? "off";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readPolicy(
|
|
98
|
+
handle: RuntimeApiHandle,
|
|
99
|
+
kind: OverlayKind,
|
|
100
|
+
): OverlayVisibilityPolicy | null {
|
|
101
|
+
// `getVisibilityPolicy` is on `RuntimeApiHandle` (Layer 06 X1 +
|
|
102
|
+
// L07 roster update). Read directly — no cast. The function itself
|
|
103
|
+
// returns null on unknown/unset policy, so the handle covers the
|
|
104
|
+
// pre-paint case too.
|
|
105
|
+
return handle.getVisibilityPolicy(kind) ?? null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Compose the OverlayVisibility record for a single kind. Pure except
|
|
110
|
+
* for the handle / ctx reads — no side effects, no allocation beyond
|
|
111
|
+
* the result object.
|
|
112
|
+
*/
|
|
113
|
+
export function composeOverlayVisibility(
|
|
114
|
+
ctx: UiApiContext,
|
|
115
|
+
state: OverlaysVisibilityState,
|
|
116
|
+
kind: OverlayKind,
|
|
117
|
+
): OverlayVisibility {
|
|
118
|
+
// debug-panel: when debugMode is "off" the panel is forced off
|
|
119
|
+
// regardless of policy or preference (CLAUDE.md Protected Invariant).
|
|
120
|
+
if (kind === "debug-panel" && readDebugMode(ctx) === "off") {
|
|
121
|
+
return { state: "forced-off", reason: "policy-enforcement" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const policy = readPolicy(ctx.handle, kind);
|
|
125
|
+
const pref = state.localPrefs.get(kind);
|
|
126
|
+
|
|
127
|
+
// Hard policy overrides.
|
|
128
|
+
if (policy?.enforcement === "always") {
|
|
129
|
+
return { state: "forced-on", reason: "policy-enforcement" };
|
|
130
|
+
}
|
|
131
|
+
if (policy?.enforcement === "never") {
|
|
132
|
+
return { state: "forced-off", reason: "policy-enforcement" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// authored-default / no policy: local preference wins if expressed.
|
|
136
|
+
if (pref === true) {
|
|
137
|
+
return { state: "visible", reason: "user-preference" };
|
|
138
|
+
}
|
|
139
|
+
if (pref === false) {
|
|
140
|
+
return { state: "hidden", reason: "user-preference" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// No preference — fall back to policy default, else intrinsic default.
|
|
144
|
+
const defaultOn = policy?.defaultOn ?? INTRINSIC_DEFAULT_ON[kind];
|
|
145
|
+
return defaultOn
|
|
146
|
+
? { state: "visible", reason: "policy-default" }
|
|
147
|
+
: { state: "hidden", reason: "policy-default" };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Notify all subscribers for a kind with the current composed visibility.
|
|
152
|
+
* Called after `setLocalPreference` / `resetLocalPreference`.
|
|
153
|
+
*/
|
|
154
|
+
export function notifyVisibilitySubscribers(
|
|
155
|
+
ctx: UiApiContext,
|
|
156
|
+
state: OverlaysVisibilityState,
|
|
157
|
+
kind: OverlayKind,
|
|
158
|
+
): void {
|
|
159
|
+
const listeners = state.subscribers.get(kind);
|
|
160
|
+
if (!listeners || listeners.size === 0) return;
|
|
161
|
+
const visibility = composeOverlayVisibility(ctx, state, kind);
|
|
162
|
+
for (const listener of listeners) {
|
|
163
|
+
try {
|
|
164
|
+
listener(visibility);
|
|
165
|
+
} catch {
|
|
166
|
+
// Isolate listener errors — one broken subscriber must not block
|
|
167
|
+
// the rest of the notification chain.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @endStateApi v3 — `ui.overlays` family (layer 10).
|
|
3
|
+
*
|
|
4
|
+
* Resolution order per OverlayAnchorQuery.kind:
|
|
5
|
+
*
|
|
6
|
+
* - Id-keyed kinds (block / scope / comment / revision / page):
|
|
7
|
+
* `handle.geometry.getAnchor(AnchorQuery)` directly; controller
|
|
8
|
+
* hook is the fallback when geometry returns null. Slice 11
|
|
9
|
+
* promoted these to `live` against geometry.
|
|
10
|
+
*
|
|
11
|
+
* - `kind: "selection"`: controller hook first (the shell-side
|
|
12
|
+
* `ShellOverlayAnchorBridge` populated by
|
|
13
|
+
* `useShellSelectionAnchorBridge` — carries tool-aware anchor
|
|
14
|
+
* semantics via `resolveSelectionAnchor`). `handle.geometry.
|
|
15
|
+
* getSelectionRects` is the headless / pre-mount fallback that
|
|
16
|
+
* returns the raw range rect without tool-context awareness.
|
|
17
|
+
* Flicker-remediation 2026-04-22 flipped this order (previously
|
|
18
|
+
* geometry-first), because raw-rects pre-empted the bridge's
|
|
19
|
+
* tool-aware anchor for structural tools (image, table-cell).
|
|
20
|
+
*
|
|
21
|
+
* - subscribe: live-with-adapter. Delegates to the bound
|
|
22
|
+
* controller's `subscribeOverlays` hook. Geometry invalidation
|
|
23
|
+
* streaming into the UI API direct from `handle.geometry` is a
|
|
24
|
+
* follow-up slice (needs a rAF-coalesced fan-out that doesn't
|
|
25
|
+
* exist on the geometry facet today).
|
|
26
|
+
*
|
|
27
|
+
* Return shape when geometry/handle is unavailable:
|
|
28
|
+
* - getAnchor returns null (never throws). Per U4, "if geometry is
|
|
29
|
+
* unavailable, return null — do not silently measure DOM."
|
|
30
|
+
* - getRects returns [].
|
|
31
|
+
* - subscribe throws — subscription is a stateful wiring decision; a
|
|
32
|
+
* silent no-op would hide broken event routing.
|
|
33
|
+
*
|
|
34
|
+
* Contract U4 — overlays derive anchors from geometry, not DOM. The
|
|
35
|
+
* shell bridge itself is geometry-backed (the resolver reads
|
|
36
|
+
* `geometryFacet.getRenderFrame().anchorIndex`); the UI API never
|
|
37
|
+
* measures DOM.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
41
|
+
import type {
|
|
42
|
+
GeometryRect,
|
|
43
|
+
OverlayAnchorQuery,
|
|
44
|
+
OverlayVisibility,
|
|
45
|
+
UiListener,
|
|
46
|
+
UiUnsubscribe,
|
|
47
|
+
} from "./_types.ts";
|
|
48
|
+
import type { OverlayKind, SelectionSnapshot } from "../../public-types.ts";
|
|
49
|
+
import type { AnchorQuery } from "../../../runtime/geometry/geometry-types.ts";
|
|
50
|
+
import type { UiApiContext } from "./_context.ts";
|
|
51
|
+
import {
|
|
52
|
+
composeOverlayVisibility,
|
|
53
|
+
createOverlaysVisibilityState,
|
|
54
|
+
notifyVisibilitySubscribers,
|
|
55
|
+
} from "./overlays-visibility.ts";
|
|
56
|
+
import { emitUxResponse } from "../_ux-response.ts";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Translate the UI API's `OverlayAnchorQuery` (public, presentation-shaped) to
|
|
60
|
+
* the geometry facet's id-keyed `AnchorQuery`. Returns null for
|
|
61
|
+
* `kind: "selection"` — selection doesn't live in the id-keyed anchor
|
|
62
|
+
* index; it's resolved through a separate code path via
|
|
63
|
+
* `handle.geometry.getSelectionRects` (see `resolveSelectionRects` in
|
|
64
|
+
* this file).
|
|
65
|
+
*/
|
|
66
|
+
function toAnchorQuery(query: OverlayAnchorQuery): AnchorQuery | null {
|
|
67
|
+
switch (query.kind) {
|
|
68
|
+
case "block":
|
|
69
|
+
return { kind: "block-id", value: query.value };
|
|
70
|
+
case "scope":
|
|
71
|
+
return { kind: "scope-id", value: query.value };
|
|
72
|
+
case "comment":
|
|
73
|
+
return { kind: "comment-id", value: query.value };
|
|
74
|
+
case "revision":
|
|
75
|
+
return { kind: "revision-id", value: query.value };
|
|
76
|
+
case "page":
|
|
77
|
+
return { kind: "page-index", value: query.value };
|
|
78
|
+
case "selection":
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Resolve `OverlayAnchorQuery.kind === "selection"` via
|
|
85
|
+
* `handle.geometry.getSelectionRects({from, to, story})`. The current
|
|
86
|
+
* selection range is read from `handle.getRenderSnapshot().selection`
|
|
87
|
+
* (collapsed selections return []; callers that want caret geometry
|
|
88
|
+
* use `handle.geometry.getCaret(offset)` directly).
|
|
89
|
+
*
|
|
90
|
+
* Returns `[]` when geometry / render snapshot are unavailable (pre-paint
|
|
91
|
+
* / headless handles that don't expose the method). Never reaches DOM
|
|
92
|
+
* per U4.
|
|
93
|
+
*/
|
|
94
|
+
function resolveSelectionRects(ctx: UiApiContext): readonly GeometryRect[] {
|
|
95
|
+
const render = ctx.handle.getRenderSnapshot?.() as
|
|
96
|
+
| { selection?: SelectionSnapshot }
|
|
97
|
+
| null
|
|
98
|
+
| undefined;
|
|
99
|
+
const selection = render?.selection;
|
|
100
|
+
if (!selection || selection.isCollapsed) return [];
|
|
101
|
+
const getSelectionRects = ctx.handle.geometry?.getSelectionRects;
|
|
102
|
+
if (typeof getSelectionRects !== "function") return [];
|
|
103
|
+
const from = Math.min(selection.anchor, selection.head);
|
|
104
|
+
const to = Math.max(selection.anchor, selection.head);
|
|
105
|
+
return getSelectionRects({
|
|
106
|
+
from,
|
|
107
|
+
to,
|
|
108
|
+
story: selection.storyTarget,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const getAnchorMetadata: ApiV3FnMetadata = {
|
|
113
|
+
name: "ui.overlays.getAnchor",
|
|
114
|
+
status: "live",
|
|
115
|
+
sourceLayer: "presentation",
|
|
116
|
+
liveEvidence: {
|
|
117
|
+
runnerTest: "test/api/v3/ui/overlays-anchor.test.ts",
|
|
118
|
+
commit: "refactor-10-slice-11",
|
|
119
|
+
},
|
|
120
|
+
uxIntent: { uiVisible: false },
|
|
121
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "document", auditCategory: "ui-overlays-read" },
|
|
122
|
+
stateClass: "C-local",
|
|
123
|
+
persistsTo: "none",
|
|
124
|
+
rwdReference: "§UI API § ui.overlays.getAnchor. For id-keyed kinds (block/scope/comment/revision/page): reads handle.geometry.getAnchor(query) directly; controller hook is the fallback when geometry returns null. For kind:'selection': controller hook (shell bridge — tool-aware) is the primary path; handle.geometry.getSelectionRects is the fallback for headless / pre-mount.",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const getRectsMetadata: ApiV3FnMetadata = {
|
|
128
|
+
name: "ui.overlays.getRects",
|
|
129
|
+
status: "live",
|
|
130
|
+
sourceLayer: "presentation",
|
|
131
|
+
liveEvidence: {
|
|
132
|
+
runnerTest: "test/api/v3/ui/overlays-anchor.test.ts",
|
|
133
|
+
commit: "refactor-10-slice-11",
|
|
134
|
+
},
|
|
135
|
+
uxIntent: { uiVisible: false },
|
|
136
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "scope", auditCategory: "ui-overlays-read" },
|
|
137
|
+
stateClass: "C-local",
|
|
138
|
+
persistsTo: "none",
|
|
139
|
+
rwdReference: "§UI API § ui.overlays.getRects. For id-keyed kinds: reads handle.geometry.getAnchorRects(query) directly; controller hook is the fallback when geometry returns []. For kind:'selection': controller hook (shell bridge — tool-aware) is the primary path; handle.geometry.getSelectionRects is the fallback.",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const subscribeMetadata: ApiV3FnMetadata = {
|
|
143
|
+
name: "ui.overlays.subscribe",
|
|
144
|
+
status: "live-with-adapter",
|
|
145
|
+
sourceLayer: "presentation",
|
|
146
|
+
liveEvidence: {
|
|
147
|
+
runnerTest: "test/api/v3/ui/overlays-anchor.test.ts",
|
|
148
|
+
commit: "refactor-07-slice-2",
|
|
149
|
+
},
|
|
150
|
+
// Stream-form bidirectional channel (U4 + U7). Subscribe call is
|
|
151
|
+
// uiVisible — attaching the listener re-parents which overlays are
|
|
152
|
+
// re-resolved on geometry invalidation. Per-invalidation OverlayAnchorQuery
|
|
153
|
+
// deliveries travel through the listener.
|
|
154
|
+
uxIntent: {
|
|
155
|
+
uiVisible: true,
|
|
156
|
+
expectsUxResponse: "surface-refresh",
|
|
157
|
+
expectedDelta: "overlay subscriber attached; geometry invalidations that overlap attached queries propagate through the listener",
|
|
158
|
+
},
|
|
159
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "document", auditCategory: "ui-overlays-subscribe" },
|
|
160
|
+
stateClass: "C-local",
|
|
161
|
+
persistsTo: "none",
|
|
162
|
+
bidirectional: true,
|
|
163
|
+
subscriptionShape: {
|
|
164
|
+
eventType: "ui.overlays.invalidated",
|
|
165
|
+
payloadType: "OverlayAnchorQuery",
|
|
166
|
+
coalescing: "raf",
|
|
167
|
+
},
|
|
168
|
+
rwdReference: "§UI API § ui.overlays.subscribe. Adapter delegates to UiController.subscribeOverlays; throws when the active binding has no hook. Subscribe call emits one `ux.response.ui.overlays.subscribe` acknowledgement; per-invalidation OverlayAnchorQuery deliveries flow through the listener.",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// ----- U9 overlay-visibility metadata (state-classes X3) -----
|
|
172
|
+
|
|
173
|
+
export const getVisibilityMetadata: ApiV3FnMetadata = {
|
|
174
|
+
name: "ui.overlays.getVisibility",
|
|
175
|
+
status: "live",
|
|
176
|
+
sourceLayer: "presentation",
|
|
177
|
+
liveEvidence: {
|
|
178
|
+
runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
|
|
179
|
+
commit: "refactor-10-slice-x3",
|
|
180
|
+
},
|
|
181
|
+
uxIntent: { uiVisible: false },
|
|
182
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-read" },
|
|
183
|
+
stateClass: "C-local",
|
|
184
|
+
persistsTo: "none",
|
|
185
|
+
rwdReference: "§UI API § ui.overlays.getVisibility (U9). Single composition site merging class-A policy (handle.getVisibilityPolicy) with class-C local preference. debug-panel is additionally gated on ChromePosture.debugMode per CLAUDE.md Protected Invariant.",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const setLocalPreferenceMetadata: ApiV3FnMetadata = {
|
|
189
|
+
name: "ui.overlays.setLocalPreference",
|
|
190
|
+
status: "live",
|
|
191
|
+
sourceLayer: "presentation",
|
|
192
|
+
liveEvidence: {
|
|
193
|
+
runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
|
|
194
|
+
commit: "refactor-10-slice-x3",
|
|
195
|
+
},
|
|
196
|
+
uxIntent: {
|
|
197
|
+
uiVisible: true,
|
|
198
|
+
expectsUxResponse: "surface-refresh",
|
|
199
|
+
expectedDelta: "overlay local preference set; subscribers for the kind receive the new composed visibility",
|
|
200
|
+
},
|
|
201
|
+
agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-overlays-set-preference" },
|
|
202
|
+
stateClass: "C-local",
|
|
203
|
+
persistsTo: "none",
|
|
204
|
+
rwdReference: "§UI API § ui.overlays.setLocalPreference (U9). Class-C write; no broadcast, no canonical persistence. Hosts that want cross-reload survival save the preferences on beforeunload and restore on mount.",
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const resetLocalPreferenceMetadata: ApiV3FnMetadata = {
|
|
208
|
+
name: "ui.overlays.resetLocalPreference",
|
|
209
|
+
status: "live",
|
|
210
|
+
sourceLayer: "presentation",
|
|
211
|
+
liveEvidence: {
|
|
212
|
+
runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
|
|
213
|
+
commit: "refactor-10-slice-x3",
|
|
214
|
+
},
|
|
215
|
+
uxIntent: {
|
|
216
|
+
uiVisible: true,
|
|
217
|
+
expectsUxResponse: "surface-refresh",
|
|
218
|
+
expectedDelta: "overlay local preference cleared; subsequent reads fall back to policy default",
|
|
219
|
+
},
|
|
220
|
+
agentMetadata: { readOrMutate: "mutate", boundedScope: "session", auditCategory: "ui-overlays-reset-preference" },
|
|
221
|
+
stateClass: "C-local",
|
|
222
|
+
persistsTo: "none",
|
|
223
|
+
rwdReference: "§UI API § ui.overlays.resetLocalPreference (U9). Class-C write — clears the per-session preference for a specific OverlayKind.",
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const subscribeVisibilityMetadata: ApiV3FnMetadata = {
|
|
227
|
+
name: "ui.overlays.subscribeVisibility",
|
|
228
|
+
status: "live",
|
|
229
|
+
sourceLayer: "presentation",
|
|
230
|
+
liveEvidence: {
|
|
231
|
+
runnerTest: "test/api/v3/ui/overlays-visibility.test.ts",
|
|
232
|
+
commit: "refactor-10-slice-x3",
|
|
233
|
+
},
|
|
234
|
+
uxIntent: {
|
|
235
|
+
uiVisible: true,
|
|
236
|
+
expectsUxResponse: "surface-refresh",
|
|
237
|
+
expectedDelta: "visibility subscriber attached; per-kind OverlayVisibility deliveries propagate on local-preference change",
|
|
238
|
+
},
|
|
239
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-subscribe-visibility" },
|
|
240
|
+
stateClass: "C-local",
|
|
241
|
+
persistsTo: "none",
|
|
242
|
+
bidirectional: true,
|
|
243
|
+
subscriptionShape: {
|
|
244
|
+
eventType: "ui.overlays.visibility_changed",
|
|
245
|
+
payloadType: "OverlayVisibility",
|
|
246
|
+
coalescing: "microtask",
|
|
247
|
+
},
|
|
248
|
+
rwdReference: "§UI API § ui.overlays.subscribeVisibility (U9). Fires on BOTH local-preference mutation AND class-A policy-change (closed 2026-04-22 — chained to L06's handle.subscribeVisibilityPolicy). debugMode-change fan-out remains a Phase Q follow-up.",
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export function createOverlaysFamily(ctx: UiApiContext) {
|
|
252
|
+
// Closure-local visibility state — per UI API instance. Cross-instance
|
|
253
|
+
// isolation is structural (two createUiApi(handle) calls hold two
|
|
254
|
+
// states). Storage is in-memory; hosts persist across reloads via
|
|
255
|
+
// beforeunload/restore per architecture 10 §U9.
|
|
256
|
+
const visibilityState = createOverlaysVisibilityState();
|
|
257
|
+
|
|
258
|
+
// W10 policy-change fan-out — when L06 mutates the class-A policy
|
|
259
|
+
// (via runtime.workflow.setVisibilityPolicy / clearVisibilityPolicy),
|
|
260
|
+
// re-notify every kind that has a live subscriber. This closes the
|
|
261
|
+
// follow-up flagged in `subscribeVisibilityMetadata.rwdReference`:
|
|
262
|
+
// previously we only fired on local-preference changes, leaving
|
|
263
|
+
// authoring-tool policy mutations invisible until a doc reload.
|
|
264
|
+
//
|
|
265
|
+
// The subscription is global (not per-kind) because L06's hook fires
|
|
266
|
+
// for ANY policy change — we iterate our local subscriber map on fire
|
|
267
|
+
// and compose a fresh OverlayVisibility per kind. Cost: O(subscribed
|
|
268
|
+
// kinds) × O(composeOverlayVisibility), bounded by 6 kinds.
|
|
269
|
+
// `subscribeVisibilityPolicy` is on `RuntimeApiHandle` (L06 X1 + L07
|
|
270
|
+
// roster update). Optional-chain defensively: legacy / test-stub
|
|
271
|
+
// handles that predate X1 are allowed to omit it — the graceful-
|
|
272
|
+
// degradation contract is tested at `overlays-visibility.test.ts`
|
|
273
|
+
// "X1⇄X3 — handles without subscribeVisibilityPolicy degrade
|
|
274
|
+
// gracefully (no throw)".
|
|
275
|
+
//
|
|
276
|
+
// TODO(ui.dispose): we intentionally do not retain the unsubscribe
|
|
277
|
+
// here — the family instance lives for the lifetime of the host's UI
|
|
278
|
+
// API, and disposal of that API also collapses the handle. If host
|
|
279
|
+
// teardown ever gains a ui.dispose() hook, the unsubscribe ref MUST
|
|
280
|
+
// be retained and invoked there to avoid a leak on the L06 policy-
|
|
281
|
+
// broadcast channel.
|
|
282
|
+
ctx.handle.subscribeVisibilityPolicy?.(() => {
|
|
283
|
+
for (const kind of visibilityState.subscribers.keys()) {
|
|
284
|
+
notifyVisibilitySubscribers(ctx, visibilityState, kind);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
getAnchor(query: OverlayAnchorQuery): GeometryRect | null {
|
|
290
|
+
// `kind: "selection"` — bridge-first, geometry fallback.
|
|
291
|
+
//
|
|
292
|
+
// Flicker-remediation 2026-04-22: Slice 11's original geometry-
|
|
293
|
+
// first order pre-empted the shell-side bridge. The bridge
|
|
294
|
+
// (populated by `useShellSelectionAnchorBridge` via
|
|
295
|
+
// `resolveSelectionAnchor`) carries tool-aware semantics —
|
|
296
|
+
// structural-context tools resolve to the containing block or
|
|
297
|
+
// table-cell rect, not the raw selection range. Raw
|
|
298
|
+
// `handle.geometry.getSelectionRects` returns only the range
|
|
299
|
+
// rects; using them for a tool-active selection placed the
|
|
300
|
+
// floating surface against the wrong anchor. Order swapped so
|
|
301
|
+
// the shell's semantically-correct resolver wins when mounted;
|
|
302
|
+
// geometry remains the fallback for headless / pre-mount cases
|
|
303
|
+
// where no controller is bound.
|
|
304
|
+
if (query.kind === "selection") {
|
|
305
|
+
const hook = ctx.binding?.controller.getOverlayAnchor;
|
|
306
|
+
if (hook) {
|
|
307
|
+
const fromBridge = hook(query);
|
|
308
|
+
if (fromBridge) return fromBridge;
|
|
309
|
+
}
|
|
310
|
+
const rects = resolveSelectionRects(ctx);
|
|
311
|
+
return rects.length > 0 ? (rects[0] ?? null) : null;
|
|
312
|
+
}
|
|
313
|
+
const anchorQuery = toAnchorQuery(query);
|
|
314
|
+
if (anchorQuery) {
|
|
315
|
+
const fromGeometry = ctx.handle.geometry?.getAnchor?.(anchorQuery) ?? null;
|
|
316
|
+
if (fromGeometry) return fromGeometry;
|
|
317
|
+
}
|
|
318
|
+
const hook = ctx.binding?.controller.getOverlayAnchor;
|
|
319
|
+
// U4: geometry unavailable AND no controller hook → null.
|
|
320
|
+
// MUST NOT fall back to DOM measurement.
|
|
321
|
+
return hook ? hook(query) : null;
|
|
322
|
+
},
|
|
323
|
+
getRects(query: OverlayAnchorQuery): readonly GeometryRect[] {
|
|
324
|
+
// Bridge-first, geometry fallback — same rationale as getAnchor.
|
|
325
|
+
if (query.kind === "selection") {
|
|
326
|
+
const hook = ctx.binding?.controller.getOverlayRects;
|
|
327
|
+
if (hook) {
|
|
328
|
+
const fromBridge = hook(query);
|
|
329
|
+
if (fromBridge.length > 0) return fromBridge;
|
|
330
|
+
}
|
|
331
|
+
return resolveSelectionRects(ctx);
|
|
332
|
+
}
|
|
333
|
+
const anchorQuery = toAnchorQuery(query);
|
|
334
|
+
if (anchorQuery) {
|
|
335
|
+
const fromGeometry = ctx.handle.geometry?.getAnchorRects?.(anchorQuery) ?? [];
|
|
336
|
+
if (fromGeometry.length > 0) return fromGeometry;
|
|
337
|
+
}
|
|
338
|
+
const hook = ctx.binding?.controller.getOverlayRects;
|
|
339
|
+
return hook ? hook(query) : [];
|
|
340
|
+
},
|
|
341
|
+
subscribe(listener: UiListener<OverlayAnchorQuery>): UiUnsubscribe {
|
|
342
|
+
const controller = ctx.binding?.controller;
|
|
343
|
+
if (!controller) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
"ui.overlays.subscribe: no controller bound — call ui.session.bind(controller) first",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (!controller.subscribeOverlays) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`ui.overlays.subscribe: controller of kind "${controller.kind}" did not provide a subscribeOverlays hook`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const unsubscribe = controller.subscribeOverlays(listener);
|
|
354
|
+
emitUxResponse(ctx.handle, {
|
|
355
|
+
apiFn: subscribeMetadata.name,
|
|
356
|
+
intent: subscribeMetadata.uxIntent.expectedDelta ?? "",
|
|
357
|
+
mockOrLive: "live-with-adapter",
|
|
358
|
+
uiVisible: true,
|
|
359
|
+
expectedDelta: subscribeMetadata.uxIntent.expectedDelta,
|
|
360
|
+
actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.overlays" } },
|
|
361
|
+
});
|
|
362
|
+
return unsubscribe;
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// ----- U9 overlay-visibility (state-classes X3) -----
|
|
366
|
+
|
|
367
|
+
getVisibility(kind: OverlayKind): OverlayVisibility {
|
|
368
|
+
return composeOverlayVisibility(ctx, visibilityState, kind);
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
setLocalPreference(kind: OverlayKind, visible: boolean): void {
|
|
372
|
+
const prev = visibilityState.localPrefs.get(kind);
|
|
373
|
+
if (prev === visible) return; // idempotent — no spurious wakeups.
|
|
374
|
+
visibilityState.localPrefs.set(kind, visible);
|
|
375
|
+
emitUxResponse(ctx.handle, {
|
|
376
|
+
apiFn: setLocalPreferenceMetadata.name,
|
|
377
|
+
intent: setLocalPreferenceMetadata.uxIntent.expectedDelta ?? "",
|
|
378
|
+
mockOrLive: "live",
|
|
379
|
+
uiVisible: true,
|
|
380
|
+
expectedDelta: setLocalPreferenceMetadata.uxIntent.expectedDelta,
|
|
381
|
+
actualDelta: { kind: "surface-refresh", payload: { overlay: kind, visible } },
|
|
382
|
+
});
|
|
383
|
+
notifyVisibilitySubscribers(ctx, visibilityState, kind);
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
resetLocalPreference(kind: OverlayKind): void {
|
|
387
|
+
const had = visibilityState.localPrefs.has(kind);
|
|
388
|
+
if (!had) return; // idempotent.
|
|
389
|
+
visibilityState.localPrefs.delete(kind);
|
|
390
|
+
emitUxResponse(ctx.handle, {
|
|
391
|
+
apiFn: resetLocalPreferenceMetadata.name,
|
|
392
|
+
intent: resetLocalPreferenceMetadata.uxIntent.expectedDelta ?? "",
|
|
393
|
+
mockOrLive: "live",
|
|
394
|
+
uiVisible: true,
|
|
395
|
+
expectedDelta: resetLocalPreferenceMetadata.uxIntent.expectedDelta,
|
|
396
|
+
actualDelta: { kind: "surface-refresh", payload: { overlay: kind, reset: true } },
|
|
397
|
+
});
|
|
398
|
+
notifyVisibilitySubscribers(ctx, visibilityState, kind);
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
subscribeVisibility(
|
|
402
|
+
kind: OverlayKind,
|
|
403
|
+
listener: UiListener<OverlayVisibility>,
|
|
404
|
+
): UiUnsubscribe {
|
|
405
|
+
let listeners = visibilityState.subscribers.get(kind);
|
|
406
|
+
if (!listeners) {
|
|
407
|
+
listeners = new Set();
|
|
408
|
+
visibilityState.subscribers.set(kind, listeners);
|
|
409
|
+
}
|
|
410
|
+
listeners.add(listener);
|
|
411
|
+
emitUxResponse(ctx.handle, {
|
|
412
|
+
apiFn: subscribeVisibilityMetadata.name,
|
|
413
|
+
intent: subscribeVisibilityMetadata.uxIntent.expectedDelta ?? "",
|
|
414
|
+
mockOrLive: "live",
|
|
415
|
+
uiVisible: true,
|
|
416
|
+
expectedDelta: subscribeVisibilityMetadata.uxIntent.expectedDelta,
|
|
417
|
+
actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.overlays.visibility", overlay: kind } },
|
|
418
|
+
});
|
|
419
|
+
return () => {
|
|
420
|
+
const current = visibilityState.subscribers.get(kind);
|
|
421
|
+
if (!current) return;
|
|
422
|
+
current.delete(listener);
|
|
423
|
+
if (current.size === 0) visibilityState.subscribers.delete(kind);
|
|
424
|
+
};
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @endStateApi v3 — `ui.scope` family (layer 10).
|
|
3
|
+
*
|
|
4
|
+
* Opens the mounted-path scope-read seam requested by coord-08 §1 +
|
|
5
|
+
* coord-11 §5/§50 ("L08 §2 blocked on L10 ui.scope.* seam"). Unblocked
|
|
6
|
+
* by L07's `compileScopeBundleById` handle primitive (refactor/07
|
|
7
|
+
* commit `3ffac7fe`, 2026-04-23).
|
|
8
|
+
*
|
|
9
|
+
* This slice ships the MVP primitive:
|
|
10
|
+
*
|
|
11
|
+
* ui.scope.getBundle(scopeId: string): ScopeBundle | null
|
|
12
|
+
*
|
|
13
|
+
* Thin wrapper over `handle.compileScopeBundleById(scopeId, nowUtc)`.
|
|
14
|
+
* `nowUtc` is generated internally via `new Date().toISOString()` so UI
|
|
15
|
+
* consumers don't have to thread a clock through their code. The
|
|
16
|
+
* underlying AI API (`ai.getScopeBundle`) keeps its explicit-clock
|
|
17
|
+
* contract for agent-replay determinism; the UI seam trades that for
|
|
18
|
+
* ergonomic one-call scope reads appropriate for interactive UX.
|
|
19
|
+
*
|
|
20
|
+
* Enumeration — `ui.scope.list()` returning `SemanticScope[]` — is a
|
|
21
|
+
* deferred follow-up. Coord-11 §5 scope-card migration needs
|
|
22
|
+
* one-at-a-time bundle reads (the card mounts for a selected scope);
|
|
23
|
+
* broad enumeration stays on L11 using existing `WorkflowScope`-typed
|
|
24
|
+
* snapshots from the rail until a batch primitive exists.
|
|
25
|
+
*
|
|
26
|
+
* Contract compliance:
|
|
27
|
+
* - U1: reads through `RuntimeApiHandle` + returned ScopeBundle is
|
|
28
|
+
* pure canonical-document projection. No DOM / PM.
|
|
29
|
+
* - stateClass: A-canonical (canonical document truth). Unlike other
|
|
30
|
+
* L10 families which are C-local, this one surfaces runtime-owned
|
|
31
|
+
* canonical state — an intentional exception documented here.
|
|
32
|
+
* Persisted to canonical per the underlying scope authoring.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
36
|
+
import type { ScopeBundle } from "../../public-types.ts";
|
|
37
|
+
import type { UiApiContext } from "./_context.ts";
|
|
38
|
+
|
|
39
|
+
export const getBundleMetadata: ApiV3FnMetadata = {
|
|
40
|
+
name: "ui.scope.getBundle",
|
|
41
|
+
status: "live-with-adapter",
|
|
42
|
+
sourceLayer: "presentation",
|
|
43
|
+
liveEvidence: {
|
|
44
|
+
runnerTest: "test/api/v3/ui/scope-get-bundle.test.ts",
|
|
45
|
+
commit: "refactor-10-slice-ui-scope-mvp",
|
|
46
|
+
},
|
|
47
|
+
uxIntent: { uiVisible: false },
|
|
48
|
+
agentMetadata: {
|
|
49
|
+
readOrMutate: "read",
|
|
50
|
+
boundedScope: "scope",
|
|
51
|
+
auditCategory: "ui-scope-read",
|
|
52
|
+
},
|
|
53
|
+
// UI-surface convenience wrapper over a canonical read. Classification
|
|
54
|
+
// mirrors the underlying `ai.getScopeBundle` — reading canonical
|
|
55
|
+
// scope state, not class-C local UI preference.
|
|
56
|
+
stateClass: "A-canonical",
|
|
57
|
+
persistsTo: "canonical",
|
|
58
|
+
rwdReference:
|
|
59
|
+
"§UI API § ui.scope.getBundle. Wraps handle.compileScopeBundleById(scopeId, nowUtc). nowUtc generated internally from new Date().toISOString() for ergonomic UI use; callers that need determinism (agent replay, tests) use ai.getScopeBundle with an explicit clock.",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function createScopeFamily(ctx: UiApiContext) {
|
|
63
|
+
return {
|
|
64
|
+
getBundle(scopeId: string): ScopeBundle | null {
|
|
65
|
+
const compile = ctx.handle.compileScopeBundleById;
|
|
66
|
+
if (typeof compile !== "function") return null;
|
|
67
|
+
const nowUtc = new Date().toISOString();
|
|
68
|
+
return compile.call(ctx.handle, scopeId, nowUtc);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|