@volter/editor-core 0.5.57
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/BUNDLED_NOTICES +1527 -0
- package/LICENSE +686 -0
- package/LICENSE-APACHE +202 -0
- package/NOTICE +18 -0
- package/README.md +24 -0
- package/dist/build/chunk-JS4D4XZ3.js +175 -0
- package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
- package/dist/build/vite-plugin-product-contributions.js +100 -0
- package/dist/build/vite-plugin-product-contributions.js.map +7 -0
- package/dist/build/vite-plugin-shared-react.js +25 -0
- package/dist/build/vite-plugin-shared-react.js.map +7 -0
- package/dist/build/vite-plugin-shared-three.js +86 -0
- package/dist/build/vite-plugin-shared-three.js.map +7 -0
- package/dist/server/chunk-KMJC7AGO.js +98 -0
- package/dist/server/chunk-KMJC7AGO.js.map +7 -0
- package/dist/server/chunk-PSILUMGS.js +46 -0
- package/dist/server/chunk-PSILUMGS.js.map +7 -0
- package/dist/server/frame-proxy.js +255 -0
- package/dist/server/frame-proxy.js.map +7 -0
- package/dist/server/open-browser.js +53 -0
- package/dist/server/open-browser.js.map +7 -0
- package/dist/server/process-shutdown.js +127 -0
- package/dist/server/process-shutdown.js.map +7 -0
- package/dist/server/session-registry.js +141 -0
- package/dist/server/session-registry.js.map +7 -0
- package/dist/server/spawn-opener.js +11 -0
- package/dist/server/spawn-opener.js.map +7 -0
- package/dist/server/worktree-identity.js +11 -0
- package/dist/server/worktree-identity.js.map +7 -0
- package/dist-server/packaged.mjs +88816 -0
- package/package.json +118 -0
- package/scripts/build-plugins.mjs +14 -0
- package/scripts/build-server.mjs +26 -0
- package/scripts/build-session.mjs +9 -0
- package/server/account-credentials.ts +60 -0
- package/server/account-service.ts +1434 -0
- package/server/adapter-region-includes.ts +47 -0
- package/server/asset-catalog-v2.ts +175 -0
- package/server/asset-history-snapshots.ts +196 -0
- package/server/asset-ledger-store.ts +195 -0
- package/server/asset-library-routes.ts +1595 -0
- package/server/blender-wasm-artifact.ts +430 -0
- package/server/boot-timings.ts +106 -0
- package/server/canonical-path.ts +50 -0
- package/server/checkout-workspace-preflight.ts +349 -0
- package/server/cloud-asset-catalog.ts +158 -0
- package/server/coding-inference-launch.ts +237 -0
- package/server/collaboration-account-client.ts +202 -0
- package/server/collaboration-attribution.ts +122 -0
- package/server/collaboration-session.ts +934 -0
- package/server/comfyui-bridge.ts +248 -0
- package/server/console-ledger.ts +499 -0
- package/server/creation-site-transform.ts +304 -0
- package/server/creation-site-write.ts +648 -0
- package/server/data-file-serialize.ts +158 -0
- package/server/editor-brand-html.ts +72 -0
- package/server/editor-control-socket.ts +366 -0
- package/server/editor-server-options.ts +190 -0
- package/server/editor-server.ts +1483 -0
- package/server/editor-sse.ts +352 -0
- package/server/engine-provenance.ts +169 -0
- package/server/engine-source-restart.ts +117 -0
- package/server/frame-bridge.ts +115 -0
- package/server/frame-proxy.ts +490 -0
- package/server/frame-workbench.ts +259 -0
- package/server/frontend-handoff.ts +229 -0
- package/server/game-globals-shadow.ts +113 -0
- package/server/gameplay-session-retention.ts +149 -0
- package/server/gameplay-sessions.ts +123 -0
- package/server/generation-jobs.ts +201 -0
- package/server/generation-reconciler.ts +137 -0
- package/server/generative-execution-context.ts +59 -0
- package/server/git-workflow.ts +612 -0
- package/server/harness-chat-caller.ts +36 -0
- package/server/harness-chat-service.ts +1681 -0
- package/server/idle-shutdown.ts +170 -0
- package/server/js-profiling-policy.ts +52 -0
- package/server/launcher-settings.ts +84 -0
- package/server/local-asset-catalog.ts +410 -0
- package/server/managed-account-defaults.ts +22 -0
- package/server/mock-control-plane.ts +540 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/native-credential-store.ts +162 -0
- package/server/open-browser.ts +118 -0
- package/server/packaged.ts +1192 -0
- package/server/play-stall.ts +126 -0
- package/server/process-shutdown.ts +218 -0
- package/server/product-presets.ts +55 -0
- package/server/project-build-artifact.ts +95 -0
- package/server/project-components.ts +100 -0
- package/server/project-dependency-invalidation.ts +114 -0
- package/server/project-file-scan.ts +235 -0
- package/server/project-hmr-files.ts +215 -0
- package/server/project-install-roots.ts +210 -0
- package/server/project-kinds.ts +71 -0
- package/server/project-mcp-servers.ts +82 -0
- package/server/project-module-freshness.ts +109 -0
- package/server/project-module-instance.ts +375 -0
- package/server/project-optimize-deps-entries.ts +827 -0
- package/server/project-output-writer.ts +679 -0
- package/server/project-package-origin.ts +63 -0
- package/server/project-root-surface.ts +831 -0
- package/server/project-scratch-path.ts +47 -0
- package/server/project-script-hmr.ts +619 -0
- package/server/project-serving-plugins.ts +236 -0
- package/server/project-tools.ts +653 -0
- package/server/project-validation.ts +250 -0
- package/server/project-verbs.ts +127 -0
- package/server/project-view.ts +52 -0
- package/server/project-watch.ts +1134 -0
- package/server/project-work-coordinator.ts +288 -0
- package/server/provider-credentials.ts +331 -0
- package/server/recent-projects-store.ts +98 -0
- package/server/redact-secrets.ts +71 -0
- package/server/repository-presence.ts +267 -0
- package/server/routes/account.ts +357 -0
- package/server/routes/agents.ts +60 -0
- package/server/routes/assets.ts +154 -0
- package/server/routes/build.ts +13 -0
- package/server/routes/collaboration.ts +595 -0
- package/server/routes/configurations.ts +419 -0
- package/server/routes/context.ts +264 -0
- package/server/routes/control-plane.ts +1811 -0
- package/server/routes/logs.ts +272 -0
- package/server/routes/project-identity.ts +302 -0
- package/server/routes/project-open.ts +429 -0
- package/server/routes/project-source.ts +686 -0
- package/server/routes/project-state.ts +383 -0
- package/server/routes/relay.ts +1029 -0
- package/server/routes/route-helpers.ts +29 -0
- package/server/routes/served-modules.ts +138 -0
- package/server/routes/session-tabs.ts +196 -0
- package/server/routes/settings.ts +121 -0
- package/server/routes/share-control.ts +486 -0
- package/server/routes/themes.ts +116 -0
- package/server/routes/tools.ts +230 -0
- package/server/routes/worktrees.ts +595 -0
- package/server/scoped-game-css.ts +461 -0
- package/server/server-utils.ts +1194 -0
- package/server/session-product.ts +112 -0
- package/server/session-registry.ts +230 -0
- package/server/share-claims.ts +138 -0
- package/server/share-host.ts +526 -0
- package/server/share-session-gateway.ts +1362 -0
- package/server/share-tunnel.ts +123 -0
- package/server/spawn-opener.ts +74 -0
- package/server/support/play/log-format.ts +76 -0
- package/server/support/play/session-record.ts +199 -0
- package/server/support/project/build-discipline.ts +718 -0
- package/server/support/project/inspection-node.ts +119 -0
- package/server/support/project/log-naming.ts +54 -0
- package/server/support/project/provenance.ts +290 -0
- package/server/support/project/run-name.ts +18 -0
- package/server/support/project/session-journal.ts +830 -0
- package/server/support/project/shared.ts +327 -0
- package/server/tab-bootstrap.ts +62 -0
- package/server/tab-heartbeat.ts +477 -0
- package/server/tab-lifecycle.ts +550 -0
- package/server/tab-presence.ts +1508 -0
- package/server/team-agent-mirror.ts +95 -0
- package/server/twin-auth.ts +138 -0
- package/server/vendored-lock-recorder.ts +968 -0
- package/server/worktree-identity.ts +121 -0
- package/server/worktree-management.ts +398 -0
- package/server/worktree-retention.d.mts +13 -0
- package/server/worktree-retention.mjs +205 -0
- package/src/EditorContext.tsx +169 -0
- package/src/account.ts +333 -0
- package/src/action-registry.ts +532 -0
- package/src/active-product.ts +74 -0
- package/src/active-project.ts +155 -0
- package/src/adapter-editor-config.ts +25 -0
- package/src/adapter-observation.ts +49 -0
- package/src/animation/stage-transport.ts +379 -0
- package/src/animation/three-clips-subject.ts +172 -0
- package/src/api/asset-library-wire.ts +45 -0
- package/src/api/assets.ts +365 -0
- package/src/api/base.ts +10 -0
- package/src/api/build.ts +99 -0
- package/src/api/git-wire.ts +56 -0
- package/src/api/logs.ts +92 -0
- package/src/api/project-identity.ts +74 -0
- package/src/api/project-open.ts +355 -0
- package/src/api/project-source.ts +162 -0
- package/src/api/project-state.ts +107 -0
- package/src/api/relay.ts +268 -0
- package/src/api/settings.ts +36 -0
- package/src/api/themes.ts +45 -0
- package/src/api/worktrees.ts +257 -0
- package/src/asset-compare-core.ts +171 -0
- package/src/asset-compare.ts +294 -0
- package/src/asset-editor-context.tsx +100 -0
- package/src/asset-events.ts +97 -0
- package/src/asset-inspector-actions.ts +87 -0
- package/src/asset-preview-framing.ts +357 -0
- package/src/asset-preview.ts +2802 -0
- package/src/asset-selection-viewer-registry.ts +113 -0
- package/src/asset-selection.ts +133 -0
- package/src/asset-workflow/asset-capabilities.ts +344 -0
- package/src/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/asset-workflow/asset-ledger.ts +156 -0
- package/src/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/asset-workflow/asset-types.ts +142 -0
- package/src/asset-workflow/asset-workflow-quality.ts +61 -0
- package/src/asset-workflow/audio-preview-player.ts +193 -0
- package/src/asset-workflow/audio-waveform.ts +22 -0
- package/src/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/asset-workflow/folder-preview.ts +345 -0
- package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/asset-workflow/image-view-scale.ts +32 -0
- package/src/asset-workflow/import-contract.ts +124 -0
- package/src/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/asset-workflow/model-inspection.ts +818 -0
- package/src/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +139 -0
- package/src/asset-workflow/project-asset-operations.ts +154 -0
- package/src/asset-workflow/project-asset-roots.ts +68 -0
- package/src/asset-workflow/project-content.ts +288 -0
- package/src/asset-workflow/project-source-index.ts +550 -0
- package/src/asset-workflow/thumbnail-system.ts +256 -0
- package/src/authoring/active-adapter.ts +199 -0
- package/src/authoring/active-systems.ts +422 -0
- package/src/authoring/adapter-key.ts +18 -0
- package/src/authoring/authoring-asset-url.ts +27 -0
- package/src/authoring/bootstrap-state.ts +49 -0
- package/src/authoring/boundary-authoring-adapter.ts +184 -0
- package/src/authoring/breakpoint-state.ts +43 -0
- package/src/authoring/canvas-scene-guides.ts +76 -0
- package/src/authoring/component-instance-root.ts +171 -0
- package/src/authoring/composite-authoring-adapter.ts +2110 -0
- package/src/authoring/consumer-actions.ts +520 -0
- package/src/authoring/css-numeric-style.ts +97 -0
- package/src/authoring/design-time-layers.ts +849 -0
- package/src/authoring/design-time-mount-registry.ts +235 -0
- package/src/authoring/design-time-settle.ts +343 -0
- package/src/authoring/edit-mode-authoring.ts +619 -0
- package/src/authoring/eyedropper-session.ts +60 -0
- package/src/authoring/instance-source-menu-register.ts +13 -0
- package/src/authoring/instance-source-menu.ts +135 -0
- package/src/authoring/layered-pick.ts +183 -0
- package/src/authoring/live-gesture-lock.ts +50 -0
- package/src/authoring/live-object-transform.ts +62 -0
- package/src/authoring/mount-failure-report.ts +154 -0
- package/src/authoring/mounted-root-subjects.ts +144 -0
- package/src/authoring/no-authoring-adapter.ts +55 -0
- package/src/authoring/null-inspection-subjects.tsx +75 -0
- package/src/authoring/object3d-document-persistence.ts +106 -0
- package/src/authoring/object3d-document-session-registry.ts +120 -0
- package/src/authoring/object3d-document-session.ts +1326 -0
- package/src/authoring/object3d-gesture-controller.ts +113 -0
- package/src/authoring/panel-authoring.ts +121 -0
- package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
- package/src/authoring/prefab-instance-section-model.ts +55 -0
- package/src/authoring/project-authoring-session.ts +105 -0
- package/src/authoring/provenance.ts +99 -0
- package/src/authoring/quarks-particle-systems.ts +19 -0
- package/src/authoring/react-canvas-navigation.ts +255 -0
- package/src/authoring/react-design-canvas-style.ts +20 -0
- package/src/authoring/react-story-board.ts +937 -0
- package/src/authoring/selection-scope.ts +195 -0
- package/src/authoring/shell-document-ops.ts +169 -0
- package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
- package/src/authoring/shell-viewport-policy.ts +48 -0
- package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/authoring/stories-scope.ts +35 -0
- package/src/authoring/story-board-chrome-fit.ts +107 -0
- package/src/authoring/story-board-presentation.ts +111 -0
- package/src/authoring/three-projection-core.ts +226 -0
- package/src/authoring/viewport-pick-context.ts +40 -0
- package/src/authoring/viewport-raycast.ts +240 -0
- package/src/authoring/viewport-tool-context.ts +73 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +152 -0
- package/src/authoring/world-pan-state.ts +198 -0
- package/src/authoring/world-session-state.ts +101 -0
- package/src/authoring/write-pipe.ts +173 -0
- package/src/availability-tick.ts +66 -0
- package/src/bitmap-label.ts +120 -0
- package/src/blender-tab-metrics.ts +161 -0
- package/src/board-open-actions.ts +20 -0
- package/src/boot-routing.ts +391 -0
- package/src/build-identity.ts +15 -0
- package/src/bytes-codec.ts +62 -0
- package/src/camera-authoring.ts +175 -0
- package/src/cancellation-reason.ts +58 -0
- package/src/canvas-preview-frames.ts +482 -0
- package/src/capture-camera-pose.ts +77 -0
- package/src/chrome-registry.ts +159 -0
- package/src/chrome-slot-registry.ts +91 -0
- package/src/collaboration-client.ts +264 -0
- package/src/collaboration-presence.ts +41 -0
- package/src/command-dispatch.ts +19 -0
- package/src/command-listener.ts +2743 -0
- package/src/command-registry.ts +70 -0
- package/src/component-board-registry.ts +205 -0
- package/src/component-states-registry.ts +200 -0
- package/src/components/AgentPresentationNotice.tsx +75 -0
- package/src/components/AlignToolbar.tsx +204 -0
- package/src/components/AppRoot.tsx +394 -0
- package/src/components/ApplicationMenus.tsx +415 -0
- package/src/components/AssetBrowser.tsx +2843 -0
- package/src/components/AssetEditorShell.tsx +216 -0
- package/src/components/AssetInspectorToolSection.tsx +124 -0
- package/src/components/BoardRulers.tsx +354 -0
- package/src/components/CameraInfo.tsx +81 -0
- package/src/components/CanvasSceneViewport.tsx +556 -0
- package/src/components/CapabilityCoverageSection.tsx +185 -0
- package/src/components/CenterDocuments.tsx +22 -0
- package/src/components/ChromeSlot.tsx +20 -0
- package/src/components/CodeView.tsx +470 -0
- package/src/components/CollaborationHeaderControl.css +74 -0
- package/src/components/CompactInspectorCard.tsx +190 -0
- package/src/components/CompactInspectorShell.tsx +39 -0
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +270 -0
- package/src/components/DefaultEditorLayout.tsx +106 -0
- package/src/components/DocumentHeaderStrip.tsx +96 -0
- package/src/components/DocumentShelfRail.tsx +45 -0
- package/src/components/DocumentThumbnail.tsx +345 -0
- package/src/components/EditorLeaseGuard.tsx +372 -0
- package/src/components/ErrorBanner.tsx +123 -0
- package/src/components/FolderPreviewTile.tsx +159 -0
- package/src/components/GameHierarchy.tsx +3842 -0
- package/src/components/GameplaySessionTimeline.tsx +295 -0
- package/src/components/InspectionProjection.tsx +968 -0
- package/src/components/Inspector.tsx +233 -0
- package/src/components/InspectorCanvasPreview.tsx +35 -0
- package/src/components/InspectorFieldsSection.tsx +290 -0
- package/src/components/InspectorObjectPreview.tsx +57 -0
- package/src/components/InspectorStoriesSection.tsx +92 -0
- package/src/components/InspectorToolSection.tsx +96 -0
- package/src/components/InspectorTransformSection.tsx +245 -0
- package/src/components/LightExplorerPanel.tsx +433 -0
- package/src/components/MediaProperties.tsx +145 -0
- package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
- package/src/components/Object3DDocumentToolbar.css +100 -0
- package/src/components/Object3DDocumentToolbar.tsx +454 -0
- package/src/components/Object3DDocumentViewport.tsx +58 -0
- package/src/components/ProjectHeader.css +617 -0
- package/src/components/ProjectHeader.tsx +328 -0
- package/src/components/ProjectLayout.tsx +249 -0
- package/src/components/ReactCanvasControls.tsx +284 -0
- package/src/components/RootSelectionOverlay.tsx +3162 -0
- package/src/components/RootTextEditor.tsx +79 -0
- package/src/components/SaveStatus.tsx +70 -0
- package/src/components/StageHost.tsx +1954 -0
- package/src/components/StageOverlays.tsx +21 -0
- package/src/components/StartupErrorScreen.tsx +76 -0
- package/src/components/StartupLoadingScreen.tsx +65 -0
- package/src/components/StatsOverlay.tsx +78 -0
- package/src/components/SurfaceStateOverlay.tsx +24 -0
- package/src/components/ToolContributionSurfaces.tsx +65 -0
- package/src/components/ToolHost.tsx +370 -0
- package/src/components/ToolObject3DPreview.tsx +39 -0
- package/src/components/Toolbar.tsx +595 -0
- package/src/components/TransientHint.tsx +44 -0
- package/src/components/TransportStrip.tsx +174 -0
- package/src/components/VersionControlSection.tsx +470 -0
- package/src/components/VgaiLogo.css +83 -0
- package/src/components/VgaiLogo.tsx +35 -0
- package/src/components/ViewportControlsHint.tsx +60 -0
- package/src/components/ViewportFurniture.tsx +401 -0
- package/src/components/ViewportOverlay.tsx +145 -0
- package/src/components/ViewportOverlaysMenu.tsx +153 -0
- package/src/components/ViewportShadingMenu.tsx +300 -0
- package/src/components/ViewportViewMenu.tsx +101 -0
- package/src/components/WorkspaceDocumentSurface.tsx +204 -0
- package/src/components/WorkspaceUtilitySurface.tsx +22 -0
- package/src/components/WorktreeSwitcher.css +239 -0
- package/src/components/WorktreeSwitcher.tsx +890 -0
- package/src/components/account-documents.tsx +1162 -0
- package/src/components/asset-documents.tsx +812 -0
- package/src/components/asset-editor-persistence.ts +216 -0
- package/src/components/asset-selection-section.tsx +535 -0
- package/src/components/asset-thumbnails.tsx +312 -0
- package/src/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
- package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
- package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
- package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
- package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
- package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
- package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
- package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/components/asset-viewers/shader-source.ts +144 -0
- package/src/components/asset-workflow.css +731 -0
- package/src/components/board-guides.ts +150 -0
- package/src/components/compact-inspector.css +539 -0
- package/src/components/core-utilities.tsx +90 -0
- package/src/components/editor-notifications.css +40 -0
- package/src/components/engine-workspace.css +274 -0
- package/src/components/inspector-preview-section.tsx +238 -0
- package/src/components/inspector-property-grouping.ts +64 -0
- package/src/components/inspector-revert-label.ts +20 -0
- package/src/components/inspector-selection.ts +42 -0
- package/src/components/inspector-stories-gating.ts +171 -0
- package/src/components/inspector-transform-subject.ts +11 -0
- package/src/components/inspector-transform.ts +75 -0
- package/src/components/kind-documents.tsx +500 -0
- package/src/components/palette-action-publisher.tsx +140 -0
- package/src/components/primitives/DraftColorInput.tsx +74 -0
- package/src/components/product-shell.css +1829 -0
- package/src/components/project-tool-documents.tsx +410 -0
- package/src/components/scene-documents.tsx +223 -0
- package/src/components/stage-keyboard.tsx +37 -0
- package/src/components/stage-overlay-set.tsx +107 -0
- package/src/components/stage-presence-markers.ts +478 -0
- package/src/components/standard-viewport-dressing.ts +417 -0
- package/src/components/status-contributions.tsx +403 -0
- package/src/components/tool-documents.tsx +309 -0
- package/src/components/tool-schema-form.tsx +262 -0
- package/src/components/use-after-paint.ts +41 -0
- package/src/components/use-project-image-assets.ts +86 -0
- package/src/components/viewport-header-controls.css +90 -0
- package/src/components/viewport-surface-status.tsx +55 -0
- package/src/components/workspace-history.ts +32 -0
- package/src/components/workspace-static-panel-registry.tsx +152 -0
- package/src/components/workspace-surfaces.css +621 -0
- package/src/components/world-documents.tsx +586 -0
- package/src/components/world-overlay-gestures.ts +1694 -0
- package/src/composite-screenshot.ts +1641 -0
- package/src/console-sync.ts +131 -0
- package/src/constraint-helper.ts +338 -0
- package/src/content-entry-source-registry.ts +184 -0
- package/src/coverage/authoring-seam-evidence.ts +300 -0
- package/src/coverage/canvas-reveal.ts +192 -0
- package/src/coverage/design-time-surfaces.ts +101 -0
- package/src/coverage/live-seam-evidence.ts +11 -0
- package/src/coverage/ontology-invariants.ts +466 -0
- package/src/coverage/session-vitals.ts +501 -0
- package/src/coverage/system-seam-evidence.ts +72 -0
- package/src/crash-null-boundary.ts +36 -0
- package/src/creation-site-edit.ts +1479 -0
- package/src/creation-site-registry.ts +160 -0
- package/src/delegate-harness-registry.ts +143 -0
- package/src/document-context-registry.ts +177 -0
- package/src/document-open-registry.ts +200 -0
- package/src/document-preview-source.ts +20 -0
- package/src/document-renderer-session.ts +138 -0
- package/src/editor-api.ts +46 -0
- package/src/editor-chrome-capture.ts +139 -0
- package/src/editor-commands.ts +164 -0
- package/src/editor-console.ts +563 -0
- package/src/editor-current-view.ts +82 -0
- package/src/editor-document-probe.ts +881 -0
- package/src/editor-git-client.ts +115 -0
- package/src/editor-host-door.ts +462 -0
- package/src/editor-hotkeys.ts +842 -0
- package/src/editor-lease-view.ts +39 -0
- package/src/editor-lease.ts +415 -0
- package/src/editor-mode.ts +19 -0
- package/src/editor-notifications.ts +140 -0
- package/src/editor-presence.ts +563 -0
- package/src/editor-presentation-activity.ts +50 -0
- package/src/editor-presentation-notice.ts +42 -0
- package/src/editor-runtime.tsx +145 -0
- package/src/editor-server-response.ts +86 -0
- package/src/editor-session-attribution.ts +75 -0
- package/src/editor-session-mode.ts +54 -0
- package/src/editor-shell-store.ts +1304 -0
- package/src/editor-state-facets.ts +74 -0
- package/src/editor-styles.css +33 -0
- package/src/editor-view-presentation.ts +810 -0
- package/src/editor-viewport.ts +5302 -0
- package/src/entity-lod.ts +31 -0
- package/src/entity-object.ts +91 -0
- package/src/files/file-provider.ts +62 -0
- package/src/files/project-files.ts +264 -0
- package/src/finders/index.ts +136 -0
- package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/frame/bridge.tsx +1408 -0
- package/src/frame/product.ts +77 -0
- package/src/gameplay-dom-recording.ts +319 -0
- package/src/gameplay-export-state.ts +14 -0
- package/src/gameplay-replay.ts +417 -0
- package/src/gameplay-session-time.ts +9 -0
- package/src/gameplay-sessions.ts +204 -0
- package/src/harness-chat-types.ts +253 -0
- package/src/hierarchy-component-marks.ts +298 -0
- package/src/hierarchy-drop.ts +91 -0
- package/src/hierarchy-expansion-state.ts +80 -0
- package/src/hierarchy-header-slot.ts +52 -0
- package/src/hierarchy-internals.ts +197 -0
- package/src/hierarchy-kind-icon.ts +217 -0
- package/src/hierarchy-mark-reader.ts +73 -0
- package/src/hierarchy-menu-registry.ts +67 -0
- package/src/hierarchy-node-rows.ts +307 -0
- package/src/hierarchy-panel-view.ts +280 -0
- package/src/hierarchy-projection.ts +76 -0
- package/src/hierarchy-row-cache.ts +243 -0
- package/src/hierarchy-row-model.ts +308 -0
- package/src/hierarchy-rows.ts +11 -0
- package/src/hierarchy-walk.ts +86 -0
- package/src/history/editor-session.ts +25 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-delegate.ts +163 -0
- package/src/history/history-limit-notices.ts +43 -0
- package/src/history/history-service.ts +1173 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +386 -0
- package/src/history/project-root-history-backends.ts +139 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +103 -0
- package/src/history/source-history-backend.ts +546 -0
- package/src/history/types.ts +124 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hotkeys.ts +188 -0
- package/src/inference-diagnostics.ts +69 -0
- package/src/initial-project.ts +80 -0
- package/src/inspection/active-subject.ts +578 -0
- package/src/inspection/active-surface.ts +142 -0
- package/src/inspection/compose.ts +1064 -0
- package/src/inspection/display.ts +170 -0
- package/src/inspection/document-subject.ts +109 -0
- package/src/inspection/game-subject.ts +85 -0
- package/src/inspection/model.ts +542 -0
- package/src/inspection/null-subject.ts +115 -0
- package/src/inspection/serialize.ts +357 -0
- package/src/inspection/use-active-inspection.ts +180 -0
- package/src/inspector-presentation.ts +201 -0
- package/src/inspector-section-registry.ts +221 -0
- package/src/instance-source-actions.ts +163 -0
- package/src/instanced-presentation.ts +164 -0
- package/src/js-heap.ts +71 -0
- package/src/key-actions.ts +91 -0
- package/src/keymap-presets.ts +401 -0
- package/src/layout-policy.ts +31 -0
- package/src/learn-links.ts +73 -0
- package/src/light-explorer-model.ts +134 -0
- package/src/live-canvas-frame.ts +55 -0
- package/src/live-document.ts +279 -0
- package/src/live-module-source.ts +230 -0
- package/src/live-session-registry.ts +220 -0
- package/src/live-transition.ts +633 -0
- package/src/manifest-project.ts +107 -0
- package/src/model-thumbnail.ts +523 -0
- package/src/native-selection-style.ts +202 -0
- package/src/object3d-document-write-policy.ts +137 -0
- package/src/packaged-runtime.ts +108 -0
- package/src/palettes/maya.palette.json +57 -0
- package/src/palettes/substance.palette.json +57 -0
- package/src/pasteboard-module.ts +81 -0
- package/src/performance-profiler.ts +367 -0
- package/src/performance-sources.ts +69 -0
- package/src/play-boot-phase.ts +145 -0
- package/src/presentation-surface.ts +248 -0
- package/src/project-adapter.ts +1143 -0
- package/src/project-asset-refresh.ts +26 -0
- package/src/project-declaration-refresh.ts +31 -0
- package/src/project-local-state.ts +118 -0
- package/src/project-manager.ts +243 -0
- package/src/project-module-changes.ts +163 -0
- package/src/project-module-split.ts +266 -0
- package/src/project-provenance.ts +115 -0
- package/src/project-ready.ts +42 -0
- package/src/project-session-reset.ts +67 -0
- package/src/project-shape.ts +68 -0
- package/src/project-tool-discovery.ts +66 -0
- package/src/project-tools.ts +107 -0
- package/src/project-work-types.ts +149 -0
- package/src/projection/three.ts +898 -0
- package/src/projection/types.ts +44 -0
- package/src/readiness.ts +113 -0
- package/src/reflection-probe-helper.ts +142 -0
- package/src/reported-play-state.ts +90 -0
- package/src/resolve-relative-specifier.ts +33 -0
- package/src/scene-document-plan.ts +315 -0
- package/src/scene-framing.ts +315 -0
- package/src/scene-live-open.ts +210 -0
- package/src/scene-view-fog.ts +89 -0
- package/src/scoped-game-css.ts +152 -0
- package/src/session-orphan-record.ts +193 -0
- package/src/session-tombstone.ts +126 -0
- package/src/settings/settings-provider.ts +82 -0
- package/src/settings-store.ts +345 -0
- package/src/shared-view-restore.ts +42 -0
- package/src/shell-store-door.ts +45 -0
- package/src/source-conflict.ts +122 -0
- package/src/spatial-handle-visuals.ts +332 -0
- package/src/stage-context.ts +341 -0
- package/src/stage-store-registry.ts +63 -0
- package/src/stale-chunk-recovery.ts +34 -0
- package/src/state-report-deferral.ts +73 -0
- package/src/storage/host-files-storage.ts +97 -0
- package/src/storage/http-storage.ts +174 -0
- package/src/storage/index.ts +75 -0
- package/src/storage/mem-storage.ts +149 -0
- package/src/storage/path-lock.ts +44 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/types.ts +116 -0
- package/src/stories/StoryComponentThumbnail.tsx +184 -0
- package/src/stories/StoryPreviewMount.tsx +306 -0
- package/src/stories/component-content-source.tsx +106 -0
- package/src/stories/component-states-source.ts +78 -0
- package/src/stories/compose-project-stories.ts +255 -0
- package/src/stories/pixi-story-model.ts +30 -0
- package/src/stories/prefabs-finder.ts +54 -0
- package/src/stories/prefabs-from-stories.ts +182 -0
- package/src/stories/project-story-discovery.ts +78 -0
- package/src/stories/project-story-regions.ts +24 -0
- package/src/stories/story-actions.ts +54 -0
- package/src/stories/story-capture-command.ts +124 -0
- package/src/stories/story-capture.ts +584 -0
- package/src/stories/story-declared-medium.ts +126 -0
- package/src/stories/story-discovery.ts +176 -0
- package/src/stories/story-document-openers.ts +36 -0
- package/src/stories/story-dom-runtime.ts +78 -0
- package/src/stories/story-grouping.ts +111 -0
- package/src/stories/story-lane.ts +88 -0
- package/src/stories/story-mount-turn.ts +27 -0
- package/src/stories/story-opener.ts +260 -0
- package/src/stories/story-pixi-preview.ts +408 -0
- package/src/stories/story-presentation.ts +215 -0
- package/src/stories/story-registry.ts +517 -0
- package/src/stories/story-three-preview.ts +807 -0
- package/src/stories/three-story-model.ts +96 -0
- package/src/story-three-preview-runtime.ts +56 -0
- package/src/surface-keyboard.ts +112 -0
- package/src/surface-state.ts +124 -0
- package/src/tab-bootstrap.js +628 -0
- package/src/tab-census.ts +198 -0
- package/src/tab-lifecycle-client.ts +196 -0
- package/src/theme-library.ts +844 -0
- package/src/theme-preference.ts +378 -0
- package/src/theme.css +3437 -0
- package/src/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/three-viewport/camera-fit.ts +41 -0
- package/src/three-viewport/interactive-renderer.ts +132 -0
- package/src/three-viewport/selection-brackets.ts +310 -0
- package/src/three-viewport/selection-outline.ts +183 -0
- package/src/three-viewport/skeleton-helper.ts +61 -0
- package/src/three-viewport/source-color.ts +167 -0
- package/src/three-viewport/studio-environment.ts +96 -0
- package/src/three-viewport-presentation.ts +23 -0
- package/src/tool-contribution-play.ts +74 -0
- package/src/tool-loader.ts +1798 -0
- package/src/transform-mode-request.ts +57 -0
- package/src/transient-hint.ts +78 -0
- package/src/trigger-volume-helper.ts +116 -0
- package/src/ui-source/adapter-region-includes.ts +241 -0
- package/src/ui-source/ensure-import.ts +132 -0
- package/src/ui-source/file-region-resolver.ts +302 -0
- package/src/ui-source/inspect.ts +775 -0
- package/src/ui-source/oid-transform.ts +1845 -0
- package/src/ui-source/plan-csf-story.ts +196 -0
- package/src/ui-source/plan-extract-component.ts +421 -0
- package/src/ui-source/plan-fork-component.ts +621 -0
- package/src/ui-source/plan-named-style.ts +95 -0
- package/src/ui-source/plan-source-edit.ts +353 -0
- package/src/ui-source/r3f-contract-resolver.ts +327 -0
- package/src/ui-source/r3f-diagnostic-index.ts +165 -0
- package/src/ui-source/r3f-environment-binding.ts +133 -0
- package/src/ui-source/r3f-joint-binding.ts +251 -0
- package/src/ui-source/r3f-lod-binding.ts +91 -0
- package/src/ui-source/r3f-particle-binding.ts +235 -0
- package/src/ui-source/r3f-physics-binding.ts +483 -0
- package/src/ui-source/r3f-project-contracts.ts +444 -0
- package/src/ui-source/relative-import-specifier.ts +34 -0
- package/src/ui-source/reparent-guard.ts +544 -0
- package/src/ui-source/source-edit-request.ts +97 -0
- package/src/ui-source/source-write-backend.ts +618 -0
- package/src/ui-source/syntactic-prop-specs.ts +296 -0
- package/src/ui-source/tier-source-write-backend.ts +279 -0
- package/src/ui-source/ts-ast.ts +171 -0
- package/src/ui-source/utility-class-support.ts +169 -0
- package/src/ui-source/write-component-default.ts +133 -0
- package/src/ui-source/writer.ts +1989 -0
- package/src/viewport-activation-timings.ts +840 -0
- package/src/viewport-authoring-policy.ts +186 -0
- package/src/viewport-controls-hint.ts +57 -0
- package/src/viewport-door.ts +237 -0
- package/src/viewport-shading-boundary.ts +12 -0
- package/src/vite-error-surface.ts +141 -0
- package/src/wait-until.ts +37 -0
- package/src/workspace-areas.ts +156 -0
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-available-documents.ts +127 -0
- package/src/workspace-core-utilities.ts +31 -0
- package/src/workspace-document-ids.ts +59 -0
- package/src/workspace-document-registry.ts +612 -0
- package/src/workspace-document-restore.ts +145 -0
- package/src/workspace-host-commands.ts +141 -0
- package/src/workspace-persistence-gate.ts +40 -0
- package/src/workspace-play-utilities.ts +44 -0
- package/src/workspace-presets.ts +425 -0
- package/src/workspace-regions.ts +294 -0
- package/src/workspace-state-persistence.ts +536 -0
- package/src/workspace-static-panels.ts +73 -0
- package/src/workspace-status-registry.ts +121 -0
- package/src/workspace-style.ts +239 -0
- package/src/workspace-utility-commands.ts +74 -0
- package/src/workspace-utility-registry.ts +263 -0
- package/src/workspace-viewport-rect.ts +97 -0
- package/src/world-adoption.ts +115 -0
- package/src/world-document-routing.ts +104 -0
- package/vite-plugin-creation-site-write.ts +184 -0
- package/vite-plugin-creation-site.ts +80 -0
- package/vite-plugin-game-static.ts +303 -0
- package/vite-plugin-module-doorways.ts +336 -0
- package/vite-plugin-product-contributions.ts +197 -0
- package/vite-plugin-project-game-static.ts +125 -0
- package/vite-plugin-project-jsx-js.ts +65 -0
- package/vite-plugin-project-root-absolute-assets.ts +359 -0
- package/vite-plugin-shared-react.ts +420 -0
- package/vite-plugin-shared-three.ts +203 -0
- package/vite-plugin-ui-oid.ts +2106 -0
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product door onto EDITOR CHROME — read and drive what a person sees in
|
|
3
|
+
* the editor's named surfaces, through the session, without play mode.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists (WO: "No product door drives or reads EDITOR CHROME")
|
|
6
|
+
*
|
|
7
|
+
* `vgai eval`'s `page()` step is play-mode-gated and rooted at the GAME
|
|
8
|
+
* container, so an editor surface that is not a running game — a capability's
|
|
9
|
+
* workspace document, the Data sheet, the Project Tools catalog — could be
|
|
10
|
+
* neither driven nor read through the product at all. The Sheets build could
|
|
11
|
+
* not live-verify a TSV paste or a sheet-tab click: it had a screenshot
|
|
12
|
+
* (`editor.captureActiveDocument`) and nothing else. That gap is the defect the
|
|
13
|
+
* doctrine names — when a live surface can only be diagnosed out of band, the
|
|
14
|
+
* product's own doors have failed.
|
|
15
|
+
*
|
|
16
|
+
* ## The API decision, and why it is this small
|
|
17
|
+
*
|
|
18
|
+
* ONE wire command (`document-probe`) carrying a step union, exposed on the
|
|
19
|
+
* live side as `editor.document.{query,click,drag,key,type,paste,select}`.
|
|
20
|
+
* Each verb is a thing an agent cannot otherwise do and cannot fake:
|
|
21
|
+
*
|
|
22
|
+
* - `query` — READ. What is rendered, as data: tag, role, text, attributes,
|
|
23
|
+
* value/checked/disabled, rect. The one door that answers "what does the
|
|
24
|
+
* human see" for a surface that is not a canvas.
|
|
25
|
+
* - `click` — a REAL user gesture (pointerdown/mousedown/mouseup/click plus
|
|
26
|
+
* focus), not `element.click()`, because a component listening on
|
|
27
|
+
* `pointerdown` (react-data-grid's cell selection does) is invisible to the
|
|
28
|
+
* synthetic shortcut. `clicks: 2` is a real DOUBLE click (numbered
|
|
29
|
+
* `detail` plus the `dblclick` React's `onDoubleClick` listens for) — the
|
|
30
|
+
* Outliner's own rename gesture, and an option on the same verb rather
|
|
31
|
+
* than a second one because it is the same gesture on the same target.
|
|
32
|
+
* - `type` — text into a focused field, character by character, committed
|
|
33
|
+
* with Enter. `paste` cannot stand in: an untrusted `ClipboardEvent`
|
|
34
|
+
* performs no default action, so a plain `<input>` keeps its old value.
|
|
35
|
+
* - `key` — keydown/keyup on the explicit target, else on whatever inside the
|
|
36
|
+
* scope has focus. Arrow keys ARE the spreadsheet's cursor. (No `keypress`:
|
|
37
|
+
* it is deprecated and no shipped surface listens for it.)
|
|
38
|
+
* - `paste` — a real `ClipboardEvent` with a populated `DataTransfer`, which
|
|
39
|
+
* is the only way to exercise a paste handler; nothing else in the product
|
|
40
|
+
* can produce one. Verified live against the Data sheet's own `onPaste`,
|
|
41
|
+
* which read the TSV and wrote the file.
|
|
42
|
+
* - `select` — choose a value on a `<select>`. `click` genuinely cannot do
|
|
43
|
+
* this: the option list of a native dropdown is drawn by the OS, so there
|
|
44
|
+
* is nothing inside the document's container for a pointer gesture to
|
|
45
|
+
* resolve against. And a plain `element.value = x` is a no-op against
|
|
46
|
+
* React, which caches the last value it wrote on the node and puts it back
|
|
47
|
+
* on the next render. So this verb goes through the PROTOTYPE's own value
|
|
48
|
+
* setter (which bypasses that cache) and then dispatches `input`/`change` —
|
|
49
|
+
* the established spelling from this repo's own probes, and the one React's
|
|
50
|
+
* synthetic-event layer honours. Without it the animation transport's clip
|
|
51
|
+
* and speed — two `<select>`s — were undrivable through the product.
|
|
52
|
+
*
|
|
53
|
+
* There is deliberately NO `screenshot` verb: `editor.captureActiveDocument()`
|
|
54
|
+
* already captures the document's content (`editor-view-presentation.ts`'s
|
|
55
|
+
* `activeDocumentContent`); this door's scope is the document's whole box —
|
|
56
|
+
* that content plus its header strip and shelf rail — and a synonym would be
|
|
57
|
+
* a second name for one behavior.
|
|
58
|
+
*
|
|
59
|
+
* ## Scope is a VOCABULARY, and the contract is that it is closed
|
|
60
|
+
*
|
|
61
|
+
* This is NOT general page automation, and the refusal is how that stays true.
|
|
62
|
+
* Every step resolves against ONE NAMED SURFACE — `document` (the default),
|
|
63
|
+
* `header`, `shelf`, `rail`, `outliner`, `content` ({@link DocumentProbeScope}) — and a
|
|
64
|
+
* selector that matches nothing inside it, or a resolved element that is not a
|
|
65
|
+
* descendant of it, is REFUSED with a message naming the scope rather than
|
|
66
|
+
* silently reaching into the rest of the page.
|
|
67
|
+
*
|
|
68
|
+
* ## How a scope finds its element: the owner's own stamp, never a class guess
|
|
69
|
+
*
|
|
70
|
+
* The Properties rail and the Outliner are VS CODE VIEWS (`vgai.properties`,
|
|
71
|
+
* `vgai.outliner`) holding React portals of ours, so their roots are not
|
|
72
|
+
* anywhere near the document's box and cannot be reached by walking down from
|
|
73
|
+
* it. They are found by the id THE WORKBENCH REGISTERED: the contribution
|
|
74
|
+
* hands each view's body element over as a named part, and `offerVgaiPart`
|
|
75
|
+
* (`frame/bridge.tsx`) stamps it `data-vgai-part="<part>"` at the moment of
|
|
76
|
+
* the handover. A pane that is disposed takes its stamped element with it, so
|
|
77
|
+
* a closed view answers "not open" instead of matching a stale node. The same
|
|
78
|
+
* shape as `activeDocumentContainer`'s `data-workspace-document-id` and as the
|
|
79
|
+
* portal pair below: the surface publishes its own identity, and this module
|
|
80
|
+
* never guesses from a class name.
|
|
81
|
+
*
|
|
82
|
+
* `rail` and `outliner` are EDITOR CHROME rather than the active document, so
|
|
83
|
+
* (unlike `document`/`header`/`shelf`) they are reachable while the Game
|
|
84
|
+
* document is active and while no document is open at all. The `editor.
|
|
85
|
+
* hierarchy()` / `editor.inspect()` doors still report what those panels
|
|
86
|
+
* RESOLVED; this door reports what they DREW and drives their controls —
|
|
87
|
+
* WALK 5's beats 7 and 12 are the gap it closes (`editor.setField('name')`
|
|
88
|
+
* writes the name, but it is not the Outliner's rename, and nothing at all
|
|
89
|
+
* could click a Properties tab).
|
|
90
|
+
*
|
|
91
|
+
* Two deliberate consequences of the containment rule:
|
|
92
|
+
*
|
|
93
|
+
* - A PORTALED overlay THIS DOCUMENT OPENED is in scope; anyone else's is
|
|
94
|
+
* not. Amended 2026-09-21 (B6), and what changed is the DOM rather than the
|
|
95
|
+
* rule: this note used to say a popover portaled to the theme root recorded
|
|
96
|
+
* no ownership, so any heuristic wide enough to reach it would also reach
|
|
97
|
+
* the editor's chrome menus. `ThemeRootPortal` records it now — an anchor
|
|
98
|
+
* span where the portal was declared and a matching stamp on what landed —
|
|
99
|
+
* so {@link scopeRoots} walks that pair from inside the document's box and
|
|
100
|
+
* reaches exactly the overlays the document opened. A chrome menu's anchor
|
|
101
|
+
* is in the chrome, so it is still refused by the same containment check.
|
|
102
|
+
* Without this a document's own header MENU could be opened by its trigger
|
|
103
|
+
* and never chosen from, which is "a control is accepted through its own
|
|
104
|
+
* click" failing one step short (measured on the Model document's Add
|
|
105
|
+
* menu). Widening further — every `.vgai-menu` on the root — is still the
|
|
106
|
+
* "general automation framework" this module exists to not become.
|
|
107
|
+
* - The GAME document is refused outright by the three scopes that ARE the
|
|
108
|
+
* active document (see `resolveDocumentScope`): a game is driven through
|
|
109
|
+
* its own registered commands and the play-gated `page` door, never
|
|
110
|
+
* through synthetic DOM gestures. `rail` and `outliner` are the editor's
|
|
111
|
+
* panels around it and stay open.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
import type {
|
|
115
|
+
DocumentKeyStep,
|
|
116
|
+
DocumentProbeResult,
|
|
117
|
+
DocumentProbeScope,
|
|
118
|
+
DocumentProbeStep,
|
|
119
|
+
ProbedElement,
|
|
120
|
+
} from '@volter/editor-sdk/document-probe';
|
|
121
|
+
import { GAME_DOCUMENT_ID } from './workspace-document-ids';
|
|
122
|
+
import {
|
|
123
|
+
activeWorkspaceDocument,
|
|
124
|
+
activeWorkspaceDocumentId,
|
|
125
|
+
activeWorkspaceDocumentViewId,
|
|
126
|
+
} from './workspace-document-registry';
|
|
127
|
+
|
|
128
|
+
/** The active document's mounted content element — what the `document`,
|
|
129
|
+
* `header` and `shelf` scopes resolve from. Shared with
|
|
130
|
+
* `editor-view-presentation.ts`'s capture so a probe and a screenshot can
|
|
131
|
+
* never disagree about what "the active document" is. */
|
|
132
|
+
export function activeDocumentContainer(documentId: string): HTMLElement | null {
|
|
133
|
+
return (
|
|
134
|
+
Array.from(document.querySelectorAll<HTMLElement>('[data-workspace-document-id]')).find(
|
|
135
|
+
(element) =>
|
|
136
|
+
element.dataset['workspaceDocumentId'] === documentId &&
|
|
137
|
+
(activeWorkspaceDocumentViewId() === null ||
|
|
138
|
+
element.dataset['workspaceViewId'] === activeWorkspaceDocumentViewId()),
|
|
139
|
+
) ?? null
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface Scope {
|
|
144
|
+
readonly container: HTMLElement;
|
|
145
|
+
readonly name: DocumentProbeScope;
|
|
146
|
+
readonly id: string;
|
|
147
|
+
readonly title: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** THE VOCABULARY, and the one place each word is spelled. Every name resolves
|
|
151
|
+
* through its surface's OWN stamp — see this module's header. */
|
|
152
|
+
const SCOPE_NAMES: readonly DocumentProbeScope[] = [
|
|
153
|
+
'document',
|
|
154
|
+
'header',
|
|
155
|
+
'shelf',
|
|
156
|
+
'rail',
|
|
157
|
+
'outliner',
|
|
158
|
+
'content',
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
/** The two scopes that are VS Code views: the part id the contribution hands
|
|
162
|
+
* over (`frame/bridge.tsx`'s `offerVgaiPart` stamps it), and the view id the
|
|
163
|
+
* workbench registered it under (`vgai.contribution.ts`). */
|
|
164
|
+
const VIEW_SCOPES = {
|
|
165
|
+
rail: { part: 'properties', view: 'vgai.properties', title: 'Properties' },
|
|
166
|
+
outliner: { part: 'outliner', view: 'vgai.outliner', title: 'Outliner' },
|
|
167
|
+
content: { part: 'content', view: 'vgai.content', title: 'Content' },
|
|
168
|
+
} as const satisfies Record<string, { part: string; view: string; title: string }>;
|
|
169
|
+
|
|
170
|
+
/** The two scopes that are strips of the ACTIVE DOCUMENT's own box, by the
|
|
171
|
+
* `data-testid` their components write (`DocumentHeaderStrip`,
|
|
172
|
+
* `DocumentShelfRail`). */
|
|
173
|
+
const DOCUMENT_STRIPS = {
|
|
174
|
+
header: { prefix: 'document-header', title: 'header strip' },
|
|
175
|
+
shelf: { prefix: 'document-shelf', title: 'tool shelf' },
|
|
176
|
+
} as const satisfies Record<string, { prefix: string; title: string }>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* EVERY ROOT THIS SCOPE COVERS — the document's own box, and whatever it
|
|
180
|
+
* PORTALED out of itself.
|
|
181
|
+
*
|
|
182
|
+
* `ThemeRootPortal` writes an anchor span where the portal was declared and
|
|
183
|
+
* stamps both ends with one generated id (`data-vgai-portal` in place,
|
|
184
|
+
* `data-vgai-portal-content` on what landed at the theme root). So a menu the
|
|
185
|
+
* document's own header opened is found by walking that pair from INSIDE the
|
|
186
|
+
* box — not by a heuristic over every `.vgai-menu` on the root, which would
|
|
187
|
+
* also reach the editor's chrome menus and is exactly the widening this
|
|
188
|
+
* module's header rules out. A portal the chrome opened has its anchor in the
|
|
189
|
+
* chrome, so it is not a root here and stays refused.
|
|
190
|
+
*
|
|
191
|
+
* Re-read per step rather than cached with the scope: a menu opens and closes
|
|
192
|
+
* between steps, which is the whole point of driving one.
|
|
193
|
+
*/
|
|
194
|
+
function scopeRoots(scope: Scope): HTMLElement[] {
|
|
195
|
+
const roots: HTMLElement[] = [scope.container];
|
|
196
|
+
// TRANSITIVE, because a portal opens a portal: a menu's PANEL is portaled,
|
|
197
|
+
// and a submenu opened from one of its rows is portaled from inside THAT
|
|
198
|
+
// panel — so the second anchor is not in the document's box at all. One
|
|
199
|
+
// level deep found the Add menu's rows and none of Mesh's (measured
|
|
200
|
+
// 2026-09-21 on the Model document). The walk terminates because each id is
|
|
201
|
+
// taken once.
|
|
202
|
+
for (let index = 0; index < roots.length; index++) {
|
|
203
|
+
for (const anchor of roots[index]!.querySelectorAll<HTMLElement>('[data-vgai-portal]')) {
|
|
204
|
+
const id = anchor.dataset['vgaiPortal'];
|
|
205
|
+
if (id === undefined) continue;
|
|
206
|
+
const content = document.querySelector<HTMLElement>(
|
|
207
|
+
`[data-vgai-portal-content="${CSS.escape(id)}"]`,
|
|
208
|
+
);
|
|
209
|
+
if (content && !roots.includes(content)) roots.push(content);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return roots;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** A registered VIEW's handed-over body, by the part id the workbench stamped
|
|
216
|
+
* on it. `null` when the view is closed — its pane took the element with it. */
|
|
217
|
+
function viewPart(part: string): HTMLElement | null {
|
|
218
|
+
return document.querySelector<HTMLElement>(`[data-vgai-part="${part}"]`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resolveScope(name: DocumentProbeScope): Scope {
|
|
222
|
+
const view =
|
|
223
|
+
name === 'rail' || name === 'outliner' || name === 'content' ? VIEW_SCOPES[name] : null;
|
|
224
|
+
if (view) {
|
|
225
|
+
const container = viewPart(view.part);
|
|
226
|
+
if (!container) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`The ${view.title} view (${view.view}) is not open, so scope '${name}' has nothing to ` +
|
|
229
|
+
'reach. Open it from the workbench (View: Open View…) and retry — this door reads ' +
|
|
230
|
+
'and drives what is on screen, and reports its absence rather than an empty match list.',
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return { container, name, id: view.view, title: view.title };
|
|
234
|
+
}
|
|
235
|
+
return resolveDocumentScope(name);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `document`, `header` and `shelf` — the ACTIVE CENTRE DOCUMENT's box and the
|
|
239
|
+
* two strips inside it. */
|
|
240
|
+
function resolveDocumentScope(name: DocumentProbeScope): Scope {
|
|
241
|
+
const active = activeWorkspaceDocument();
|
|
242
|
+
if (!active)
|
|
243
|
+
throw new Error(
|
|
244
|
+
`No active editor document, so scope '${name}' has nothing to reach. The editor's own ` +
|
|
245
|
+
"views are still readable: scope 'outliner', scope 'rail', and scope 'content'.",
|
|
246
|
+
);
|
|
247
|
+
const id = activeWorkspaceDocumentId() ?? active.descriptor.id;
|
|
248
|
+
// The Game document is the one center document with its OWN doors — and the
|
|
249
|
+
// one where a synthetic DOM gesture is the exact instrument the doctrine
|
|
250
|
+
// bans ("never pilot a game with synthetic key events"). Its input is
|
|
251
|
+
// play-gated (`InputManager.setEnabled` + `gated-globals.ts`), so a probe
|
|
252
|
+
// here would either silently do nothing (not playing) or bypass the play
|
|
253
|
+
// path's contract (playing). Refuse toward the honest doors instead of
|
|
254
|
+
// becoming an ungated second one.
|
|
255
|
+
if (id === GAME_DOCUMENT_ID) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"The Game document is out of this door's scope: drive and read a game through its own " +
|
|
258
|
+
'doors — `game.commands()`/`game.command(n)`/`game.state(n)`, or `page(step)` during ' +
|
|
259
|
+
"play — never through synthetic DOM gestures. The editor's own views around it stay " +
|
|
260
|
+
"readable while a game is active: scope 'outliner' and scope 'rail'.",
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const container = activeDocumentContainer(id);
|
|
264
|
+
if (!container) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`The active document's surface is not mounted: ${id} (${active.title}). ` +
|
|
267
|
+
'Present it first (`editor.present`) and retry.',
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
// The scope is the document's WHOLE box — its header strip and its shelf
|
|
271
|
+
// rail as well as its content (`WorkspaceDocumentSurface`: the strip is a
|
|
272
|
+
// sibling of the content element the id is written on). Three builders
|
|
273
|
+
// measured the same wall: a document's own header controls and menus were
|
|
274
|
+
// unreachable and unreadable through the product, so a header could not be
|
|
275
|
+
// driven by its own click. The Game document's refusal above still holds.
|
|
276
|
+
const box = container.closest<HTMLElement>('.vgai-dock-document') ?? container;
|
|
277
|
+
if (name === 'document') return { container: box, name, id, title: active.title };
|
|
278
|
+
// `header` / `shelf` — MEASURED to be inside the box already (the strips are
|
|
279
|
+
// this element's own children), so these names buy AIM rather than reach: a
|
|
280
|
+
// selector that also matches in the content can be pointed at one strip
|
|
281
|
+
// without counting indices. A document that draws no strip (`runtime:
|
|
282
|
+
// false` and no toolbar, or a workspace whose skin hides headers) refuses by
|
|
283
|
+
// name rather than answering zero matches.
|
|
284
|
+
const strip = DOCUMENT_STRIPS[name as keyof typeof DOCUMENT_STRIPS];
|
|
285
|
+
const element = box.querySelector<HTMLElement>(`[data-testid="${strip.prefix}:${id}"]`);
|
|
286
|
+
if (!element) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`The active document "${active.title}" (${id}) draws no ${strip.title}, so scope ` +
|
|
289
|
+
`'${name}' has nothing to reach. Its whole box is scope 'document'.`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return { container: element, name, id, title: `${active.title} — ${strip.title}` };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** The refusal that keeps this door scoped — it names the scope every time,
|
|
296
|
+
* and the vocabulary, because "wrong scope" is the likeliest cause. */
|
|
297
|
+
function outOfScope(scope: Scope, detail: string): Error {
|
|
298
|
+
return new Error(
|
|
299
|
+
`Out of scope: ${detail}. This step ran in scope '${scope.name}' — ` +
|
|
300
|
+
`"${scope.title}" (${scope.id}) — and reaches ONLY inside it. The other surfaces are ` +
|
|
301
|
+
`named, not walked to: ${SCOPE_NAMES.filter((name) => name !== scope.name)
|
|
302
|
+
.map((name) => `'${name}'`)
|
|
303
|
+
.join(', ')} (pass \`{ scope }\`). The rest of the page is deliberately unreachable.`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const MAX_TEXT = 400;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The sentinel a custom property is resolved against.
|
|
311
|
+
*
|
|
312
|
+
* `getComputedStyle(el).getPropertyValue('--x')` answers with the DECLARED
|
|
313
|
+
* text, so a theme token reads back as its own `color-mix(…)` algebra rather
|
|
314
|
+
* than the colour it paints. To get the paint, the value has to go through a
|
|
315
|
+
* property the browser actually resolves. A throwaway span inside the element
|
|
316
|
+
* inherits the same custom-property cascade, so `color: var(--x)` on it
|
|
317
|
+
* computes to `rgb(…)`.
|
|
318
|
+
*
|
|
319
|
+
* The sentinel is how a NON-colour token stays honest: `color` silently keeps
|
|
320
|
+
* its previous value when the new one does not parse, so a token holding a
|
|
321
|
+
* length would otherwise be reported as whatever colour happened to be there.
|
|
322
|
+
* Seed the sentinel, apply the var, and an unchanged reading means "this did
|
|
323
|
+
* not resolve as a colour" — report the declared text instead of a lie.
|
|
324
|
+
*/
|
|
325
|
+
const STYLE_PROBE_SENTINEL = 'rgb(1, 2, 3)';
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* `background-color`'s INITIAL value, which is what the probe below computes
|
|
329
|
+
* to whenever the substituted token is not a colour — CSS's own answer for a
|
|
330
|
+
* declaration that is invalid at computed-value time on a NON-INHERITED
|
|
331
|
+
* property.
|
|
332
|
+
*/
|
|
333
|
+
const STYLE_PROBE_INITIAL = 'rgba(0, 0, 0, 0)';
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* What a custom property PAINTS, its declared text when it is not a colour,
|
|
337
|
+
* or the empty string when THE PROPERTY IS NOT DECLARED AT ALL.
|
|
338
|
+
*
|
|
339
|
+
* That last case is the one this door got wrong first and a builder caught: an
|
|
340
|
+
* undeclared `var(--x)` makes the whole declaration invalid, so the probe fell
|
|
341
|
+
* back and the reading came back as the surrounding text colour — a confident
|
|
342
|
+
* wrong answer, identical for a real token, a misspelled one and a group the
|
|
343
|
+
* palette does not declare. (Measured under Classic:
|
|
344
|
+
* `--vgai-viewport-background` and a deliberately nonexistent name both
|
|
345
|
+
* answered `rgb(197, 200, 206)`, and a reader nearly concluded Classic
|
|
346
|
+
* declares a viewport group.) A custom property's computed value is the empty
|
|
347
|
+
* string exactly when it is undeclared, so that is the answer.
|
|
348
|
+
*
|
|
349
|
+
* THE PAINT PROPERTY IS `background-color`, AND THAT IS THE WHOLE TRICK. It
|
|
350
|
+
* used to be `color`, with a sentinel seeded first and the reasoning "an
|
|
351
|
+
* unchanged reading means this did not resolve as a colour". That reasoning is
|
|
352
|
+
* wrong, because `color` is INHERITED: a declaration invalid at computed-value
|
|
353
|
+
* time does not keep its previous value, it takes the INHERITED one — so a
|
|
354
|
+
* token holding a length read back as whatever colour the surrounding text
|
|
355
|
+
* happened to be, the same confident lie one paragraph up. Measured 2026-09-21
|
|
356
|
+
* on the Outliner: `--vgai-tree-row-height`, which holds `20px`, answered
|
|
357
|
+
* `rgb(195, 195, 195)` while a reader was measuring row heights with it.
|
|
358
|
+
* `background-color` is NOT inherited, so the same invalid declaration lands
|
|
359
|
+
* on its initial value — `rgba(0, 0, 0, 0)`, a constant this module knows —
|
|
360
|
+
* and "did this resolve as a colour" becomes a fact instead of a guess. The
|
|
361
|
+
* sentinel is still seeded, so a browser that somehow leaves the declaration
|
|
362
|
+
* untouched is also caught. A token whose value IS `transparent` reports its
|
|
363
|
+
* declared text, which is the more useful of the two true answers.
|
|
364
|
+
*/
|
|
365
|
+
function resolveCustomProperty(element: HTMLElement, name: string): string {
|
|
366
|
+
const declared = getComputedStyle(element).getPropertyValue(name).trim();
|
|
367
|
+
if (declared === '') return '';
|
|
368
|
+
const owner = element.ownerDocument;
|
|
369
|
+
const probe = owner.createElement('span');
|
|
370
|
+
// Out of flow and invisible: this must not reflow the surface being measured.
|
|
371
|
+
probe.style.position = 'absolute';
|
|
372
|
+
probe.style.pointerEvents = 'none';
|
|
373
|
+
probe.style.visibility = 'hidden';
|
|
374
|
+
probe.style.backgroundColor = STYLE_PROBE_SENTINEL;
|
|
375
|
+
element.appendChild(probe);
|
|
376
|
+
try {
|
|
377
|
+
probe.style.backgroundColor = `var(${name})`;
|
|
378
|
+
const painted = getComputedStyle(probe).backgroundColor.trim();
|
|
379
|
+
return painted === STYLE_PROBE_SENTINEL || painted === STYLE_PROBE_INITIAL ? declared : painted;
|
|
380
|
+
} finally {
|
|
381
|
+
probe.remove();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resolveStyles(element: HTMLElement, names: readonly string[]): Record<string, string> {
|
|
386
|
+
const computed = getComputedStyle(element);
|
|
387
|
+
const styles: Record<string, string> = {};
|
|
388
|
+
for (const name of names) {
|
|
389
|
+
styles[name] = name.startsWith('--')
|
|
390
|
+
? resolveCustomProperty(element, name)
|
|
391
|
+
: // A standard property is already RESOLVED by the browser here, which is
|
|
392
|
+
// the whole reason to ask for it: the token's expression has become the
|
|
393
|
+
// rgb the person sees.
|
|
394
|
+
computed.getPropertyValue(cssPropertyName(name)).trim();
|
|
395
|
+
}
|
|
396
|
+
return styles;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** `backgroundColor` → `background-color`; a name already dashed passes through. */
|
|
400
|
+
function cssPropertyName(name: string): string {
|
|
401
|
+
return name.includes('-') ? name : name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function describeElement(
|
|
405
|
+
element: Element,
|
|
406
|
+
index: number,
|
|
407
|
+
styles?: readonly string[],
|
|
408
|
+
): ProbedElement {
|
|
409
|
+
const html = element as HTMLElement;
|
|
410
|
+
const attributes: Record<string, string> = {};
|
|
411
|
+
for (const attribute of Array.from(element.attributes)) {
|
|
412
|
+
attributes[attribute.name] = attribute.value;
|
|
413
|
+
}
|
|
414
|
+
const rect = html.getBoundingClientRect();
|
|
415
|
+
const value = (element as HTMLInputElement).value;
|
|
416
|
+
const checked = (element as HTMLInputElement).checked;
|
|
417
|
+
// `||`, not `??`: in a BACKGROUNDED tab — this door's primary caller —
|
|
418
|
+
// Chrome answers `innerText` with the empty string for everything, so the
|
|
419
|
+
// nullish fallback never fired and every probe read a blank document
|
|
420
|
+
// (measured live, 2026-08-14). `textContent` is the honest answer there.
|
|
421
|
+
const text = (html.innerText || element.textContent || '').trim();
|
|
422
|
+
return {
|
|
423
|
+
index,
|
|
424
|
+
tag: element.tagName.toLowerCase(),
|
|
425
|
+
text: text.length > MAX_TEXT ? `${text.slice(0, MAX_TEXT)}…` : text,
|
|
426
|
+
attributes,
|
|
427
|
+
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
428
|
+
...(typeof value === 'string' ? { value } : {}),
|
|
429
|
+
...(typeof checked === 'boolean' ? { checked } : {}),
|
|
430
|
+
...(html.hasAttribute('disabled') ? { disabled: true } : {}),
|
|
431
|
+
...(styles && styles.length > 0 ? { styles: resolveStyles(html, styles) } : {}),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Resolve one target inside the scope, refusing loudly rather than reaching out. */
|
|
436
|
+
function resolveTarget(scope: Scope, selector: string, index: number): HTMLElement {
|
|
437
|
+
const matches = matchesIn(scope, selector);
|
|
438
|
+
const element = matches[index];
|
|
439
|
+
if (!element) {
|
|
440
|
+
throw outOfScope(
|
|
441
|
+
scope,
|
|
442
|
+
`${JSON.stringify(selector)} matched ${matches.length} element(s), so index ${index} does not exist`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
// querySelectorAll on an element is already descendants-only; this asserts
|
|
446
|
+
// the invariant rather than trusting it. A PORTALED overlay the document
|
|
447
|
+
// opened is one of the roots ({@link scopeRoots}), so it passes here by
|
|
448
|
+
// being inside the root that owns it and not by any relaxation.
|
|
449
|
+
if (!scopeRoots(scope).some((root) => root.contains(element))) {
|
|
450
|
+
throw outOfScope(
|
|
451
|
+
scope,
|
|
452
|
+
`${JSON.stringify(selector)} resolved outside the document's container`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return element;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function pointerInit(element: HTMLElement): MouseEventInit {
|
|
459
|
+
const rect = element.getBoundingClientRect();
|
|
460
|
+
return {
|
|
461
|
+
bubbles: true,
|
|
462
|
+
cancelable: true,
|
|
463
|
+
composed: true,
|
|
464
|
+
view: window,
|
|
465
|
+
clientX: rect.x + rect.width / 2,
|
|
466
|
+
clientY: rect.y + rect.height / 2,
|
|
467
|
+
button: 0,
|
|
468
|
+
buttons: 1,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* A REAL click: the same event sequence a mouse produces. `element.click()`
|
|
474
|
+
* dispatches only `click`, so a component that selects on `pointerdown` (the
|
|
475
|
+
* sheet grid's cell selection) never sees the gesture at all.
|
|
476
|
+
*
|
|
477
|
+
* `clicks` > 1 is a REAL double click and not two calls to this function: the
|
|
478
|
+
* browser numbers consecutive presses in `detail`, and a `dblclick` follows
|
|
479
|
+
* the second `click`. React's `onDoubleClick` listens for that `dblclick` and
|
|
480
|
+
* nothing else, so a rename driven by two separate single clicks never starts
|
|
481
|
+
* (measured against the Outliner's own `onDoubleClick` → `onStartEditing`).
|
|
482
|
+
*/
|
|
483
|
+
function dispatchClick(element: HTMLElement, clicks = 1): void {
|
|
484
|
+
const init = pointerInit(element);
|
|
485
|
+
const total = Math.max(1, Math.round(clicks));
|
|
486
|
+
withoutPointerCapture(element, () => {
|
|
487
|
+
for (let n = 1; n <= total; n++) {
|
|
488
|
+
const down = { ...init, detail: n };
|
|
489
|
+
const up = { ...init, buttons: 0, detail: n };
|
|
490
|
+
element.dispatchEvent(new PointerEvent('pointerdown', { ...down, pointerType: 'mouse' }));
|
|
491
|
+
element.dispatchEvent(new MouseEvent('mousedown', down));
|
|
492
|
+
element.focus();
|
|
493
|
+
element.dispatchEvent(new PointerEvent('pointerup', { ...up, pointerType: 'mouse' }));
|
|
494
|
+
element.dispatchEvent(new MouseEvent('mouseup', up));
|
|
495
|
+
element.dispatchEvent(new MouseEvent('click', up));
|
|
496
|
+
}
|
|
497
|
+
if (total >= 2) {
|
|
498
|
+
element.dispatchEvent(new MouseEvent('dblclick', { ...init, buttons: 0, detail: total }));
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* TYPE `text` into a field the way a person does — one character at a time,
|
|
505
|
+
* through the PROTOTYPE's value setter, between real `keydown`/`keyup`.
|
|
506
|
+
*
|
|
507
|
+
* Three traps this exists for, each measured on a shipped surface:
|
|
508
|
+
* - `paste` cannot stand in. An untrusted `ClipboardEvent` performs NO
|
|
509
|
+
* default action, so a plain `<input>` with no paste handler keeps its old
|
|
510
|
+
* value and the probe reports a click it never made.
|
|
511
|
+
* - `element.value = x` is invisible to React (the value tracker it installs
|
|
512
|
+
* on the node compares the new value against itself and re-renders the old
|
|
513
|
+
* one) — the same trap {@link setSelectValue} documents, and the same cure.
|
|
514
|
+
* - a field may commit on a KEY rather than on `input`. The Outliner's rename
|
|
515
|
+
* reads `event.currentTarget.value` inside `onKeyDown` for Enter; a numeric
|
|
516
|
+
* field in the Properties rail blurs on Enter. So the keys are real and the
|
|
517
|
+
* Enter is the caller's choice, not an implicit one.
|
|
518
|
+
*/
|
|
519
|
+
function typeInto(element: HTMLElement, text: string, replace: boolean, enter: boolean): void {
|
|
520
|
+
const editable =
|
|
521
|
+
element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement ? element : null;
|
|
522
|
+
if (!editable) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`Cannot type into a <${element.tagName.toLowerCase()}>: this verb drives a text field. ` +
|
|
525
|
+
"Click the control that opens one first (the Outliner's rename is a double click — " +
|
|
526
|
+
'`click(selector, { clicks: 2 })`), then `type` into the field it puts up.',
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const proto =
|
|
530
|
+
element instanceof HTMLTextAreaElement
|
|
531
|
+
? HTMLTextAreaElement.prototype
|
|
532
|
+
: HTMLInputElement.prototype;
|
|
533
|
+
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
534
|
+
const write = (next: string) => {
|
|
535
|
+
if (setter) setter.call(editable, next);
|
|
536
|
+
else editable.value = next;
|
|
537
|
+
};
|
|
538
|
+
editable.focus();
|
|
539
|
+
let held = editable.value;
|
|
540
|
+
if (replace && held !== '') {
|
|
541
|
+
// What select-all-and-delete produces, in one edit: a person's first
|
|
542
|
+
// keystroke over a selected field is a replacement, not N backspaces.
|
|
543
|
+
editable.select?.();
|
|
544
|
+
held = '';
|
|
545
|
+
write(held);
|
|
546
|
+
editable.dispatchEvent(
|
|
547
|
+
new InputEvent('input', {
|
|
548
|
+
bubbles: true,
|
|
549
|
+
composed: true,
|
|
550
|
+
inputType: 'deleteContentBackward',
|
|
551
|
+
}),
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
for (const character of [...text]) {
|
|
555
|
+
const init: KeyboardEventInit = {
|
|
556
|
+
key: character,
|
|
557
|
+
bubbles: true,
|
|
558
|
+
cancelable: true,
|
|
559
|
+
composed: true,
|
|
560
|
+
};
|
|
561
|
+
editable.dispatchEvent(new KeyboardEvent('keydown', init));
|
|
562
|
+
held += character;
|
|
563
|
+
write(held);
|
|
564
|
+
editable.dispatchEvent(
|
|
565
|
+
new InputEvent('input', {
|
|
566
|
+
bubbles: true,
|
|
567
|
+
composed: true,
|
|
568
|
+
inputType: 'insertText',
|
|
569
|
+
data: character,
|
|
570
|
+
}),
|
|
571
|
+
);
|
|
572
|
+
editable.dispatchEvent(new KeyboardEvent('keyup', init));
|
|
573
|
+
}
|
|
574
|
+
if (enter) {
|
|
575
|
+
const init: KeyboardEventInit = {
|
|
576
|
+
key: 'Enter',
|
|
577
|
+
code: 'Enter',
|
|
578
|
+
bubbles: true,
|
|
579
|
+
cancelable: true,
|
|
580
|
+
composed: true,
|
|
581
|
+
};
|
|
582
|
+
editable.dispatchEvent(new KeyboardEvent('keydown', init));
|
|
583
|
+
editable.dispatchEvent(new KeyboardEvent('keyup', init));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* A synthetic pointer has no id the browser knows, so a listener that calls
|
|
589
|
+
* `setPointerCapture(event.pointerId)` — three's OrbitControls and
|
|
590
|
+
* TransformControls do, on every press — throws `NotFoundError` INTO THE
|
|
591
|
+
* CONSOLE whenever a document yields the press to the viewport (measured
|
|
592
|
+
* 2026-09-02: every missed click on an Asset Lab canvas). Listeners run
|
|
593
|
+
* synchronously inside `dispatchEvent`, so the capture calls are made
|
|
594
|
+
* harmless for exactly the dispatch and restored after; a real pointer is
|
|
595
|
+
* never affected.
|
|
596
|
+
*/
|
|
597
|
+
function withoutPointerCapture(_element: HTMLElement, dispatch: () => void): void {
|
|
598
|
+
// On the PROTOTYPE, not the target: the viewport's controls listen on the
|
|
599
|
+
// canvas's container and capture THERE, so the event's whole bubble path
|
|
600
|
+
// has to be covered. Synchronous, restored in `finally`, and a real pointer
|
|
601
|
+
// (which never dispatches through here) is untouched.
|
|
602
|
+
const proto = Element.prototype;
|
|
603
|
+
const set = proto.setPointerCapture;
|
|
604
|
+
const release = proto.releasePointerCapture;
|
|
605
|
+
proto.setPointerCapture = () => {};
|
|
606
|
+
proto.releasePointerCapture = () => {};
|
|
607
|
+
try {
|
|
608
|
+
dispatch();
|
|
609
|
+
} finally {
|
|
610
|
+
proto.setPointerCapture = set;
|
|
611
|
+
proto.releasePointerCapture = release;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** A REAL drag: press at `from`, move through `steps` points, release at `to`,
|
|
616
|
+
* all in the element's own box fractions — `dispatchClick`'s sequence with
|
|
617
|
+
* the moves a mouse makes between press and release. Moves carry
|
|
618
|
+
* `buttons: 1` (the primary button is held), the release `buttons: 0`. */
|
|
619
|
+
function dispatchDrag(
|
|
620
|
+
element: HTMLElement,
|
|
621
|
+
from: readonly [number, number],
|
|
622
|
+
to: readonly [number, number],
|
|
623
|
+
steps: number,
|
|
624
|
+
modifiers: { altKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean } = {},
|
|
625
|
+
via: readonly (readonly [number, number])[] = [],
|
|
626
|
+
): void {
|
|
627
|
+
const rect = element.getBoundingClientRect();
|
|
628
|
+
const at = (fx: number, fy: number): MouseEventInit => ({
|
|
629
|
+
bubbles: true,
|
|
630
|
+
cancelable: true,
|
|
631
|
+
composed: true,
|
|
632
|
+
view: window,
|
|
633
|
+
clientX: rect.x + rect.width * fx,
|
|
634
|
+
clientY: rect.y + rect.height * fy,
|
|
635
|
+
button: 0,
|
|
636
|
+
buttons: 1,
|
|
637
|
+
altKey: modifiers.altKey ?? false,
|
|
638
|
+
ctrlKey: modifiers.ctrlKey ?? false,
|
|
639
|
+
metaKey: modifiers.metaKey ?? false,
|
|
640
|
+
shiftKey: modifiers.shiftKey ?? false,
|
|
641
|
+
});
|
|
642
|
+
const start = at(from[0], from[1]);
|
|
643
|
+
withoutPointerCapture(element, () => {
|
|
644
|
+
// A REAL POINTER HOVERS BEFORE IT PRESSES, and some targets latch on the
|
|
645
|
+
// hover rather than on the press. three's `TransformControls` is the
|
|
646
|
+
// worked case: `pointerHover` is what sets `this.axis` from the picker
|
|
647
|
+
// under the cursor, and `pointerDown` does nothing at all while `axis` is
|
|
648
|
+
// null — so a synthetic drag that began with `pointerdown` handed the
|
|
649
|
+
// press to the VIEWPORT instead, which read it as a click on empty space
|
|
650
|
+
// and cleared the selection. Measured 2026-09-21 on a Blender Model
|
|
651
|
+
// document: dragging the X arrow of a freshly duplicated cube moved
|
|
652
|
+
// nothing and deselected it.
|
|
653
|
+
//
|
|
654
|
+
// Same family as the `button: -1` note below, and the same rule: mirror
|
|
655
|
+
// what the platform does. `buttons: 0` because nothing is held yet.
|
|
656
|
+
element.dispatchEvent(
|
|
657
|
+
new PointerEvent('pointermove', {
|
|
658
|
+
...start,
|
|
659
|
+
button: -1,
|
|
660
|
+
buttons: 0,
|
|
661
|
+
pointerType: 'mouse',
|
|
662
|
+
}),
|
|
663
|
+
);
|
|
664
|
+
element.dispatchEvent(new MouseEvent('mousemove', { ...start, buttons: 0 }));
|
|
665
|
+
element.dispatchEvent(new PointerEvent('pointerdown', { ...start, pointerType: 'mouse' }));
|
|
666
|
+
element.dispatchEvent(new MouseEvent('mousedown', start));
|
|
667
|
+
element.focus();
|
|
668
|
+
const count = Math.max(1, Math.round(steps));
|
|
669
|
+
const path: (readonly [number, number])[] = [from, ...via, to];
|
|
670
|
+
for (let leg = 0; leg + 1 < path.length; leg++) {
|
|
671
|
+
const a = path[leg] as readonly [number, number];
|
|
672
|
+
const b = path[leg + 1] as readonly [number, number];
|
|
673
|
+
for (let i = 1; i <= count; i++) {
|
|
674
|
+
const t = i / count;
|
|
675
|
+
const move = at(a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t);
|
|
676
|
+
// `button: -1` ON A `pointermove`, because that is what a real mouse
|
|
677
|
+
// reports: `PointerEvent.button` names the button whose STATE CHANGED,
|
|
678
|
+
// and on a move none did. Held-ness lives in `buttons: 1` (kept by
|
|
679
|
+
// `at`). This is not a detail — three.js guards on it exactly:
|
|
680
|
+
// `TransformControls.pointerMove` returns at
|
|
681
|
+
// `pointer.button !== -1` (r180, :472), so a drag of a gizmo handle
|
|
682
|
+
// latched the axis, drew the drag helper line, and then moved NOTHING,
|
|
683
|
+
// forever (measured 2026-09-21 on a Blender Model document, where it
|
|
684
|
+
// read as "the transform provider is not writing"). `MouseEvent`'s
|
|
685
|
+
// legacy `mousemove` keeps `button: 0` — a real browser reports that
|
|
686
|
+
// one as 0, and mirroring the platform is the whole job here.
|
|
687
|
+
element.dispatchEvent(
|
|
688
|
+
new PointerEvent('pointermove', { ...move, button: -1, pointerType: 'mouse' }),
|
|
689
|
+
);
|
|
690
|
+
element.dispatchEvent(new MouseEvent('mousemove', move));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const end = { ...at(to[0], to[1]), buttons: 0 };
|
|
694
|
+
element.dispatchEvent(new PointerEvent('pointerup', { ...end, pointerType: 'mouse' }));
|
|
695
|
+
element.dispatchEvent(new MouseEvent('mouseup', end));
|
|
696
|
+
if (from[0] === to[0] && from[1] === to[1]) {
|
|
697
|
+
element.dispatchEvent(new MouseEvent('click', end));
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** The element a key goes to: the explicit target, else whatever inside the
|
|
703
|
+
* scope currently has focus, else the container itself. */
|
|
704
|
+
function keyTarget(scope: Scope, explicit: HTMLElement | null): HTMLElement {
|
|
705
|
+
if (explicit) return explicit;
|
|
706
|
+
const focused = document.activeElement;
|
|
707
|
+
if (focused instanceof HTMLElement && scopeRoots(scope).some((root) => root.contains(focused)))
|
|
708
|
+
return focused;
|
|
709
|
+
return scope.container;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** Run a selector inside the scope, turning a malformed one into an honest error. */
|
|
713
|
+
function matchesIn(scope: Scope, selector: string): HTMLElement[] {
|
|
714
|
+
try {
|
|
715
|
+
return scopeRoots(scope).flatMap((root) =>
|
|
716
|
+
Array.from(root.querySelectorAll<HTMLElement>(selector)),
|
|
717
|
+
);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
throw new Error(`Invalid selector ${JSON.stringify(selector)}: ${String(error)}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** An optional `selector` resolves to a target; its absence means "the focused
|
|
724
|
+
* element inside the scope" — the shared shape of the `key` and `paste` steps. */
|
|
725
|
+
function gestureTarget(scope: Scope, step: { selector?: string; index?: number }): HTMLElement {
|
|
726
|
+
const explicit =
|
|
727
|
+
step.selector === undefined ? null : resolveTarget(scope, step.selector, step.index ?? 0);
|
|
728
|
+
return keyTarget(scope, explicit);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function keyInit(step: DocumentKeyStep): KeyboardEventInit {
|
|
732
|
+
return {
|
|
733
|
+
key: step.key,
|
|
734
|
+
code: step.code ?? step.key,
|
|
735
|
+
bubbles: true,
|
|
736
|
+
cancelable: true,
|
|
737
|
+
composed: true,
|
|
738
|
+
...(step.ctrlKey ? { ctrlKey: true } : {}),
|
|
739
|
+
...(step.metaKey ? { metaKey: true } : {}),
|
|
740
|
+
...(step.shiftKey ? { shiftKey: true } : {}),
|
|
741
|
+
...(step.altKey ? { altKey: true } : {}),
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Set a `<select>`'s value the way React can see it.
|
|
747
|
+
*
|
|
748
|
+
* `HTMLSelectElement.prototype`'s own `value` setter is called explicitly
|
|
749
|
+
* because React installs a value tracker on the node: assigning through the
|
|
750
|
+
* instance updates that tracker too, so React compares the new value against
|
|
751
|
+
* itself, concludes nothing changed, and re-renders the OLD value. Going
|
|
752
|
+
* through the prototype descriptor leaves the tracker stale, which is exactly
|
|
753
|
+
* what makes the following `change` read as a real user edit.
|
|
754
|
+
*/
|
|
755
|
+
function setSelectValue(element: HTMLSelectElement, value: string): void {
|
|
756
|
+
const options = [...element.options].map((option) => option.value);
|
|
757
|
+
if (!options.includes(value)) {
|
|
758
|
+
throw new Error(
|
|
759
|
+
`No option with value ${JSON.stringify(value)} on this <select> — it offers ` +
|
|
760
|
+
`${options.map((option) => JSON.stringify(option)).join(', ') || '(no options)'}. ` +
|
|
761
|
+
"The value is the option's `value`, not its label; `query` reports both.",
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value')?.set;
|
|
765
|
+
if (setter) setter.call(element, value);
|
|
766
|
+
else element.value = value;
|
|
767
|
+
element.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
768
|
+
element.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const PROBE_ACTIONS = ['query', 'click', 'drag', 'key', 'paste', 'select', 'type'] as const;
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* THE WIRE'S TWO FREE-FORM FIELDS, both refused by name.
|
|
775
|
+
*
|
|
776
|
+
* The wire hands the step in untyped (`cmd['step']`), so an unknown verb or an
|
|
777
|
+
* unknown scope must refuse HERE — an exhaustive switch alone would fall
|
|
778
|
+
* through and answer `ok` with nothing, and an unrecognized scope would
|
|
779
|
+
* quietly fall back to the document and answer about a surface the caller did
|
|
780
|
+
* not ask for. Both are the silent pass-through the strict-input rule bans.
|
|
781
|
+
*/
|
|
782
|
+
function acceptStep(step: DocumentProbeStep): Scope {
|
|
783
|
+
const action = (step as { action?: unknown } | null | undefined)?.action;
|
|
784
|
+
if (!PROBE_ACTIONS.includes(action as (typeof PROBE_ACTIONS)[number])) {
|
|
785
|
+
throw new Error(
|
|
786
|
+
`Unknown document-probe action ${JSON.stringify(action)}. This door has exactly ` +
|
|
787
|
+
`${PROBE_ACTIONS.length} verbs: ${PROBE_ACTIONS.join(', ')} (\`editor.document.*\`).`,
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const requested = (step as { scope?: unknown }).scope ?? 'document';
|
|
791
|
+
if (!SCOPE_NAMES.includes(requested as DocumentProbeScope)) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
`Unknown scope ${JSON.stringify(requested)}. This door addresses exactly these ` +
|
|
794
|
+
`surfaces: ${SCOPE_NAMES.map((name) => `'${name}'`).join(', ')}.`,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
return resolveScope(requested as DocumentProbeScope);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export async function runDocumentProbe(step: DocumentProbeStep): Promise<DocumentProbeResult> {
|
|
801
|
+
const scope = acceptStep(step);
|
|
802
|
+
const where = { name: scope.name, id: scope.id, title: scope.title };
|
|
803
|
+
/** Every gesture answers with the element it drove, so a transcript proves
|
|
804
|
+
* WHAT was driven and not merely that something was. */
|
|
805
|
+
const drove = (element: HTMLElement): DocumentProbeResult => ({
|
|
806
|
+
scope: where,
|
|
807
|
+
matched: 1,
|
|
808
|
+
elements: [describeElement(element, 0)],
|
|
809
|
+
});
|
|
810
|
+
switch (step.action) {
|
|
811
|
+
case 'query': {
|
|
812
|
+
const matches = matchesIn(scope, step.selector);
|
|
813
|
+
return {
|
|
814
|
+
scope: where,
|
|
815
|
+
matched: matches.length,
|
|
816
|
+
elements: matches
|
|
817
|
+
.slice(0, step.limit ?? 25)
|
|
818
|
+
.map((element, index) => describeElement(element, index, step.styles)),
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
case 'click': {
|
|
822
|
+
const element = resolveTarget(scope, step.selector, step.index ?? 0);
|
|
823
|
+
dispatchClick(element, step.clicks ?? 1);
|
|
824
|
+
return drove(element);
|
|
825
|
+
}
|
|
826
|
+
case 'type': {
|
|
827
|
+
const target = gestureTarget(scope, step);
|
|
828
|
+
typeInto(target, step.text, step.replace ?? true, step.enter ?? true);
|
|
829
|
+
return drove(target);
|
|
830
|
+
}
|
|
831
|
+
case 'drag': {
|
|
832
|
+
const element = resolveTarget(scope, step.selector, step.index ?? 0);
|
|
833
|
+
dispatchDrag(
|
|
834
|
+
element,
|
|
835
|
+
step.from,
|
|
836
|
+
step.to,
|
|
837
|
+
step.steps ?? 8,
|
|
838
|
+
{
|
|
839
|
+
...(step.altKey === undefined ? {} : { altKey: step.altKey }),
|
|
840
|
+
...(step.ctrlKey === undefined ? {} : { ctrlKey: step.ctrlKey }),
|
|
841
|
+
...(step.metaKey === undefined ? {} : { metaKey: step.metaKey }),
|
|
842
|
+
...(step.shiftKey === undefined ? {} : { shiftKey: step.shiftKey }),
|
|
843
|
+
},
|
|
844
|
+
step.via ?? [],
|
|
845
|
+
);
|
|
846
|
+
return drove(element);
|
|
847
|
+
}
|
|
848
|
+
case 'key': {
|
|
849
|
+
const target = gestureTarget(scope, step);
|
|
850
|
+
const init = keyInit(step);
|
|
851
|
+
target.dispatchEvent(new KeyboardEvent('keydown', init));
|
|
852
|
+
target.dispatchEvent(new KeyboardEvent('keyup', init));
|
|
853
|
+
return drove(target);
|
|
854
|
+
}
|
|
855
|
+
case 'select': {
|
|
856
|
+
const element = resolveTarget(scope, step.selector, step.index ?? 0);
|
|
857
|
+
if (!(element instanceof HTMLSelectElement)) {
|
|
858
|
+
throw new Error(
|
|
859
|
+
`${JSON.stringify(step.selector)} resolved a <${element.tagName.toLowerCase()}>, not a ` +
|
|
860
|
+
"<select>. This verb sets a dropdown's value; a button or a checkbox takes `click`.",
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
setSelectValue(element, step.value);
|
|
864
|
+
return drove(element);
|
|
865
|
+
}
|
|
866
|
+
case 'paste': {
|
|
867
|
+
const target = gestureTarget(scope, step);
|
|
868
|
+
const data = new DataTransfer();
|
|
869
|
+
data.setData('text/plain', step.text);
|
|
870
|
+
target.dispatchEvent(
|
|
871
|
+
new ClipboardEvent('paste', {
|
|
872
|
+
bubbles: true,
|
|
873
|
+
cancelable: true,
|
|
874
|
+
composed: true,
|
|
875
|
+
clipboardData: data,
|
|
876
|
+
}),
|
|
877
|
+
);
|
|
878
|
+
return drove(target);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|