@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,1143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ADAPTER LOADER — the host half of `vgai.adapter.ts`.
|
|
3
|
+
*
|
|
4
|
+
* ARCHITECTURE-CORE §The editor protocol: every game supplies a SERVER — its
|
|
5
|
+
* adapter — and the editor is a universal CLIENT of it. This module is where a
|
|
6
|
+
* project's adapter becomes a live binding table:
|
|
7
|
+
*
|
|
8
|
+
* 1. the project has `vgai.adapter.ts` ⇒ **the project's own file wins,
|
|
9
|
+
* always**. It is imported through the SAME door every other
|
|
10
|
+
* project-owned module goes through (`project-module-url.ts`'s
|
|
11
|
+
* `fsImportPath` — the ONE owner of `/@fs/` urls for project code, PD-3)
|
|
12
|
+
* and its default export is parsed by `parseAdapterDefinition`, which
|
|
13
|
+
* rejects an unknown key by name. This branch is FIRST because a game
|
|
14
|
+
* supplies its own protocol server: a project that ships the declaration
|
|
15
|
+
* has stated its table, and no host-side table may quietly outrank it —
|
|
16
|
+
* not even when its ingest root id happens to match a registry key
|
|
17
|
+
* (a user's `cuberun` folder is not ours);
|
|
18
|
+
* 2. the project is an INGEST GAME whose adapter the host's in-tree registry
|
|
19
|
+
* holds, and it shipped none of its own ⇒ that module is the adapter.
|
|
20
|
+
* Placement follows REALM (ARCHITECTURE-CORE §The editor protocol): a
|
|
21
|
+
* vendored game's bytes are served verbatim, so its host-realm declaration
|
|
22
|
+
* lives in `ingest/games/<id>/vgai.adapter.ts` and is imported through the
|
|
23
|
+
* registry glob, never through the project's file routes. **This branch is
|
|
24
|
+
* REACHABLE only because the step-1 probe can answer "no"**: the realm
|
|
25
|
+
* rule keeps a vendored game's adapter out of its served bundle, but
|
|
26
|
+
* "the file is not on disk" and "the probe says absent" were different
|
|
27
|
+
* answers until `probeProjectFile` learned to recognize the SPA fallback
|
|
28
|
+
* — a 200 serving `index.html` for every missing project-root path, on
|
|
29
|
+
* both the dev server and the hosted surface. An existence check reading
|
|
30
|
+
* `res.ok` is a constant TRUE here and makes this branch dead code, which
|
|
31
|
+
* is exactly how every vendored game silently lost its scene table once.
|
|
32
|
+
* **A binding taken from here is NEVER silent**:
|
|
33
|
+
* the facet's `source` says `'registry'` and its `modulePath` names the
|
|
34
|
+
* repo file, so `vgai status` reports whose declaration is running. A
|
|
35
|
+
* project bound this way did not ship that table; the host did, and the
|
|
36
|
+
* user is entitled to read that off the same door as everything else;
|
|
37
|
+
* 3. the project has NONE ⇒ it gets `nativeAdapter()`. **That absence IS the
|
|
38
|
+
* declared native default, not a silent fallback**: the same binding table
|
|
39
|
+
* the template ships explicitly, chosen because the project declared
|
|
40
|
+
* nothing else — there is no third behavior for "no adapter".
|
|
41
|
+
*
|
|
42
|
+
* The ordering also settles the one folder that can answer BOTH probes: an
|
|
43
|
+
* in-tree ingest fixture keeps its manifest and its adapter side by side, so
|
|
44
|
+
* opening `ingest/games/<id>/` as a folder project reaches THE SAME FILE
|
|
45
|
+
* through the project door. One declaration, one winner, decided by a rule that
|
|
46
|
+
* does not ask which population the project belongs to.
|
|
47
|
+
*
|
|
48
|
+
* It is also the ONE sanctioned importer of `@editor/finders`
|
|
49
|
+
* (`packages/engine/test/finder-import-boundary.test.ts` is the tripwire). The
|
|
50
|
+
* engine never runs a finder nobody selected; the loader runs exactly the
|
|
51
|
+
* selections the adapter's table names, with the inputs gathered here.
|
|
52
|
+
*
|
|
53
|
+
* The resolved table is published as the `adapter` facet of `/__editor/state`
|
|
54
|
+
* (`command-listener.ts`'s `collectState`), which is the minimal proof-of-load
|
|
55
|
+
* client: `vgai status` / `vgai eval 'await editor.status()'` read which
|
|
56
|
+
* adapter loaded, its regions, and its scene table.
|
|
57
|
+
*
|
|
58
|
+
* The OBSERVATION table has one client and it is not the facet: the ingest
|
|
59
|
+
* mount's system-adapter assembly (`ingest/ingest-render-debug.ts`'s
|
|
60
|
+
* `wireIngestSystems`) projects the declarations onto the host's ordinary
|
|
61
|
+
* `SystemAdapters.debug` alongside the game's own contract projection, so
|
|
62
|
+
* `game.providers()` / `game.state()` reach an adapter declaration through the
|
|
63
|
+
* SAME door first-party content uses. The facet publishes only the statically
|
|
64
|
+
* readable half (ids, kinds, whether an answer is bound); the closures travel
|
|
65
|
+
* host-side through `adapter-observation.ts`, because a closure cannot be a
|
|
66
|
+
* wire shape.
|
|
67
|
+
*
|
|
68
|
+
* The scene table has TWO real clients (ARCHITECTURE-CORE §Editor). The EDIT
|
|
69
|
+
* TAB ROW is the first: `components/scene-documents.tsx` subscribes to this
|
|
70
|
+
* facet and projects `scenes` as workspace documents — every authorable scene
|
|
71
|
+
* a tab, the default open by default — and `command-listener.ts`'s `open` verb
|
|
72
|
+
* resolves one entry through the same pure decision table
|
|
73
|
+
* (`scene-document-plan.ts`). Play's `open()`, walking the table as live
|
|
74
|
+
* navigation, is the second and is a later rung.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
import type { EntrypointSource, FinderInput, ProjectComponentRef } from '@editor/finders';
|
|
78
|
+
import {
|
|
79
|
+
type FinderResult,
|
|
80
|
+
type ProjectSourceFile,
|
|
81
|
+
registerContributedFinder,
|
|
82
|
+
runFinderSelection,
|
|
83
|
+
} from '@editor/finders';
|
|
84
|
+
import { fsImportPath } from '@volter/editor-sdk/session/project-module-url';
|
|
85
|
+
import type {
|
|
86
|
+
AdapterDefinition,
|
|
87
|
+
AdapterRegion,
|
|
88
|
+
DocumentEntry,
|
|
89
|
+
ObservationDeclaration,
|
|
90
|
+
ObservationKind,
|
|
91
|
+
} from '@volter/editor-project/adapter/adapter-module';
|
|
92
|
+
import {
|
|
93
|
+
nativeAdapter,
|
|
94
|
+
parseAdapterDefinition,
|
|
95
|
+
regionsFromManifestRoots,
|
|
96
|
+
unmatchedRegionIncludeIds,
|
|
97
|
+
} from '@volter/editor-project/adapter/adapter-module';
|
|
98
|
+
import { declaredRoots, ingestRoots } from '@volter/editor-project/adapter/manifest-interpreter';
|
|
99
|
+
import type { ResolvedGameManifest } from '@volter/editor-project/manifest/load';
|
|
100
|
+
import { activeProjectKey } from './active-project';
|
|
101
|
+
import { setAdapterEditorConfiguration } from './adapter-editor-config';
|
|
102
|
+
import { setAdapterInputBinding, setAdapterObservations } from './adapter-observation';
|
|
103
|
+
import { connectSourceFileEvents } from './asset-events';
|
|
104
|
+
import {
|
|
105
|
+
listProjectComponents,
|
|
106
|
+
listProjectSourceFiles,
|
|
107
|
+
probeProjectFile,
|
|
108
|
+
readProjectTextFile,
|
|
109
|
+
} from './editor-api';
|
|
110
|
+
import { editorConsole } from './editor-console';
|
|
111
|
+
import { fetchGameManifest } from './manifest-project';
|
|
112
|
+
import { setPresentationRegions } from './presentation-surface';
|
|
113
|
+
import { type ActiveProject, getCurrentProject, onProjectChange } from './project-manager';
|
|
114
|
+
import { projectModuleChangeMatches, subscribeProjectModuleChange } from './project-module-changes';
|
|
115
|
+
import { notifyProjectShapeChanged, registerDocumentKindsSupplier } from './project-shape';
|
|
116
|
+
import { resolveRelativeSpecifier } from './resolve-relative-specifier';
|
|
117
|
+
import { storyPrefabsFinder } from './stories/prefabs-finder';
|
|
118
|
+
import {
|
|
119
|
+
contributedFinderModules,
|
|
120
|
+
documentContributionForKind,
|
|
121
|
+
refreshProjectToolContributions,
|
|
122
|
+
subscribeToolContributions,
|
|
123
|
+
} from './tool-loader';
|
|
124
|
+
|
|
125
|
+
/** The game's adapter module lives beside `vgai.project.json`, by contract. */
|
|
126
|
+
export const ADAPTER_MODULE_FILENAME = 'vgai.adapter.ts';
|
|
127
|
+
|
|
128
|
+
/** The resolved scene table — the adapter's declarations plus what its own
|
|
129
|
+
* finder selections found. */
|
|
130
|
+
export interface ResolvedDocumentTable {
|
|
131
|
+
readonly entries: readonly DocumentEntry[];
|
|
132
|
+
/** `null` = nothing settled a default, which stays a fact rather than a pick. */
|
|
133
|
+
readonly default: string | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* One observation slot as it travels over `/__editor/state` — the statically
|
|
138
|
+
* readable half of an {@link ObservationDeclaration}, and nothing else.
|
|
139
|
+
*
|
|
140
|
+
* `answer` is a CLOSURE the host evaluates against the mounted game, so it
|
|
141
|
+
* cannot be serialized and is deliberately not here; `answered` carries the one
|
|
142
|
+
* fact its presence states. That split is the two-state rule made visible on
|
|
143
|
+
* the wire: `answered: false` reads as "declared, nothing binds it yet", which
|
|
144
|
+
* is a different answer from the slot not appearing at all. The closures
|
|
145
|
+
* themselves stay host-side in `adapter-observation.ts`, where the mount reads
|
|
146
|
+
* them.
|
|
147
|
+
*/
|
|
148
|
+
export interface ProjectObservationFacet {
|
|
149
|
+
readonly id: string;
|
|
150
|
+
readonly kind: ObservationKind;
|
|
151
|
+
/** Whether the declaration binds an `answer`. */
|
|
152
|
+
readonly answered: boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface ProjectAdapterFacet {
|
|
156
|
+
/**
|
|
157
|
+
* WHOSE declaration is running — the one field that answers it, because
|
|
158
|
+
* "a module loaded" never did.
|
|
159
|
+
*
|
|
160
|
+
* - `project` — the project's own `vgai.adapter.ts` supplied the table.
|
|
161
|
+
* - `registry` — the HOST's in-tree ingest registry supplied it, matched on
|
|
162
|
+
* this project's ingest root id. The project did not ship this
|
|
163
|
+
* declaration; a reader must be able to see that without pattern-matching
|
|
164
|
+
* a path, which is why it is a value here and not an inference from
|
|
165
|
+
* {@link modulePath}.
|
|
166
|
+
* - `native` — the project declared none and got `nativeAdapter()`, the
|
|
167
|
+
* declared native default.
|
|
168
|
+
*/
|
|
169
|
+
readonly source: 'project' | 'registry' | 'native';
|
|
170
|
+
/**
|
|
171
|
+
* Where the loaded module came from, or `null` for the native default:
|
|
172
|
+
* project-relative for a project that owns its own file, and the REPO path
|
|
173
|
+
* (`packages/editor/src/ingest/games/<id>/vgai.adapter.ts`) for a registry
|
|
174
|
+
* binding, whose adapter is host-realm by the placement rule and therefore
|
|
175
|
+
* has no project-relative home to name.
|
|
176
|
+
*/
|
|
177
|
+
readonly modulePath: string | null;
|
|
178
|
+
readonly editor?: Omit<NonNullable<AdapterDefinition['editor']>, 'Layout'> & {
|
|
179
|
+
readonly layout?: string;
|
|
180
|
+
};
|
|
181
|
+
readonly regions: readonly AdapterRegion[];
|
|
182
|
+
readonly scenes: ResolvedDocumentTable;
|
|
183
|
+
/** The observation table, wire-shaped. See {@link ProjectObservationFacet}. */
|
|
184
|
+
readonly observation: readonly ProjectObservationFacet[];
|
|
185
|
+
/** Finder diagnostics — what could not be answered, and why. Never swallowed. */
|
|
186
|
+
readonly notes: readonly string[];
|
|
187
|
+
/** True while a declared finder has no registration yet — a contribution's
|
|
188
|
+
* finder before the contribution pass — so the table is NOT final. The
|
|
189
|
+
* chrome treats the document kinds as unknown until this clears, rather
|
|
190
|
+
* than concluding "no model" from a table the pass is about to fill. */
|
|
191
|
+
readonly documentsPending: boolean;
|
|
192
|
+
/**
|
|
193
|
+
* A load/parse failure — or a probe that could not answer — named. Non-null
|
|
194
|
+
* means the project's OWN adapter did not load, and what stood in is
|
|
195
|
+
* whatever {@link source} says: a registry binding, or the declared native
|
|
196
|
+
* default. Never swallowed, because the editor still opening on a broken
|
|
197
|
+
* `vgai.adapter.ts` is only acceptable while it SAYS the file is broken.
|
|
198
|
+
*/
|
|
199
|
+
readonly error: string | null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let _facet: ProjectAdapterFacet | null = null;
|
|
203
|
+
/** The parsed declaration behind {@link _facet}, kept for
|
|
204
|
+
* {@link projectAdapterDefinition}. `null` whenever no declaration FILE was
|
|
205
|
+
* parsed — see that function for why the native default is not stored here. */
|
|
206
|
+
let _definition: AdapterDefinition | null = null;
|
|
207
|
+
let _owner = '__unscoped__';
|
|
208
|
+
/** The load in flight, so a reader can wait for the answer instead of racing
|
|
209
|
+
* it. See {@link projectAdapterDefinition}. */
|
|
210
|
+
let _inFlight: Promise<void> | null = null;
|
|
211
|
+
let _refreshEpoch = 0;
|
|
212
|
+
const _listeners = new Set<() => void>();
|
|
213
|
+
|
|
214
|
+
/** The loaded adapter table, or `null` before the first load completes —
|
|
215
|
+
* "nobody looked yet" is deliberately distinct from "the project has none". */
|
|
216
|
+
export function projectAdapterFacet(): ProjectAdapterFacet | null {
|
|
217
|
+
return _owner === activeProjectKey() ? _facet : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* THE PARSED DECLARATION, for the one consumer that needs the whole thing:
|
|
222
|
+
* `binding-resolver.ts`, which hands it to `resolveRootBinding` so it lands on
|
|
223
|
+
* `binding.project.definition`.
|
|
224
|
+
*
|
|
225
|
+
* Two deliberate properties:
|
|
226
|
+
*
|
|
227
|
+
* - It **awaits the load in flight**. A resolve that read module state
|
|
228
|
+
* synchronously would answer `null` for a project whose adapter is still
|
|
229
|
+
* loading — a project-switch-then-Play race — and `null` on that binding
|
|
230
|
+
* means "this project declares no adapter", which would be a fabricated
|
|
231
|
+
* fact. Waiting is what makes the `null` honest.
|
|
232
|
+
* - The **declared native default is `null` here**, not `nativeAdapter()`.
|
|
233
|
+
* `binding.project.definition` answers "what did the PROJECT declare", so a
|
|
234
|
+
* project that shipped no declaration file must read as `null`. Which table
|
|
235
|
+
* actually stood in — project, registry, or native — is
|
|
236
|
+
* {@link ProjectAdapterFacet.source}'s job, and that is where a consumer
|
|
237
|
+
* asking the other question looks.
|
|
238
|
+
*/
|
|
239
|
+
export async function projectAdapterDefinition(): Promise<AdapterDefinition | null> {
|
|
240
|
+
if (_inFlight) await _inFlight;
|
|
241
|
+
return _owner === activeProjectKey() ? _definition : null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Initial layout waits for the adapter declaration, not deferred finder contributions. */
|
|
245
|
+
export function waitForProjectAdapter(): Promise<void> {
|
|
246
|
+
const ready = () => !getCurrentProject() || Boolean(projectAdapterFacet());
|
|
247
|
+
if (ready()) return Promise.resolve();
|
|
248
|
+
return new Promise<void>((resolve) => {
|
|
249
|
+
const stop = subscribeProjectAdapter(() => {
|
|
250
|
+
if (ready()) {
|
|
251
|
+
stop();
|
|
252
|
+
resolve();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function subscribeProjectAdapter(fn: () => void): () => void {
|
|
259
|
+
_listeners.add(fn);
|
|
260
|
+
return () => _listeners.delete(fn);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function publish(
|
|
264
|
+
next: ProjectAdapterFacet | null,
|
|
265
|
+
observation: readonly ObservationDeclaration[] = [],
|
|
266
|
+
input: AdapterDefinition['input'] = undefined,
|
|
267
|
+
definition: AdapterDefinition | null = null,
|
|
268
|
+
): void {
|
|
269
|
+
_owner = activeProjectKey();
|
|
270
|
+
_facet = next;
|
|
271
|
+
_definition = definition;
|
|
272
|
+
setAdapterEditorConfiguration(definition?.editor);
|
|
273
|
+
// The region table's OTHER reader, pushed rather than pulled: the capture and
|
|
274
|
+
// staleness sites ask "what is this project presented on" from inside the
|
|
275
|
+
// ingest mount and play-mode, and having them import this loader would drag
|
|
276
|
+
// its whole graph along. See `presentation-surface.ts`'s note — this stays the
|
|
277
|
+
// one place regions are decided.
|
|
278
|
+
setPresentationRegions(next?.regions ?? []);
|
|
279
|
+
// Same push, same reason, for the half of the observation table that cannot
|
|
280
|
+
// travel over the wire: the `answer` closures the ingest mount evaluates
|
|
281
|
+
// against the mounted game (`adapter-observation.ts`).
|
|
282
|
+
setAdapterObservations(observation);
|
|
283
|
+
setAdapterInputBinding(input ?? null);
|
|
284
|
+
for (const fn of _listeners) fn();
|
|
285
|
+
notifyProjectShapeChanged();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The wire half of one declaration — closures stripped, the fact of one kept. */
|
|
289
|
+
function observationFacet(declaration: ObservationDeclaration): ProjectObservationFacet {
|
|
290
|
+
return {
|
|
291
|
+
id: declaration.id,
|
|
292
|
+
kind: declaration.kind,
|
|
293
|
+
answered: declaration.answer !== undefined,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Test seam: drop the loaded table, both halves of it. */
|
|
298
|
+
export function __resetProjectAdapterForTest(): void {
|
|
299
|
+
_facet = null;
|
|
300
|
+
_definition = null;
|
|
301
|
+
_inFlight = null;
|
|
302
|
+
_refreshEpoch++;
|
|
303
|
+
setAdapterEditorConfiguration();
|
|
304
|
+
setAdapterObservations([]);
|
|
305
|
+
setAdapterInputBinding(null);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Import the project's own adapter module. Cache-busted like every other
|
|
310
|
+
* design-time project load, because a saved `vgai.adapter.ts` must be readable
|
|
311
|
+
* without restarting the editor.
|
|
312
|
+
*
|
|
313
|
+
* The url is built by `fsImportPath` (project-module-url.ts) and NOT by
|
|
314
|
+
* `projectEntryImportUrl`: a mount epoch exists to keep the roots of ONE mount
|
|
315
|
+
* on one module instance, and the adapter is not part of any mount — it is a
|
|
316
|
+
* binding table read WITHOUT booting the game. Taking an epoch here would
|
|
317
|
+
* inflate the mount-epoch census for a module no root shares.
|
|
318
|
+
*/
|
|
319
|
+
function editorConfigurationFacet(editor: NonNullable<AdapterDefinition['editor']>) {
|
|
320
|
+
const { Layout, ...configuration } = editor;
|
|
321
|
+
return {
|
|
322
|
+
...configuration,
|
|
323
|
+
...(Layout ? { layout: Layout.displayName ?? Layout.name ?? 'Project layout' } : {}),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function importAdapterModule(rootPath: string): Promise<unknown> {
|
|
328
|
+
// The server-less tier has no `/@fs/`. A storage-backed project's adapter
|
|
329
|
+
// loads through the same in-browser bundling door as every project module
|
|
330
|
+
// (fresh bundle per call — the cache-bust for free); a STAGED hosted
|
|
331
|
+
// example/ingest — reached only when the realm-correct probe found the
|
|
332
|
+
// file among its served bytes — bundles it from its own staged URL. Never
|
|
333
|
+
// the backend for those: it still holds some other project
|
|
334
|
+
// (`browserProjectStorageBacked`, the 2026-08-27 leak).
|
|
335
|
+
const url = `${fsImportPath(rootPath, ADAPTER_MODULE_FILENAME)}?t=${Date.now()}`;
|
|
336
|
+
return (await import(/* @vite-ignore */ url)) as unknown;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Map a component to its region by SURFACE. Exact, not heuristic: the manifest
|
|
341
|
+
* schema enforces at most ONE world root per medium (ARCHITECTURE-CORE
|
|
342
|
+
* §Roots), so a surface names at most one region.
|
|
343
|
+
*/
|
|
344
|
+
function regionForSurface(regions: readonly AdapterRegion[], surface: string): string | null {
|
|
345
|
+
return regions.find((region) => region.surface === surface)?.id ?? null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Resolve one of an entrypoint's relative import specifiers against the paths
|
|
350
|
+
* the project's own component index reports. Extension/index resolution is a
|
|
351
|
+
* filesystem question and the finders are pure, so the host answers it here —
|
|
352
|
+
* from a real index, never by guessing an extension onto a path.
|
|
353
|
+
*/
|
|
354
|
+
function makeResolveModule(
|
|
355
|
+
components: readonly { readonly path: string }[],
|
|
356
|
+
): (specifier: string, fromPath: string) => string | null {
|
|
357
|
+
const paths = new Set(components.map((component) => component.path));
|
|
358
|
+
return (specifier, fromPath) => resolveRelativeSpecifier(specifier, fromPath, paths);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* The ingest game this project IS, or `null` — the key the in-tree adapter
|
|
363
|
+
* registry is looked up by.
|
|
364
|
+
*
|
|
365
|
+
* It is the manifest's own single `{ ingest }` root id, which is a DECLARATION
|
|
366
|
+
* read off the project (never a folder-name guess), and it is the same id
|
|
367
|
+
* `ingest/mount-ingest-root.ts` mounts by. Two ingest roots is not this
|
|
368
|
+
* module's error to report — that route already fails loudly on it
|
|
369
|
+
* (`reportTooManyIngestRoots`) — so here it simply names no game rather than
|
|
370
|
+
* picking one.
|
|
371
|
+
*/
|
|
372
|
+
function ingestGameIdOf(manifest: ResolvedGameManifest): string | null {
|
|
373
|
+
const declared = ingestRoots(manifest);
|
|
374
|
+
return declared.length === 1 ? declared[0]!.id : null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** What a table with no finder selections is resolved against. */
|
|
378
|
+
const NO_FINDER_INPUT: FinderInput = { entrypoints: [], components: [] };
|
|
379
|
+
|
|
380
|
+
async function gatherFinderInput(
|
|
381
|
+
manifest: ResolvedGameManifest,
|
|
382
|
+
regions: readonly AdapterRegion[],
|
|
383
|
+
definition: AdapterDefinition,
|
|
384
|
+
): Promise<FinderInput> {
|
|
385
|
+
const entrypoints: EntrypointSource[] = [];
|
|
386
|
+
for (const root of declaredRoots(manifest)) {
|
|
387
|
+
if (!root.entry) continue;
|
|
388
|
+
entrypoints.push({
|
|
389
|
+
regionId: root.id,
|
|
390
|
+
path: root.entry,
|
|
391
|
+
source: await readProjectTextFile(root.entry),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let components: ProjectComponentRef[] = [];
|
|
396
|
+
const listing = await listProjectComponents().catch(
|
|
397
|
+
(error: unknown): Awaited<ReturnType<typeof listProjectComponents>> => ({
|
|
398
|
+
ok: false,
|
|
399
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
if (listing.ok) {
|
|
403
|
+
components = listing.entries.map((component) => ({
|
|
404
|
+
name: component.name,
|
|
405
|
+
path: component.path,
|
|
406
|
+
region: regionForSurface(regions, component.surface),
|
|
407
|
+
}));
|
|
408
|
+
} else {
|
|
409
|
+
// No component index (the route is unavailable): prefabs
|
|
410
|
+
// still resolve from their stories, they just carry no source path. That
|
|
411
|
+
// degrade is fine; being silent about it was not — a prefab missing its
|
|
412
|
+
// source path looked like the prefab, not like the index.
|
|
413
|
+
editorConsole.warn(
|
|
414
|
+
`Component index unavailable, so prefabs resolve without source paths: ${listing.reason}`,
|
|
415
|
+
'project',
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Every selection's `include` globs, read once: a contributed finder sees
|
|
420
|
+
// the project through these sources and nothing else.
|
|
421
|
+
const includes = new Set<string>();
|
|
422
|
+
for (const selection of definition.documents.find ?? []) {
|
|
423
|
+
for (const glob of (selection as { include?: readonly string[] }).include ?? [])
|
|
424
|
+
includes.add(glob);
|
|
425
|
+
}
|
|
426
|
+
const sources: ProjectSourceFile[] = [];
|
|
427
|
+
const files: string[] = [];
|
|
428
|
+
if (includes.size > 0) {
|
|
429
|
+
// Newest first: the table keeps this order, so "the one being worked on"
|
|
430
|
+
// is the first candidate when a fresh checkout has to pick a document.
|
|
431
|
+
const paths = await listProjectSourceFiles([...includes], { order: 'mtime' }).catch(
|
|
432
|
+
() => [] as string[],
|
|
433
|
+
);
|
|
434
|
+
for (const path of paths) {
|
|
435
|
+
files.push(path);
|
|
436
|
+
// A BINARY document is listed, never decoded. `@volter/editor-blender`'s finder
|
|
437
|
+
// includes `.blend` files, and reading one as text would hand a finder a
|
|
438
|
+
// string of replacement characters and call it a source. A NUL byte is
|
|
439
|
+
// the test because it is the one thing no text source contains, and it
|
|
440
|
+
// asks the bytes rather than a list of extensions the host would then
|
|
441
|
+
// own. COST, named rather than hidden: the bytes are fetched and decoded
|
|
442
|
+
// before the test can run, so a binary under a finder's globs is read
|
|
443
|
+
// once per adapter resolve. A selection that declared it wants paths
|
|
444
|
+
// only would close it; no shipped finder needs that yet.
|
|
445
|
+
const source = await readProjectTextFile(path);
|
|
446
|
+
if (source !== null && !source.includes('\0')) sources.push({ path, source });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
entrypoints,
|
|
451
|
+
components,
|
|
452
|
+
sources,
|
|
453
|
+
files,
|
|
454
|
+
resolveModule: makeResolveModule(components),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export type { EntrypointSource, FinderInput, ProjectComponentRef };
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Assemble finder input from parts the caller already gathered. The live
|
|
462
|
+
* loader reads those parts off the editor session; the headless portfolio
|
|
463
|
+
* run reads them off disk. Both hand the SAME shape to
|
|
464
|
+
* {@link resolveDocumentTable}, which is the one fold.
|
|
465
|
+
*/
|
|
466
|
+
export function buildFinderInput(parts: {
|
|
467
|
+
readonly entrypoints: readonly EntrypointSource[];
|
|
468
|
+
readonly components?: readonly ProjectComponentRef[];
|
|
469
|
+
}): FinderInput {
|
|
470
|
+
const components = parts.components ?? [];
|
|
471
|
+
return {
|
|
472
|
+
entrypoints: parts.entrypoints,
|
|
473
|
+
components,
|
|
474
|
+
resolveModule: makeResolveModule(components),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Run the adapter's own finder selections and fold them into one table. */
|
|
479
|
+
export function resolveDocumentTable(
|
|
480
|
+
definition: AdapterDefinition,
|
|
481
|
+
input: FinderInput,
|
|
482
|
+
): {
|
|
483
|
+
table: ResolvedDocumentTable;
|
|
484
|
+
notes: string[];
|
|
485
|
+
/** Selections whose finder is not registered (yet). */
|
|
486
|
+
unresolved: number;
|
|
487
|
+
} {
|
|
488
|
+
const entries: DocumentEntry[] = [...(definition.documents.entries ?? [])];
|
|
489
|
+
const notes: string[] = [];
|
|
490
|
+
const settled: string[] = [];
|
|
491
|
+
let unresolved = 0;
|
|
492
|
+
for (const selection of definition.documents.find ?? []) {
|
|
493
|
+
let result: FinderResult;
|
|
494
|
+
try {
|
|
495
|
+
result = runFinderSelection(selection, input);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
// A finder a CONTRIBUTION registers is not there until the contribution
|
|
498
|
+
// pass has run, which follows the first adapter load; the table is
|
|
499
|
+
// resolved again when it does (`subscribeToolContributions` below). A
|
|
500
|
+
// selection that stays unresolved is a standing note, never a throw.
|
|
501
|
+
notes.push(
|
|
502
|
+
`finder ${JSON.stringify(selection.finder)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
503
|
+
);
|
|
504
|
+
unresolved += 1;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
entries.push(...result.entries);
|
|
508
|
+
notes.push(...result.notes);
|
|
509
|
+
if (result.default !== undefined) settled.push(result.default);
|
|
510
|
+
}
|
|
511
|
+
// THE SESSION'S OWN MODEL DOCUMENT (ARCHITECTURE-CORE §The project model,
|
|
512
|
+
// "A model is Blender data"): `@volter/editor-blender`'s Model document opens a
|
|
513
|
+
// project's `.blend` files, which its `modelsFromBlendFiles` finder lists
|
|
514
|
+
// above. A project that declares the package but holds no `.blend` of its
|
|
515
|
+
// own still needs ONE — it is what `blender-start` presents into and what a
|
|
516
|
+
// first bpy call models in, saved to the session's default
|
|
517
|
+
// `models/model.blend` — so the standing `blender:runtime` entry stands
|
|
518
|
+
// exactly there: where that package's document is registered AND the table
|
|
519
|
+
// found no model of the project's own. Never as an entry no editor can
|
|
520
|
+
// open, and never as a second Model beside a real one.
|
|
521
|
+
if (
|
|
522
|
+
documentContributionForKind('model') !== undefined &&
|
|
523
|
+
!entries.some((entry) => entry.kind === 'model')
|
|
524
|
+
) {
|
|
525
|
+
entries.push({
|
|
526
|
+
id: 'blender:runtime',
|
|
527
|
+
label: 'Model',
|
|
528
|
+
kind: 'model',
|
|
529
|
+
region: null,
|
|
530
|
+
authorable: true,
|
|
531
|
+
reach: { kind: 'root-mount' },
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
// The adapter's own declaration wins; otherwise a default stands only when
|
|
535
|
+
// exactly one finder settled one. Two candidates is a CHOICE, and the host
|
|
536
|
+
// does not make choices the game's author can state.
|
|
537
|
+
const fallback = settled.length === 1 ? settled[0]! : null;
|
|
538
|
+
return {
|
|
539
|
+
table: { entries, default: definition.documents.default ?? fallback },
|
|
540
|
+
notes,
|
|
541
|
+
unresolved,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Which adapter this project resolved to, and the provenance of the choice. */
|
|
546
|
+
export interface ResolvedAdapterChoice {
|
|
547
|
+
readonly definition: AdapterDefinition;
|
|
548
|
+
readonly source: 'project' | 'registry' | 'native';
|
|
549
|
+
readonly modulePath: string | null;
|
|
550
|
+
readonly error: string | null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** The project's own `vgai.adapter.ts` probe, as data — the live loader and
|
|
554
|
+
* the headless run answer this the same way, from different IO. */
|
|
555
|
+
export type ProjectAdapterProbe =
|
|
556
|
+
| { readonly kind: 'present'; readonly module: unknown }
|
|
557
|
+
| { readonly kind: 'broken'; readonly error: string }
|
|
558
|
+
| { readonly kind: 'absent' }
|
|
559
|
+
| { readonly kind: 'unreachable'; readonly error: string };
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* AN ADAPTER DEFINITION IS SOURCED, NOT GLOBBED — the door a LANE registers
|
|
563
|
+
* its host-realm declarations through, in the family of
|
|
564
|
+
* `workspace-document-restore.ts` (a kind owns its persisted state),
|
|
565
|
+
* `document-open-registry.ts` (a kind owns how it opens),
|
|
566
|
+
* `chrome-slot-registry.ts` (the host owns the place, a package owns what sits
|
|
567
|
+
* there) and `content-entry-source-registry.ts` (the host owns the Content
|
|
568
|
+
* scope, a package owns why a component is content).
|
|
569
|
+
*
|
|
570
|
+
* IT LIVES IN THIS FILE, not beside those, and the reason is the meter. Its
|
|
571
|
+
* only consumer is {@link resolveAdapterDefinition} below — the one place the
|
|
572
|
+
* three-way precedence lives — so a separate module is a host file traded for
|
|
573
|
+
* a host file: MEASURED, `main.tsx` stayed at 494 with the door split out and
|
|
574
|
+
* dropped to 493 with it here. A registry whose whole readership is one
|
|
575
|
+
* function belongs in that function's module.
|
|
576
|
+
*
|
|
577
|
+
* WHAT IT REPLACED. This module used to import `ingest/registry.ts` directly
|
|
578
|
+
* for branch 2, so the host's boot closure carried the ingest lane's
|
|
579
|
+
* `import.meta.glob` over `src/ingest/games/*` — every repo-vendored game's
|
|
580
|
+
* manifest and adapter module in every editor boot, a `models` build that
|
|
581
|
+
* mounts no unmodified game included. The lane that OWNS those declarations is
|
|
582
|
+
* `@vgai/game`, which already imports that registry from three of its own
|
|
583
|
+
* ingest modules; it registers here from `contributions/ingest.service.ts`.
|
|
584
|
+
*
|
|
585
|
+
* NOTHING REGISTERED IS A REAL ANSWER, AND IT IS A LOUD ONE — see
|
|
586
|
+
* {@link RegistryAdapterProbe}'s `unanswered`.
|
|
587
|
+
*/
|
|
588
|
+
export interface AdapterDefinitionSource {
|
|
589
|
+
/** The source's id, in its own vocabulary. Part of every teaching message. */
|
|
590
|
+
readonly id: string;
|
|
591
|
+
/** Which module registered it. Re-registering the same owner+id REPLACES,
|
|
592
|
+
* so an HMR re-evaluation leaves one source, not two. */
|
|
593
|
+
readonly owner: string;
|
|
594
|
+
/** Ascending; ties keep registration order. */
|
|
595
|
+
readonly order?: number;
|
|
596
|
+
/** The repo path of the declaration this source holds for `rootId`, or
|
|
597
|
+
* `null` when it holds none. Never throws — a source that cannot answer
|
|
598
|
+
* says so by returning `null`. */
|
|
599
|
+
readonly modulePathFor: (rootId: string) => string | null;
|
|
600
|
+
/** Import that declaration. Only called when `modulePathFor` named a file;
|
|
601
|
+
* a module that exists and throws is LEFT to throw, so the loader can name
|
|
602
|
+
* the failure rather than swallow it. */
|
|
603
|
+
readonly importModule: (rootId: string) => Promise<unknown>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const _adapterSources: AdapterDefinitionSource[] = [];
|
|
607
|
+
|
|
608
|
+
/** Install a source. Returns the teardown. */
|
|
609
|
+
export function registerAdapterDefinitionSource(source: AdapterDefinitionSource): () => void {
|
|
610
|
+
const stale = _adapterSources.findIndex(
|
|
611
|
+
(item) => item.owner === source.owner && item.id === source.id,
|
|
612
|
+
);
|
|
613
|
+
if (stale >= 0) _adapterSources.splice(stale, 1);
|
|
614
|
+
_adapterSources.push(source);
|
|
615
|
+
_adapterSources.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
616
|
+
return () => {
|
|
617
|
+
const at = _adapterSources.indexOf(source);
|
|
618
|
+
if (at >= 0) _adapterSources.splice(at, 1);
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** The first source that claims this root id, and the file it claims it
|
|
623
|
+
* through — `null` when none does, which is NOT "this project has no
|
|
624
|
+
* adapter" (see {@link RegistryAdapterProbe}). */
|
|
625
|
+
function adapterDefinitionBinding(
|
|
626
|
+
rootId: string,
|
|
627
|
+
): { readonly source: AdapterDefinitionSource; readonly modulePath: string } | null {
|
|
628
|
+
for (const source of _adapterSources) {
|
|
629
|
+
const modulePath = source.modulePathFor(rootId);
|
|
630
|
+
if (modulePath !== null) return { source, modulePath };
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* The SOURCED-declaration probe, as data (`adapter-definition-source-registry.ts`).
|
|
637
|
+
*
|
|
638
|
+
* `unanswered` is this half's `unreachable`, and it exists for the same reason
|
|
639
|
+
* the project half's does: a manifest that DECLARES an ingest root has stated
|
|
640
|
+
* which lane owns its declaration, so "no registered source holds one" is an
|
|
641
|
+
* unanswered question about the BUILD, never an absence in the project. It is
|
|
642
|
+
* the anti-shim rule's standing warning — the native default still stands in so
|
|
643
|
+
* the editor opens, and the failure is named rather than mounted quietly.
|
|
644
|
+
*
|
|
645
|
+
* `absent` remains the honest answer of the DISK half (`coverage/
|
|
646
|
+
* portfolio-resolve.ts`), which looks at the filesystem and can genuinely say
|
|
647
|
+
* the file is not there.
|
|
648
|
+
*/
|
|
649
|
+
export type RegistryAdapterProbe =
|
|
650
|
+
| { readonly kind: 'present'; readonly module: unknown; readonly path: string }
|
|
651
|
+
| { readonly kind: 'broken'; readonly path: string; readonly error: string }
|
|
652
|
+
| { readonly kind: 'unanswered'; readonly rootId: string; readonly sources: number }
|
|
653
|
+
| { readonly kind: 'absent' };
|
|
654
|
+
|
|
655
|
+
function adapterModuleDefault(module_: unknown): unknown {
|
|
656
|
+
if (module_ !== null && typeof module_ === 'object' && 'default' in module_) {
|
|
657
|
+
return (module_ as { default: unknown }).default;
|
|
658
|
+
}
|
|
659
|
+
return module_;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* THE THREE-WAY PRECEDENCE as a pure function over already-loaded modules.
|
|
664
|
+
* {@link resolveAdapterDefinition} is the live IO half; the headless portfolio
|
|
665
|
+
* run is the disk half. Neither may invent a fourth branch.
|
|
666
|
+
*/
|
|
667
|
+
export function selectAdapterDefinition(
|
|
668
|
+
project: ProjectAdapterProbe,
|
|
669
|
+
registry: RegistryAdapterProbe,
|
|
670
|
+
): ResolvedAdapterChoice {
|
|
671
|
+
if (project.kind === 'present') {
|
|
672
|
+
try {
|
|
673
|
+
return {
|
|
674
|
+
definition: parseAdapterDefinition(adapterModuleDefault(project.module)),
|
|
675
|
+
source: 'project',
|
|
676
|
+
modulePath: ADAPTER_MODULE_FILENAME,
|
|
677
|
+
error: null,
|
|
678
|
+
};
|
|
679
|
+
} catch (err) {
|
|
680
|
+
return {
|
|
681
|
+
definition: nativeAdapter(),
|
|
682
|
+
source: 'native',
|
|
683
|
+
modulePath: null,
|
|
684
|
+
error: `${ADAPTER_MODULE_FILENAME} did not load — ${describe(err)}`,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (project.kind === 'broken') {
|
|
689
|
+
return {
|
|
690
|
+
definition: nativeAdapter(),
|
|
691
|
+
source: 'native',
|
|
692
|
+
modulePath: null,
|
|
693
|
+
error: `${ADAPTER_MODULE_FILENAME} did not load — ${project.error}`,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const unknown = project.kind === 'unreachable' ? project.error : null;
|
|
698
|
+
|
|
699
|
+
if (registry.kind === 'present') {
|
|
700
|
+
try {
|
|
701
|
+
return {
|
|
702
|
+
definition: parseAdapterDefinition(adapterModuleDefault(registry.module)),
|
|
703
|
+
source: 'registry',
|
|
704
|
+
modulePath: registry.path,
|
|
705
|
+
error: unknown,
|
|
706
|
+
};
|
|
707
|
+
} catch (err) {
|
|
708
|
+
return {
|
|
709
|
+
definition: nativeAdapter(),
|
|
710
|
+
source: 'native',
|
|
711
|
+
modulePath: null,
|
|
712
|
+
error: `${registry.path} did not load — ${describe(err)}`,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (registry.kind === 'broken') {
|
|
717
|
+
return {
|
|
718
|
+
definition: nativeAdapter(),
|
|
719
|
+
source: 'native',
|
|
720
|
+
modulePath: null,
|
|
721
|
+
error: `${registry.path} did not load — ${registry.error}`,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (registry.kind === 'unanswered') {
|
|
725
|
+
return {
|
|
726
|
+
definition: nativeAdapter(),
|
|
727
|
+
source: 'native',
|
|
728
|
+
modulePath: null,
|
|
729
|
+
error:
|
|
730
|
+
`vgai.project.json declares the ingest root \`${registry.rootId}\`, and no registered ` +
|
|
731
|
+
`adapter-definition source holds a declaration for it ` +
|
|
732
|
+
`(${registry.sources} source${registry.sources === 1 ? '' : 's'} registered). The lane ` +
|
|
733
|
+
`that owns a vendored game's declaration is \`@vgai/game\`, which registers through ` +
|
|
734
|
+
`its \`ingest.service.ts\` contribution — so either this product does not compose that ` +
|
|
735
|
+
`package, ` +
|
|
736
|
+
`or its contribution pass did not load it. The declared native default is standing in, ` +
|
|
737
|
+
`which means this game's own scene table is NOT what the editor is showing.`,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return { definition: nativeAdapter(), source: 'native', modulePath: null, error: unknown };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Regions + scene table + observation from a chosen definition. The live
|
|
746
|
+
* loader and the headless run both land here — one fold, two IO fronts.
|
|
747
|
+
*/
|
|
748
|
+
export function projectAdapterTableFrom(
|
|
749
|
+
manifest: ResolvedGameManifest,
|
|
750
|
+
choice: ResolvedAdapterChoice,
|
|
751
|
+
finderInput: FinderInput,
|
|
752
|
+
): ProjectAdapterFacet {
|
|
753
|
+
const { definition, source, modulePath, error } = choice;
|
|
754
|
+
const regions =
|
|
755
|
+
definition.regions === 'manifest-roots'
|
|
756
|
+
? regionsFromManifestRoots(declaredRoots(manifest), definition.regionIncludes)
|
|
757
|
+
: [...definition.regions];
|
|
758
|
+
const unmatchedRegions = unmatchedRegionIncludeIds(
|
|
759
|
+
declaredRoots(manifest),
|
|
760
|
+
definition.regionIncludes,
|
|
761
|
+
);
|
|
762
|
+
const { table, notes, unresolved } = resolveDocumentTable(definition, finderInput);
|
|
763
|
+
return {
|
|
764
|
+
source,
|
|
765
|
+
modulePath,
|
|
766
|
+
regions,
|
|
767
|
+
scenes: table,
|
|
768
|
+
documentsPending: unresolved > 0,
|
|
769
|
+
...(definition.editor ? { editor: editorConfigurationFacet(definition.editor) } : {}),
|
|
770
|
+
observation: definition.observation.map(observationFacet),
|
|
771
|
+
notes:
|
|
772
|
+
unmatchedRegions.length > 0
|
|
773
|
+
? [
|
|
774
|
+
...notes,
|
|
775
|
+
`adapter: regionIncludes names ${unmatchedRegions.map((id) => `\`${id}\``).join(', ')}, ` +
|
|
776
|
+
`which no root in vgai.project.json declares (roots: ${declaredRoots(manifest)
|
|
777
|
+
.map((root) => `\`${root.id}\``)
|
|
778
|
+
.join(', ')}) — those globs were merged onto nothing.`,
|
|
779
|
+
]
|
|
780
|
+
: notes,
|
|
781
|
+
error,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* PICK THE ADAPTER — the three-way decision this module's header states, and
|
|
787
|
+
* the ONLY place the precedence lives.
|
|
788
|
+
*
|
|
789
|
+
* The project's OWN file is probed FIRST and outranks the registry, whatever id
|
|
790
|
+
* its ingest root declares.
|
|
791
|
+
*
|
|
792
|
+
* `probeProjectFile`, not `projectFileExists`, and that is the correctness of
|
|
793
|
+
* this function rather than a style preference: an existence probe that reads
|
|
794
|
+
* `res.ok` is a MEASURED CONSTANT TRUE here, because both shipped surfaces
|
|
795
|
+
* answer a missing project-root path with the editor's own `index.html` at 200
|
|
796
|
+
* (the dev server's SPA fallback; `docs/DEPLOY.md` §Surface 3 for hosted).
|
|
797
|
+
* Ordering a can't-say-no probe ahead of the registry made the registry branch
|
|
798
|
+
* unreachable — every vendored game lost its scene table to a fabricated
|
|
799
|
+
* "vgai.adapter.ts did not load". The probe now discriminates by CONTENT (an
|
|
800
|
+
* HTML document is the fallback, not the file) and reports "could not tell" as
|
|
801
|
+
* an answer of its own.
|
|
802
|
+
*
|
|
803
|
+
* Never throws: every failure comes back as a named `error` beside a table that
|
|
804
|
+
* still stands up, because an editor that dies on a project's config file
|
|
805
|
+
* cannot be used to fix that file.
|
|
806
|
+
*/
|
|
807
|
+
async function resolveAdapterDefinition(
|
|
808
|
+
project: ActiveProject,
|
|
809
|
+
manifest: ResolvedGameManifest,
|
|
810
|
+
): Promise<ResolvedAdapterChoice> {
|
|
811
|
+
const ingestGameId = ingestGameIdOf(manifest);
|
|
812
|
+
const presence = await probeProjectFile(ADAPTER_MODULE_FILENAME);
|
|
813
|
+
|
|
814
|
+
if (presence === 'present') {
|
|
815
|
+
try {
|
|
816
|
+
const module_ = (await importAdapterModule(project.rootPath)) as { default?: unknown };
|
|
817
|
+
return selectAdapterDefinition({ kind: 'present', module: module_ }, { kind: 'absent' });
|
|
818
|
+
} catch (err) {
|
|
819
|
+
// NO fallback to the registry. The file is really there and it is really
|
|
820
|
+
// broken, and quietly running the host's table for a game whose author is
|
|
821
|
+
// staring at their own syntax error is exactly the silence the
|
|
822
|
+
// project-first rule exists to remove. The failure is named and the
|
|
823
|
+
// declared native default stands in, so the editor still opens.
|
|
824
|
+
return selectAdapterDefinition({ kind: 'broken', error: describe(err) }, { kind: 'absent' });
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// `unreachable` is not an absence — it is an unanswered question, and saying
|
|
829
|
+
// so is the difference between "this project has no adapter" and "we could
|
|
830
|
+
// not find out". A table is published either way, because the editor has to
|
|
831
|
+
// open; which one stood in is what `source` says.
|
|
832
|
+
const projectProbe: ProjectAdapterProbe =
|
|
833
|
+
presence === 'unreachable'
|
|
834
|
+
? {
|
|
835
|
+
kind: 'unreachable',
|
|
836
|
+
error:
|
|
837
|
+
`${ADAPTER_MODULE_FILENAME} could not be probed — the project's file routes did not ` +
|
|
838
|
+
`answer, so whether this project owns an adapter is UNKNOWN.`,
|
|
839
|
+
}
|
|
840
|
+
: { kind: 'absent' };
|
|
841
|
+
|
|
842
|
+
if (ingestGameId === null) return selectAdapterDefinition(projectProbe, { kind: 'absent' });
|
|
843
|
+
|
|
844
|
+
// THE MANIFEST ALREADY KNOWS THE LANE, so the host is not guessing. This
|
|
845
|
+
// project declares an ingest root and shipped no adapter of its own, which
|
|
846
|
+
// means its declaration lives in a SOURCE a package registers
|
|
847
|
+
// ({@link registerAdapterDefinitionSource}) — and contributions load behind
|
|
848
|
+
// the opening viewport frame (`project-tool-discovery.ts`), while this runs
|
|
849
|
+
// at editor init (`startProjectAdapterLoad`). A document whose owner has not
|
|
850
|
+
// loaded cannot resolve; saying so by WAITING is honest, where mounting the
|
|
851
|
+
// native default and re-mounting when the registration lands is a silent
|
|
852
|
+
// degrade followed by a correction (owner decision, WORK.md §The open-source
|
|
853
|
+
// launch, "THE GAP IS 2"). The wait is paid ONLY here: a project with no
|
|
854
|
+
// ingest root never reaches this line, and one whose source is already
|
|
855
|
+
// registered (any resolve after the first) short-circuits below.
|
|
856
|
+
let binding = adapterDefinitionBinding(ingestGameId);
|
|
857
|
+
if (binding === null) {
|
|
858
|
+
await waitForContributedAdapterSources(ingestGameId);
|
|
859
|
+
binding = adapterDefinitionBinding(ingestGameId);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (binding === null) {
|
|
863
|
+
const unanswered = selectAdapterDefinition(projectProbe, {
|
|
864
|
+
kind: 'unanswered',
|
|
865
|
+
rootId: ingestGameId,
|
|
866
|
+
sources: _adapterSources.length,
|
|
867
|
+
});
|
|
868
|
+
// THE FACET IS NOT A DOOR THE USER READS. `error` reaches
|
|
869
|
+
// `/__editor/state` and `vgai status` prints it, but `vgai console` stayed
|
|
870
|
+
// SILENT while the editor showed a vendored game the native default's
|
|
871
|
+
// scene table — measured live, 2026-09-19, by removing the wait above and
|
|
872
|
+
// reading both doors. "Unresolved editor console is remaining work" is the
|
|
873
|
+
// contract every `vgai` verb enforces, so a standing warning that never
|
|
874
|
+
// enters that ledger is not standing. Warning weight, not error: the
|
|
875
|
+
// editor opens and is usable, and what is wrong is the BUILD's lane list.
|
|
876
|
+
if (unanswered.error) editorConsole.warn(unanswered.error, 'ingest');
|
|
877
|
+
return unanswered;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
const module_ = (await binding.source.importModule(ingestGameId)) as { default?: unknown };
|
|
882
|
+
return selectAdapterDefinition(projectProbe, {
|
|
883
|
+
kind: 'present',
|
|
884
|
+
module: module_,
|
|
885
|
+
path: binding.modulePath,
|
|
886
|
+
});
|
|
887
|
+
} catch (err) {
|
|
888
|
+
return selectAdapterDefinition(projectProbe, {
|
|
889
|
+
kind: 'broken',
|
|
890
|
+
path: binding.modulePath,
|
|
891
|
+
error: describe(err),
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* THE WAIT, AND ITS NARRATION — one contribution pass per project open.
|
|
898
|
+
*
|
|
899
|
+
* `refreshProjectToolContributions` resolves only once the NEWEST pass has
|
|
900
|
+
* installed (its own docblock, and the four callers that already treat it that
|
|
901
|
+
* way — `project-tool-discovery.ts`, `components/tool-documents.tsx`,
|
|
902
|
+
* `workspace-state-persistence.ts`, `editor-view-presentation.ts`). It is a
|
|
903
|
+
* known startup cost, not a hung fetch: the workspace restore takes the same
|
|
904
|
+
* await for the same reason and records it at ~9s on a cold `full` scaffold.
|
|
905
|
+
*
|
|
906
|
+
* LATCHED PER PROJECT, and that is correctness rather than thrift. The pass's
|
|
907
|
+
* completion fires `subscribeToolContributions`, whose hook below re-resolves
|
|
908
|
+
* the table when the FINDER set changed — so an unlatched await would start a
|
|
909
|
+
* fresh pass inside every re-resolve it caused, and the two would drive each
|
|
910
|
+
* other forever. One pass per project open; every later resolve reads the
|
|
911
|
+
* settled promise.
|
|
912
|
+
*
|
|
913
|
+
* AND IT SAYS SO ON SCREEN. `components/ProjectLayout.tsx` already holds the
|
|
914
|
+
* boot behind `waitForProjectAdapter()` with a `role="status"` line; while this
|
|
915
|
+
* promise is pending that line NAMES the mechanism instead of reading "Loading
|
|
916
|
+
* project layout…" for nine silent seconds. An action whose success can be
|
|
917
|
+
* invisible must narrate itself.
|
|
918
|
+
*/
|
|
919
|
+
let _sourceWaitOwner: string | null = null;
|
|
920
|
+
let _sourceWait: Promise<void> | null = null;
|
|
921
|
+
let _waitNarration: string | null = null;
|
|
922
|
+
|
|
923
|
+
/** What the adapter load is waiting for, for the boot's own status line —
|
|
924
|
+
* `null` whenever it is not waiting on anything a user should be told about. */
|
|
925
|
+
export function projectAdapterWaitNarration(): string | null {
|
|
926
|
+
return _waitNarration;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function waitForContributedAdapterSources(rootId: string): Promise<void> {
|
|
930
|
+
const owner = activeProjectKey();
|
|
931
|
+
if (_sourceWaitOwner !== owner) {
|
|
932
|
+
_sourceWaitOwner = owner;
|
|
933
|
+
_sourceWait = null;
|
|
934
|
+
}
|
|
935
|
+
if (!_sourceWait) {
|
|
936
|
+
_waitNarration =
|
|
937
|
+
`Loading the ingest lane — this project declares the unmodified-game root ` +
|
|
938
|
+
`“${rootId}”, whose adapter declaration a package supplies.`;
|
|
939
|
+
for (const fn of _listeners) fn();
|
|
940
|
+
_sourceWait = refreshProjectToolContributions()
|
|
941
|
+
.catch(() => {})
|
|
942
|
+
.finally(() => {
|
|
943
|
+
_waitNarration = null;
|
|
944
|
+
for (const fn of _listeners) fn();
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
return _sourceWait;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Load the open project's adapter and publish the resolved table.
|
|
952
|
+
*
|
|
953
|
+
* Never throws, for the reason {@link resolveAdapterDefinition} records.
|
|
954
|
+
*/
|
|
955
|
+
/** The project's contributed finders, registered into this host's registry
|
|
956
|
+
* before the table resolves (ARCHITECTURE-CORE §The project model). A
|
|
957
|
+
* module that is not a finder is reported by path, never silently skipped. */
|
|
958
|
+
let unregisterContributedFinders: Array<() => void> = [];
|
|
959
|
+
let registeredFinderSignature = '';
|
|
960
|
+
function registerProjectFinders(): void {
|
|
961
|
+
const modules = contributedFinderModules();
|
|
962
|
+
const signature = modules.map((m) => m.entryPath).join('|');
|
|
963
|
+
if (
|
|
964
|
+
signature === registeredFinderSignature &&
|
|
965
|
+
modules.every((m) => registeredModules.has(m.module as object))
|
|
966
|
+
)
|
|
967
|
+
return;
|
|
968
|
+
for (const off of unregisterContributedFinders) off();
|
|
969
|
+
unregisterContributedFinders = [];
|
|
970
|
+
registeredModules = new WeakSet();
|
|
971
|
+
for (const { entryPath, module } of modules) {
|
|
972
|
+
try {
|
|
973
|
+
unregisterContributedFinders.push(
|
|
974
|
+
registerContributedFinder((module as { finder?: unknown } | null)?.finder, entryPath),
|
|
975
|
+
);
|
|
976
|
+
registeredModules.add(module as object);
|
|
977
|
+
} catch (error) {
|
|
978
|
+
editorConsole.error(error instanceof Error ? error.message : String(error), 'project');
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
registeredFinderSignature = signature;
|
|
982
|
+
}
|
|
983
|
+
let registeredModules = new WeakSet<object>();
|
|
984
|
+
|
|
985
|
+
// THE KIT'S OWN FINDER, registered once. `prefabsFromStories` is portable
|
|
986
|
+
// CSF's — the kit's design-time state (ARCHITECTURE-CORE §The target shape) —
|
|
987
|
+
// and it reads the story registry itself rather than taking a `FinderInput`
|
|
988
|
+
// field, which is what keeps the finder contract free of one lane's data.
|
|
989
|
+
// This file is the ONE sanctioned importer of the engine's finder namespace
|
|
990
|
+
// (`@editor/finders`'s header states the rule), so the registration is
|
|
991
|
+
// here rather than beside the rest of the lane in `stories/story-lane.ts`.
|
|
992
|
+
registerContributedFinder(storyPrefabsFinder, 'packages/editor/src/stories/prefabs-finder.ts');
|
|
993
|
+
|
|
994
|
+
// A contribution pass that changed the finder set re-resolves the table.
|
|
995
|
+
subscribeToolContributions(() => {
|
|
996
|
+
const modules = contributedFinderModules();
|
|
997
|
+
const changed =
|
|
998
|
+
modules.map((m) => m.entryPath).join('|') !== registeredFinderSignature ||
|
|
999
|
+
!modules.every((m) => registeredModules.has(m.module as object));
|
|
1000
|
+
if (changed && getCurrentProject()) void refreshProjectAdapter();
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
registerDocumentKindsSupplier(() =>
|
|
1004
|
+
_facet && !_facet.documentsPending
|
|
1005
|
+
? [...new Set(_facet.scenes.entries.map((entry) => entry.kind))]
|
|
1006
|
+
: null,
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
export function refreshProjectAdapter(): Promise<void> {
|
|
1010
|
+
const run = loadAndPublishProjectAdapter(++_refreshEpoch, getCurrentProject());
|
|
1011
|
+
_inFlight = run;
|
|
1012
|
+
void run.then(() => {
|
|
1013
|
+
if (_inFlight === run) _inFlight = null;
|
|
1014
|
+
});
|
|
1015
|
+
return run;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function loadAndPublishProjectAdapter(
|
|
1019
|
+
epoch: number,
|
|
1020
|
+
project: ActiveProject | null,
|
|
1021
|
+
): Promise<void> {
|
|
1022
|
+
// Declaration, story, and contribution refreshes can overlap. Only the
|
|
1023
|
+
// latest pass for the still-active project may replace its document table.
|
|
1024
|
+
const current = (): boolean => epoch === _refreshEpoch && getCurrentProject() === project;
|
|
1025
|
+
if (!project) {
|
|
1026
|
+
publish(null);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
let manifest: ResolvedGameManifest;
|
|
1031
|
+
try {
|
|
1032
|
+
manifest = await fetchGameManifest();
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (!current()) return;
|
|
1035
|
+
publish({
|
|
1036
|
+
source: 'native',
|
|
1037
|
+
modulePath: null,
|
|
1038
|
+
regions: [],
|
|
1039
|
+
scenes: { entries: [], default: null },
|
|
1040
|
+
observation: [],
|
|
1041
|
+
notes: [],
|
|
1042
|
+
documentsPending: false,
|
|
1043
|
+
error: `adapter: the project's manifest did not load, so no region could be derived — ${describe(error)}`,
|
|
1044
|
+
});
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (!current()) return;
|
|
1049
|
+
const choice = await resolveAdapterDefinition(project, manifest);
|
|
1050
|
+
if (!current()) return;
|
|
1051
|
+
registerProjectFinders();
|
|
1052
|
+
|
|
1053
|
+
// Gathering finder input reads the project's own entrypoint sources and
|
|
1054
|
+
// component index; a table with no finder SELECTIONS has nothing to feed, and
|
|
1055
|
+
// an ingest game's sources are not the host's to read at all. Skipping is the
|
|
1056
|
+
// honest read of the declaration, not an optimization dressed as one.
|
|
1057
|
+
const regionsPreview =
|
|
1058
|
+
choice.definition.regions === 'manifest-roots'
|
|
1059
|
+
? regionsFromManifestRoots(declaredRoots(manifest), choice.definition.regionIncludes)
|
|
1060
|
+
: [...choice.definition.regions];
|
|
1061
|
+
const input =
|
|
1062
|
+
(choice.definition.documents.find ?? []).length > 0
|
|
1063
|
+
? await gatherFinderInput(manifest, regionsPreview, choice.definition)
|
|
1064
|
+
: NO_FINDER_INPUT;
|
|
1065
|
+
if (!current()) return;
|
|
1066
|
+
const facet = projectAdapterTableFrom(manifest, choice, input);
|
|
1067
|
+
// `native` is the DECLARED DEFAULT standing in for a project that shipped no
|
|
1068
|
+
// declaration file, so it is not a declaration this project made — see
|
|
1069
|
+
// `projectAdapterDefinition`.
|
|
1070
|
+
publish(
|
|
1071
|
+
facet,
|
|
1072
|
+
choice.definition.observation,
|
|
1073
|
+
choice.definition.input,
|
|
1074
|
+
choice.source === 'native' ? null : choice.definition,
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function describe(error: unknown): string {
|
|
1079
|
+
return error instanceof Error ? error.message : String(error);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/** Does a project-relative path fall under any declared finder's `include`? */
|
|
1083
|
+
function finderIncludesMatch(path: string): boolean {
|
|
1084
|
+
// No parsed declaration in hand (the native default, or a load in flight):
|
|
1085
|
+
// the globs are unknown, so a src change is treated as relevant rather than
|
|
1086
|
+
// dropped — a stale table costs more than a spare resolve.
|
|
1087
|
+
if (!_definition) return true;
|
|
1088
|
+
const selections = (_definition.documents.find ?? []) as ReadonlyArray<{
|
|
1089
|
+
include?: readonly string[];
|
|
1090
|
+
}>;
|
|
1091
|
+
for (const selection of selections) {
|
|
1092
|
+
for (const glob of selection.include ?? []) {
|
|
1093
|
+
if (globToRegExp(glob).test(path)) return true;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return false;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/** `src/models/**\/*.ts` → a RegExp over a project-relative path. `**` spans
|
|
1100
|
+
* directories, `*` stays inside one; everything else is literal. */
|
|
1101
|
+
function globToRegExp(glob: string): RegExp {
|
|
1102
|
+
const DEEP = '__DEEP__';
|
|
1103
|
+
const escaped = glob
|
|
1104
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
1105
|
+
.replace(/\*\*\//g, DEEP)
|
|
1106
|
+
.replace(/\*/g, '[^/]*')
|
|
1107
|
+
.split(DEEP)
|
|
1108
|
+
.join('(?:.*/)?');
|
|
1109
|
+
return new RegExp(`^${escaped}$`);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Load the adapter once at editor init and again on every project switch —
|
|
1114
|
+
* the same lifecycle shape `project-story-discovery.ts` uses, for the same
|
|
1115
|
+
* reason: the adapter is a PROJECT capability, not a side effect of mounting
|
|
1116
|
+
* a particular root.
|
|
1117
|
+
*/
|
|
1118
|
+
export function startProjectAdapterLoad(): () => void {
|
|
1119
|
+
void refreshProjectAdapter();
|
|
1120
|
+
const changed = (path: string) => {
|
|
1121
|
+
if (
|
|
1122
|
+
projectModuleChangeMatches(path, ADAPTER_MODULE_FILENAME) ||
|
|
1123
|
+
/(?:^|\/)src\/.+\.[jt]sx?$/.test(path)
|
|
1124
|
+
) {
|
|
1125
|
+
void refreshProjectAdapter();
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
const stopModules = subscribeProjectModuleChange(changed);
|
|
1129
|
+
const stopProject = onProjectChange(() => {
|
|
1130
|
+
if (_owner !== activeProjectKey()) publish(null);
|
|
1131
|
+
void refreshProjectAdapter();
|
|
1132
|
+
});
|
|
1133
|
+
// A finder's SOURCE changed on disk — a model module written, a page added.
|
|
1134
|
+
// Nothing imports those files, so this is the only signal the table gets.
|
|
1135
|
+
const stopSources = connectSourceFileEvents((path) => {
|
|
1136
|
+
if (path === '' || finderIncludesMatch(path)) void refreshProjectAdapter();
|
|
1137
|
+
});
|
|
1138
|
+
return () => {
|
|
1139
|
+
stopModules();
|
|
1140
|
+
stopProject();
|
|
1141
|
+
stopSources();
|
|
1142
|
+
};
|
|
1143
|
+
}
|