@vgai/editor 0.2.0 → 0.4.0-canary.20260715.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/assets/axe-C7YBtOuM.js +30 -0
- package/dist/assets/browserAll-DDtGGE48.js +1 -0
- package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
- package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
- package/dist/assets/game-BdTHR2_X.js +1 -0
- package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
- package/dist/assets/game-CcHfYxjP.js +1 -0
- package/dist/assets/game-Cqzj_lpD.js +1 -0
- package/dist/assets/game-DSZdeCHu.js +1 -0
- package/dist/assets/game-Dch9sAPc.js +1 -0
- package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
- package/dist/assets/game-loop-DMIgSMV-.js +1 -0
- package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
- package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
- package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
- package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
- package/dist/assets/index-CQEfi217.js +15412 -0
- package/dist/assets/index-Dvi-V0NE.css +1 -0
- package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
- package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
- package/dist/assets/react-18-C9eHn8x1.js +1 -0
- package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
- package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
- package/dist/index.html +15 -13
- package/dist/ingest/racing-game/vgai.game.json +5 -4
- package/dist/ingest/simcity/vgai.game.json +5 -4
- package/dist/ingest/tanks/vgai.game.json +5 -4
- package/dist/project-scripts.bundle.js +3917 -0
- package/dist/scenes/untitled.vscn.json +2 -0
- package/dist-server/packaged.mjs +7697 -3435
- package/package.json +31 -4
- package/scripts/probe-reporter.ts +349 -0
- package/scripts/run-probe.ts +2260 -0
- package/server/asset-catalog-audit.ts +268 -0
- package/server/asset-catalog-v2.ts +146 -0
- package/server/asset-library-routes.ts +276 -58
- package/server/canonical-path.ts +1 -1
- package/server/dev.ts +148 -44
- package/server/editor-server.ts +888 -61
- package/server/editor-sse.ts +115 -4
- package/server/harness-chat-service.ts +796 -0
- package/server/local-asset-catalog.ts +157 -22
- package/server/packaged.ts +51 -21
- package/server/process-shutdown.ts +136 -0
- package/server/prod.ts +9 -6
- package/server/project-hmr-files.ts +41 -0
- package/server/project-operations.ts +343 -0
- package/server/project-optimize-deps-entries.ts +4 -4
- package/server/project-output-writer.ts +156 -0
- package/server/project-validation.ts +140 -0
- package/server/project-view.ts +8 -50
- package/server/server-utils.ts +75 -10
- package/server/session-registry.ts +12 -0
- package/src/EditorContext.tsx +107 -3
- package/src/action-registry.ts +115 -17
- package/src/adapter-resolver.ts +213 -478
- package/src/asset-events.ts +64 -1
- package/src/asset-preview.ts +906 -0
- package/src/asset-selection.ts +121 -0
- package/src/asset-workflow/asset-capabilities.ts +259 -0
- package/src/asset-workflow/asset-editor-errors.ts +46 -0
- package/src/asset-workflow/asset-import-pipeline.ts +176 -0
- package/src/asset-workflow/asset-library-query.ts +107 -0
- package/src/asset-workflow/asset-types.ts +192 -0
- package/src/asset-workflow/asset-workflow-quality.ts +65 -0
- package/src/asset-workflow/import-contract.ts +113 -0
- package/src/asset-workflow/model-conversion.ts +126 -0
- package/src/asset-workflow/model-inspection.ts +185 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +153 -0
- package/src/asset-workflow/project-asset-operations.ts +119 -0
- package/src/asset-workflow/thumbnail-system.ts +172 -0
- package/src/authoring/active-systems.ts +15 -0
- package/src/authoring/boundary-authoring-adapter.ts +16 -5
- package/src/authoring/composite-authoring-adapter.ts +305 -124
- package/src/authoring/design-time-layers.ts +300 -117
- package/src/authoring/edit-mode-authoring.ts +278 -293
- package/src/authoring/embed-only-adapter.ts +4 -2
- package/src/authoring/first-party-hierarchy-menu.ts +7 -2
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
- package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
- package/src/authoring/first-party-inspector/register.tsx +13 -2
- package/src/authoring/ingest-authoring-adapter.ts +142 -45
- package/src/authoring/mount-failure-report.ts +5 -5
- package/src/authoring/provenance.ts +41 -0
- package/src/authoring/react-dom-authoring-adapter.ts +92 -92
- package/src/authoring/react-inspector-section.tsx +489 -51
- package/src/authoring/react-world-authoring-adapter.ts +541 -306
- package/src/authoring/story-overlay.ts +1 -1
- package/src/authoring/story-types.ts +11 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
- package/src/authoring/viewport-tool-context.ts +78 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +186 -0
- package/src/authoring/world-pan-state.ts +33 -17
- package/src/authoring/world-session-state.ts +1 -1
- package/src/authoring/world2d-authoring-adapter.ts +109 -52
- package/src/availability-tick.ts +56 -0
- package/src/browser-play-entry.ts +5 -5
- package/src/browser-project-scripts.ts +1 -0
- package/src/build-identity.ts +15 -0
- package/src/command-listener.ts +893 -19
- package/src/components/AlignToolbar.tsx +74 -63
- package/src/components/AppRoot.tsx +193 -26
- package/src/components/ApplicationMenus.tsx +272 -0
- package/src/components/AssetBrowser.tsx +1450 -293
- package/src/components/AssetEditorShell.tsx +141 -0
- package/src/components/AssetImportDetails.tsx +218 -0
- package/src/components/AssetLibraryPanel.tsx +14 -0
- package/src/components/CameraInfo.tsx +11 -20
- package/src/components/CenterDocuments.tsx +585 -0
- package/src/components/CommandPalette.tsx +75 -30
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +57 -112
- package/src/components/DebugConsolePanel.tsx +461 -0
- package/src/components/DebuggerPanel.tsx +165 -0
- package/src/components/DefaultEditorLayout.tsx +167 -231
- package/src/components/GameHierarchy.tsx +873 -290
- package/src/components/GamePerformanceOverlay.tsx +46 -0
- package/src/components/HarnessChatPanel.tsx +2352 -0
- package/src/components/HistoryPanel.tsx +226 -0
- package/src/components/Inspector.tsx +153 -99
- package/src/components/InspectorToolSection.tsx +6 -4
- package/src/components/MountFailureBanner.tsx +31 -48
- package/src/components/NetworkSection.tsx +25 -14
- package/src/components/OnlineAssetBrowser.tsx +794 -343
- package/src/components/OverlayReportBanner.tsx +38 -64
- package/src/components/PerformancePanel.tsx +206 -0
- package/src/components/PlayBar.tsx +93 -263
- package/src/components/PrefabSaveDialog.tsx +55 -127
- package/src/components/ProjectHeader.css +60 -0
- package/src/components/ProjectHeader.tsx +170 -105
- package/src/components/ProjectScreen.tsx +246 -184
- package/src/components/ReactCanvasControls.tsx +185 -0
- package/src/components/ResolutionPicker.tsx +85 -0
- package/src/components/RootActivateDialog.tsx +39 -68
- package/src/components/SaveDialog.tsx +55 -127
- package/src/components/SaveStatus.tsx +11 -29
- package/src/components/StartupLoadingScreen.tsx +96 -0
- package/src/components/StateWatchPanel.tsx +302 -0
- package/src/components/StatsOverlay.tsx +12 -5
- package/src/components/StatusBar.tsx +64 -0
- package/src/components/ThemeManagerDialog.css +89 -0
- package/src/components/ThemeManagerDialog.tsx +371 -0
- package/src/components/ToolHost.tsx +26 -15
- package/src/components/Toolbar.tsx +117 -214
- package/src/components/VgaiVoxelLogo.tsx +89 -0
- package/src/components/ViewportOverlay.tsx +97 -172
- package/src/components/ViewportPanel.tsx +130 -31
- package/src/components/WorkspaceDock.tsx +891 -0
- package/src/components/WorldSelectionOverlay.tsx +51 -63
- package/src/components/WorldTextEditor.tsx +9 -5
- package/src/components/asset-documents.tsx +390 -0
- package/src/components/asset-editor-persistence.ts +44 -0
- package/src/components/asset-selection-section.tsx +338 -0
- package/src/components/asset-viewers/AudioViewer.tsx +73 -21
- package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
- package/src/components/asset-viewers/ImageViewer.tsx +43 -11
- package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
- package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
- package/src/components/asset-viewers/ModelViewer.tsx +805 -121
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
- package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
- package/src/components/asset-workflow.css +506 -0
- package/src/components/build-documents.tsx +696 -0
- package/src/components/core-utilities.tsx +75 -0
- package/src/components/data-documents.tsx +364 -0
- package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
- package/src/components/debugger-panel-state.ts +57 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
- package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
- package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
- package/src/components/inspector-widgets/FontPicker.tsx +13 -21
- package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
- package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
- package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
- package/src/components/inspector-widgets/shared.tsx +90 -49
- package/src/components/inspectors/AnimationSection.tsx +39 -40
- package/src/components/inspectors/AudioSection.tsx +28 -39
- package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
- package/src/components/inspectors/CameraSection.tsx +14 -23
- package/src/components/inspectors/ComponentsSection.tsx +39 -43
- package/src/components/inspectors/JointsSection.tsx +34 -43
- package/src/components/inspectors/LightSection.tsx +90 -35
- package/src/components/inspectors/MaterialSection.tsx +137 -174
- package/src/components/inspectors/MeshSection.tsx +9 -17
- package/src/components/inspectors/NavigationSection.tsx +8 -15
- package/src/components/inspectors/ParticleSection.tsx +91 -76
- package/src/components/inspectors/PhysicsSection.tsx +42 -46
- package/src/components/inspectors/PostProcessingSection.tsx +28 -36
- package/src/components/inspectors/RenderingSection.tsx +12 -20
- package/src/components/inspectors/ShadowSection.tsx +7 -8
- package/src/components/inspectors/SplineSection.tsx +31 -42
- package/src/components/inspectors/TransformSection.tsx +14 -22
- package/src/components/patterns/Dialog.tsx +95 -0
- package/src/components/patterns/Fields.tsx +44 -0
- package/src/components/patterns/List.tsx +25 -0
- package/src/components/patterns/StateSurface.tsx +40 -0
- package/src/components/patterns/Surfaces.tsx +115 -0
- package/src/components/patterns/Tabs.tsx +80 -0
- package/src/components/patterns/Toolbar.tsx +51 -0
- package/src/components/patterns/Tree.tsx +72 -0
- package/src/components/primitives/Button.tsx +46 -0
- package/src/components/primitives/ColorInput.tsx +11 -47
- package/src/components/primitives/EditorIcon.tsx +99 -0
- package/src/components/primitives/FormControls.tsx +71 -0
- package/src/components/primitives/JsonInput.tsx +15 -2
- package/src/components/primitives/Layout.tsx +100 -0
- package/src/components/primitives/Menu.tsx +105 -0
- package/src/components/primitives/NumberInput.tsx +8 -35
- package/src/components/primitives/Panel.tsx +49 -44
- package/src/components/primitives/SectionHeader.tsx +10 -28
- package/src/components/primitives/Text.tsx +46 -0
- package/src/components/primitives/Tooltip.tsx +20 -12
- package/src/components/primitives/Vec3Input.tsx +3 -0
- package/src/components/primitives/banner-tones.ts +33 -0
- package/src/components/primitives/editor-icons.ts +102 -0
- package/src/components/primitives/panel-header-styles.ts +30 -0
- package/src/components/project-operation-documents.tsx +226 -0
- package/src/components/status-contributions.tsx +175 -0
- package/src/components/story-documents-register.ts +15 -0
- package/src/components/story-documents.tsx +1185 -0
- package/src/components/tool-documents.tsx +110 -0
- package/src/components/workspace-dock.css +293 -0
- package/src/components/workspace-history.ts +33 -0
- package/src/components/world-documents.tsx +145 -0
- package/src/components/xstate-documents.tsx +209 -0
- package/src/composite-screenshot.ts +248 -0
- package/src/design-system/index.ts +70 -0
- package/src/design-system-stories/Foundation.stories.tsx +183 -0
- package/src/design-system-stories/Patterns.stories.tsx +251 -0
- package/src/design-system-stories/Workspace.stories.tsx +538 -0
- package/src/doctor/report.ts +57 -13
- package/src/editor-api.ts +295 -31
- package/src/editor-compatibility.ts +200 -0
- package/src/editor-console.ts +35 -17
- package/src/editor-hotkeys.ts +85 -33
- package/src/editor-store.ts +538 -233
- package/src/editor-viewport.ts +160 -34
- package/src/entity-factory.ts +14 -3
- package/src/gizmo-registry.ts +21 -5
- package/src/harness-chat-editor-context.ts +37 -0
- package/src/harness-chat-indicator.ts +68 -0
- package/src/harness-chat-layout.ts +53 -0
- package/src/harness-chat-lifecycle.ts +170 -0
- package/src/harness-chat-reconnect.ts +85 -0
- package/src/harness-chat-types.ts +409 -0
- package/src/hierarchy-drop.ts +50 -0
- package/src/hierarchy-expansion-state.ts +100 -0
- package/src/hierarchy-menu-registry.ts +1 -1
- package/src/hierarchy-projection.ts +76 -0
- package/src/history/editor-session.ts +21 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-service.ts +904 -0
- package/src/history/json-history-resource.ts +165 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +266 -0
- package/src/history/project-root-history-backends.ts +53 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +108 -0
- package/src/history/source-history-backend.ts +374 -0
- package/src/history/types.ts +100 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hosted-example.ts +12 -2
- package/src/hotkeys.ts +89 -18
- package/src/ingest/discovery-react.ts +9 -10
- package/src/ingest/discovery.ts +4 -4
- package/src/ingest/discovery2d.ts +5 -5
- package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
- package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
- package/src/ingest/games/games-fps/vgai.game.json +3 -3
- package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
- package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
- package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
- package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
- package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
- package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
- package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
- package/src/ingest/types.ts +1 -1
- package/src/ingest-mode.ts +47 -52
- package/src/ingest-siblings.ts +23 -33
- package/src/initial-scene.ts +1 -2
- package/src/main.tsx +16 -8
- package/src/manifest-project.ts +10 -12
- package/src/model-thumbnail.ts +261 -26
- package/src/module-mode.ts +44 -50
- package/src/play-log-events.ts +26 -0
- package/src/play-mode.ts +495 -401
- package/src/play-scene-adoption.ts +23 -0
- package/src/playground/main.ts +68 -24
- package/src/playwright-shim.ts +473 -0
- package/src/project-manager.ts +90 -10
- package/src/project-operations.ts +112 -0
- package/src/project-story-discovery.ts +25 -0
- package/src/project-tool-discovery.ts +43 -0
- package/src/scene-serializer.ts +0 -6
- package/src/scene-sync.ts +7 -2
- package/src/script-loader.ts +3 -3
- package/src/skeleton-helper.ts +57 -0
- package/src/storage/seed.ts +68 -8
- package/src/stories/StoryPreviewMount.tsx +265 -0
- package/src/stories/action-stories.fixture.tsx +44 -0
- package/src/stories/button-stories.fixture.tsx +50 -0
- package/src/stories/compose-project-stories.ts +182 -0
- package/src/stories/loader-stories.fixture.tsx +53 -0
- package/src/stories/story-discovery.ts +130 -0
- package/src/stories/story-registry.ts +146 -0
- package/src/theme-css.ts +1049 -0
- package/src/theme-library.ts +238 -0
- package/src/theme-preference.ts +130 -0
- package/src/theme.ts +527 -0
- package/src/tool-loader.ts +80 -24
- package/src/ui-source/inspect.ts +27 -4
- package/src/ui-source/source-write-backend.ts +83 -22
- package/src/widgets/index.ts +9 -18
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-dock-controller.ts +64 -0
- package/src/workspace-document-ids.ts +5 -0
- package/src/workspace-document-registry.ts +367 -0
- package/src/workspace-state-persistence.ts +455 -0
- package/src/workspace-status-registry.ts +110 -0
- package/src/workspace-utility-commands.ts +49 -0
- package/src/workspace-utility-registry.ts +161 -0
- package/src/world-document-routing.ts +16 -0
- package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
- package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
- package/src/xstate-inspect/use-live-actor-state.ts +39 -0
- package/src/xstate-inspect/xstate-graph.ts +265 -0
- package/src/xstate-inspect/xstate-layout.ts +76 -0
- package/src/z-index.ts +26 -0
- package/template/.claude/skills/editor/SKILL.md +129 -90
- package/template/AGENTS.md +1110 -0
- package/template/CLAUDE.md +1 -512
- package/template/IDIOMS.md +50 -0
- package/template/check-idioms.ts +706 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +13 -4
- package/template/public/scenes/default.scn2d.json +6 -0
- package/template/public/scenes/default.vscn.json +7 -1
- package/template/public/scenes/untitled.vscn.json +10 -0
- package/template/scripts/playtest.ts +40 -0
- package/template/server/loopback.ts +75 -0
- package/template/server/rooms/arena-room.ts +69 -1
- package/template/server/rooms/game-room.ts +68 -1
- package/template/src/data/README.md +9 -4
- package/template/src/data/data-ref.ts +35 -0
- package/template/src/data/tuning.schema.ts +1 -1
- package/template/src/main.ts +41 -62
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/operations/README.md +88 -0
- package/template/src/runtime/pixi-adapter.ts +24 -0
- package/template/src/scripts/components/data-spinner.ts +43 -2
- package/template/src/scripts/components/spin-lap-math.ts +27 -0
- package/template/src/tools/README.md +5 -3
- package/template/src/tools/example.tool.tsx +13 -7
- package/template/src/ui/Button.stories.tsx +66 -0
- package/template/src/ui/RebindPanel.tsx +2 -6
- package/template/src/ui/game.tsx +17 -0
- package/template/tests/acceptance/example.spec.ts +64 -0
- package/template/tests/logic/example.test.ts +31 -0
- package/template/tests/logic/room.test.ts +100 -0
- package/template/tests/tsconfig.json +30 -0
- package/template/validate-manifest.ts +5 -5
- package/template/validate-scenes.ts +50 -18
- package/template/vgai.game.json +20 -13
- package/template/vite.config.ts +77 -16
- package/vite-plugin-ui-oid.ts +261 -17
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
- package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
- package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
- package/dist/assets/browserAll-CEkWYlFk.js +0 -1
- package/dist/assets/create-runtime-DYIWr3va.js +0 -1
- package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
- package/dist/assets/game-8SDZPPgH.js +0 -1
- package/dist/assets/game-CevorxyV.js +0 -1
- package/dist/assets/game-Di7XlywD.js +0 -1
- package/dist/assets/game-DjnCaw8R.js +0 -1
- package/dist/assets/game-Q9A0aXSV.js +0 -1
- package/dist/assets/index-CPVV6pjw.js +0 -1
- package/dist/assets/index-CpOnYyeD.js +0 -213
- package/dist/assets/index-Dr8H_6Rl.js +0 -1
- package/dist/assets/index-_bCaipUk.js +0 -13650
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
- package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
- package/dist/assets/scene-query-BivAHhJu.js +0 -1
- package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
- package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
- package/src/authoring/scene-ui-layer.ts +0 -485
- package/src/authoring/ui-authoring-adapter.ts +0 -325
- package/src/authoring/ui-hierarchy-menu.ts +0 -40
- package/src/authoring/ui-inspector-sections.tsx +0 -156
- package/src/authoring/undo-timeline.ts +0 -145
- package/src/components/BottomPanel.tsx +0 -326
- package/src/components/BuildPanel.tsx +0 -455
- package/src/components/FolderTree.tsx +0 -179
- package/src/components/RightPanel.tsx +0 -166
- package/src/components/WorldContextMenu.tsx +0 -257
- package/src/components/asset-viewers/AssetViewer.tsx +0 -38
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
- package/src/scene-ui-mode/active-scene-ui.ts +0 -58
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
- package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
- package/src/ui-editor/inspector.tsx +0 -563
- package/src/ui-editor/node-ops.ts +0 -266
- package/src/ui-editor/overlay.tsx +0 -380
- package/src/ui-editor/palette.tsx +0 -114
- package/src/ui-editor/react-store.ts +0 -78
- package/src/ui-editor/timeline-panel.tsx +0 -443
- package/src/ui-editor/ui-edit-store.ts +0 -552
- package/src/ui-editor/ui-prefab.ts +0 -95
- package/template/project.json +0 -9
- package/template/src/scripts/main.ts +0 -94
- package/template/src/ui/DefaultHud.tsx +0 -40
- package/template/src/ui/game-state.tsx +0 -37
- package/template/src/ui/mount-default-hud.tsx +0 -19
- package/template/src/ui/mount-ui.ts +0 -15
- package/vite-plugin-react-world-provider.ts +0 -81
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Design-time layer stack (B1, docs/unified-world-editor/26-unified-editor-spec.md
|
|
3
|
-
* §3 B1 · D3/D9) — mounts
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* UNTOUCHED slot, each react/pixi world getting an absolutely-positioned
|
|
8
|
-
* layer above it. Layers default `pointer-events:none`; the eye
|
|
3
|
+
* §3 B1 · D3/D9) — mounts one or more DESIGN-TIME React/Pixi worlds into a
|
|
4
|
+
* caller-owned authoring host. Runtime still uses a z-ordered composite, but
|
|
5
|
+
* the editor supplies a distinct host per world document; only Game presents
|
|
6
|
+
* the composed result. Layers default `pointer-events:none`; the eye
|
|
9
7
|
* (`world-session-state.ts`'s `hidden`) hides a layer; `interactive` forwards
|
|
10
8
|
* real pointer events for design-time hover/press testing; `pickLock` is
|
|
11
9
|
* stored only (B4 consumes it in the pick walk). A layer that throws on
|
|
@@ -21,47 +19,74 @@
|
|
|
21
19
|
* WHOLE S-B effort — display layer + (B4) stage hit-test only, no
|
|
22
20
|
* hierarchy/stories — see `mountPixiLayer`'s doc comment.
|
|
23
21
|
*
|
|
24
|
-
* Only
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* (there is no live scene to show it, and switching focus is out of B1's
|
|
31
|
-
* scope — see `edit-mode-authoring.ts`'s `switchFocusedWorld` stub) simply
|
|
32
|
-
* keeps its existing Boundary node, untouched by this module.
|
|
22
|
+
* Only `react`/`pixijs` children are candidates for these hosts: the focused
|
|
23
|
+
* threejs world renders through `ViewportPanel`'s own `<canvas>`. A non-focused THREEJS world
|
|
24
|
+
* (there is no live scene to show it — `switchFocusedWorld` in
|
|
25
|
+
* `edit-mode-authoring.ts` changes which world is focused, via a full
|
|
26
|
+
* rebuild, rather than mounting a second live one) simply keeps its existing
|
|
27
|
+
* Boundary node, untouched by this module.
|
|
33
28
|
*/
|
|
34
29
|
|
|
35
30
|
import type { AuthoringAdapter, PickProvider } from '@engine/adapter';
|
|
36
31
|
import { createAssetCache } from '@engine/assets';
|
|
37
32
|
import { createGameLoop } from '@engine/core/game-loop';
|
|
38
|
-
import type {
|
|
33
|
+
import type { ResolvedAdapterRoot } from '@engine/manifest/load';
|
|
39
34
|
import { createGame, type Game } from '@engine/runtime/game';
|
|
40
35
|
import { stackOrder } from '@engine/runtime/input-router';
|
|
41
36
|
import { PixiSceneGameAdapter } from '@engine/world2d/pixi-game-adapter';
|
|
42
|
-
import { createDataSource } from '@vgai/scene-ui/data-source';
|
|
43
|
-
import { storiesFromRegistry, type UIStory } from '@vgai/scene-ui/registry';
|
|
44
|
-
import { UIDataProvider } from '@vgai/scene-ui/ui-data-context';
|
|
45
37
|
import { Component, type ReactNode } from 'react';
|
|
46
38
|
import {
|
|
47
39
|
fetchScene2DFile,
|
|
48
|
-
|
|
40
|
+
loadPixiComponentRegistryForProject,
|
|
49
41
|
resolveGameProviderForProject,
|
|
50
|
-
|
|
42
|
+
resolveReactAdapterRootComponent,
|
|
51
43
|
resolveReactWorldMountRuntime,
|
|
52
44
|
} from '../adapter-resolver';
|
|
53
45
|
import { editorConsole } from '../editor-console';
|
|
54
46
|
import type { EditorStore } from '../editor-store';
|
|
55
47
|
import { getCurrentProject } from '../project-manager';
|
|
48
|
+
import { mountIsolatedStory } from '../stories/StoryPreviewMount';
|
|
49
|
+
import {
|
|
50
|
+
getDefaultRootPreviewStory,
|
|
51
|
+
getRootPreviewStories,
|
|
52
|
+
type RootPreviewStory,
|
|
53
|
+
refreshProjectStories,
|
|
54
|
+
} from '../stories/story-registry';
|
|
55
|
+
|
|
56
|
+
/** D20: Play tears the design-time adapter down, so the selected CSF state
|
|
57
|
+
* must live one level above that adapter to survive Stop's rebuild. Keyed by
|
|
58
|
+
* project + root so equal root ids in different projects never bleed. */
|
|
59
|
+
const selectedPortableStoryByRoot = new Map<string, string>();
|
|
60
|
+
|
|
61
|
+
function portableStorySelectionKey(projectRoot: string, rootId: string): string {
|
|
62
|
+
return `${projectRoot}\0${rootId}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function portableDocumentLabel(story: RootPreviewStory): string {
|
|
66
|
+
const authoredTitle = story.title?.split('/').filter(Boolean).at(-1)?.trim();
|
|
67
|
+
if (authoredTitle) return authoredTitle;
|
|
68
|
+
const file = story.modulePath.split('/').pop() ?? story.modulePath;
|
|
69
|
+
const stem = file.replace(/\.stories\.[^.]+$/i, '');
|
|
70
|
+
const words = stem
|
|
71
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
72
|
+
.replace(/[-_]+/g, ' ')
|
|
73
|
+
.trim();
|
|
74
|
+
return words || file;
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
|
|
57
78
|
import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
|
|
58
79
|
import type { CompositeAuthoringAdapter } from './composite-authoring-adapter';
|
|
59
|
-
import {
|
|
80
|
+
import { EDIT_MODE_REBUILD_EVENT } from './edit-mode-authoring';
|
|
60
81
|
import { addMountFailureReport, formatMountFailureMessage } from './mount-failure-report';
|
|
61
82
|
import { ReactWorldAuthoringAdapter } from './react-world-authoring-adapter';
|
|
62
|
-
import { mountSceneUILayer } from './scene-ui-layer';
|
|
63
83
|
import { getStoryOverlay } from './story-overlay';
|
|
64
|
-
import {
|
|
84
|
+
import type { UIComponentRegistry, UIStory } from './story-types';
|
|
85
|
+
import {
|
|
86
|
+
getWorldCanvasViewport,
|
|
87
|
+
resetWorldCanvasViewport,
|
|
88
|
+
subscribeWorldCanvasViewport,
|
|
89
|
+
} from './world-canvas-viewport-state';
|
|
65
90
|
import {
|
|
66
91
|
getWorldPan,
|
|
67
92
|
panTransformValue,
|
|
@@ -70,16 +95,16 @@ import {
|
|
|
70
95
|
} from './world-pan-state';
|
|
71
96
|
import { isWorldHidden, isWorldInteractive } from './world-session-state';
|
|
72
97
|
|
|
73
|
-
/** Bottom-to-top layer z-index base — strictly below
|
|
74
|
-
* `ToolStrip`+`ViewportOverlay`'s z1000, so
|
|
98
|
+
/** Bottom-to-top layer z-index base — strictly below the selection overlay's
|
|
99
|
+
* z50 and `ToolStrip`+`ViewportOverlay`'s z1000, so canvas-local editing
|
|
75
100
|
* chrome is never occluded by a design-time layer. */
|
|
76
101
|
const BASE_Z_INDEX = 1;
|
|
77
102
|
|
|
78
103
|
/** One candidate world for a design-time layer — read entirely off its
|
|
79
104
|
* existing `BoundaryAuthoringAdapter` (kind/path/zOrder/pausable are already
|
|
80
|
-
* exposed there, independent of whether the composite has a
|
|
105
|
+
* exposed there, independent of whether the composite has a writable manifest
|
|
81
106
|
* manifest surface — see that adapter's own inspector). */
|
|
82
|
-
interface
|
|
107
|
+
export interface DesignTimeWorldDescriptor {
|
|
83
108
|
readonly worldId: string;
|
|
84
109
|
readonly kind: 'react' | 'pixijs';
|
|
85
110
|
readonly path: string | undefined;
|
|
@@ -87,8 +112,8 @@ interface LayerCandidate {
|
|
|
87
112
|
readonly pausable: boolean;
|
|
88
113
|
}
|
|
89
114
|
|
|
90
|
-
function readCandidates(composite: CompositeAuthoringAdapter):
|
|
91
|
-
const out:
|
|
115
|
+
function readCandidates(composite: CompositeAuthoringAdapter): DesignTimeWorldDescriptor[] {
|
|
116
|
+
const out: DesignTimeWorldDescriptor[] = [];
|
|
92
117
|
for (const { worldId, adapter } of composite.childAdapters()) {
|
|
93
118
|
if (!(adapter instanceof BoundaryAuthoringAdapter)) continue; // the focused (live) world
|
|
94
119
|
const kind = adapter.inspector.get(worldId, 'kind') as string;
|
|
@@ -106,19 +131,29 @@ function readCandidates(composite: CompositeAuthoringAdapter): LayerCandidate[]
|
|
|
106
131
|
return out;
|
|
107
132
|
}
|
|
108
133
|
|
|
134
|
+
/** Snapshot the non-Three roots that can own an edit-time document. Capture
|
|
135
|
+
* this before any root mounts: a successful React mount upgrades its
|
|
136
|
+
* Boundary adapter in-place, while the document still needs the manifest
|
|
137
|
+
* path and stacking metadata that Boundary disclosed. */
|
|
138
|
+
export function designTimeWorldDescriptors(
|
|
139
|
+
composite: CompositeAuthoringAdapter,
|
|
140
|
+
): readonly DesignTimeWorldDescriptor[] {
|
|
141
|
+
return readCandidates(composite);
|
|
142
|
+
}
|
|
143
|
+
|
|
109
144
|
/**
|
|
110
145
|
* The inert design-time GameContext stand-in (B1's one genuine design
|
|
111
146
|
* decision). A REAL `Game` — never a hand-typed duck-typed literal, mirroring
|
|
112
147
|
* this repo's own test convention (`packages/editor/test/adapter-conformance-kit.ts`'s
|
|
113
148
|
* `headlessGame()`) — built with a loop that is constructed but NEVER
|
|
114
|
-
* started/ticked, and with NO
|
|
149
|
+
* started/ticked, and with NO roots ever registered. This satisfies
|
|
115
150
|
* `useGame`/`useGameState` (`@engine/react/game-state`, and any project's own
|
|
116
151
|
* re-export of it) without throwing, genuinely inertly:
|
|
117
152
|
* - `state.subscribe` never fires — nothing ever calls
|
|
118
153
|
* `GameStateBridgeInternal.bump()` (only `GameInternal.runFrame` does, and
|
|
119
154
|
* this Game's loop never runs a frame);
|
|
120
155
|
* - `state.frameVersion` stays `0` forever;
|
|
121
|
-
* - `queryByComponent`/`world()`/`
|
|
156
|
+
* - `queryByComponent`/`world()`/`roots` are empty/`null` because `roots`
|
|
122
157
|
* stays `[]` (nothing ever calls `registerWorld`).
|
|
123
158
|
* `defaultWorld`/`components`/`input`/`audio` throw descriptively if touched
|
|
124
159
|
* (same as any fresh `createGame()` before a world registers) — a HUD that
|
|
@@ -157,18 +192,18 @@ class DesignTimeErrorBoundary extends Component<
|
|
|
157
192
|
}
|
|
158
193
|
|
|
159
194
|
/**
|
|
160
|
-
* A minimal `
|
|
195
|
+
* A minimal `ResolvedAdapterRoot` satisfying `resolveReactAdapterRootComponent`
|
|
161
196
|
* (`../adapter-resolver.ts`, which reads only `id`/`kind`/`scene`/`entry` off
|
|
162
197
|
* it) — the fields below it (`adapter`/`capabilities`/`loop`/`description`)
|
|
163
198
|
* are unused by that function but required by the type; filled with the
|
|
164
199
|
* honest "this is a native default-react world" values.
|
|
165
200
|
*/
|
|
166
|
-
function
|
|
201
|
+
function buildResolvedAdapterRootStub(worldId: string, entry: string): ResolvedAdapterRoot {
|
|
167
202
|
return {
|
|
168
203
|
id: worldId,
|
|
169
|
-
|
|
204
|
+
surface: 'react',
|
|
170
205
|
description: undefined,
|
|
171
|
-
adapter: { type: '
|
|
206
|
+
adapter: { type: 'builtin', identity: 'react', surface: 'react' },
|
|
172
207
|
scene: undefined,
|
|
173
208
|
entry,
|
|
174
209
|
zOrder: 0,
|
|
@@ -194,6 +229,14 @@ function createLayerElement(container: HTMLElement, worldId: string, zIndex: num
|
|
|
194
229
|
layer.style.width = '100%';
|
|
195
230
|
layer.style.height = '100%';
|
|
196
231
|
layer.style.zIndex = String(zIndex);
|
|
232
|
+
// Same containment as the runtime's per-world surfaces (create-runtime.ts,
|
|
233
|
+
// world-surface cssText): make this layer the containing block for
|
|
234
|
+
// `position:fixed` descendants and clip to the world's rectangle, so a
|
|
235
|
+
// game's full-screen UI (`fixed; inset:0` — the natural idiom) renders
|
|
236
|
+
// inside the viewport instead of floating over the editor chrome. Also
|
|
237
|
+
// makes behavior pan-invariant: without it, space-pan's transform (below)
|
|
238
|
+
// suddenly captured `fixed` children that were page-anchored un-panned.
|
|
239
|
+
layer.style.contain = 'layout paint';
|
|
197
240
|
applySessionStyle(layer, worldId);
|
|
198
241
|
// D4 (spec27 §8 "space-pan" row) — seed this layer with whatever pan is
|
|
199
242
|
// currently in effect (normally none — `mountDesignTimeLayers` resets pan
|
|
@@ -213,6 +256,10 @@ function createLayerElement(container: HTMLElement, worldId: string, zIndex: num
|
|
|
213
256
|
* every subsequent pan change (see `world-pan-state.ts`). */
|
|
214
257
|
function applyPanTransform(layer: HTMLElement): void {
|
|
215
258
|
layer.style.transform = panTransformValue(getWorldPan()) ?? '';
|
|
259
|
+
layer.style.transformOrigin = '0 0';
|
|
260
|
+
const viewport = getWorldCanvasViewport();
|
|
261
|
+
layer.style.width = viewport.width === null ? '100%' : `${viewport.width}px`;
|
|
262
|
+
layer.style.height = viewport.height === null ? '100%' : `${viewport.height}px`;
|
|
216
263
|
}
|
|
217
264
|
|
|
218
265
|
interface LayerMountResult {
|
|
@@ -249,11 +296,9 @@ interface LayerRenderState {
|
|
|
249
296
|
|
|
250
297
|
/**
|
|
251
298
|
* React design-time mount (B1, story wiring B2 · D4, catalog B3) — the entry
|
|
252
|
-
* via the EXISTING #31 loader (`
|
|
253
|
-
* verbatim), wrapped in the
|
|
254
|
-
* (`resolveGameProviderForProject`
|
|
255
|
-
* its own `src/ui/game-state.tsx` is wrapped by ITS OWN provider, never
|
|
256
|
-
* split-context — see that function's doc comment) around the inert
|
|
299
|
+
* via the EXISTING #31 loader (`resolveReactAdapterRootComponent`, reused
|
|
300
|
+
* verbatim), wrapped in the canonical engine `GameProvider`
|
|
301
|
+
* (`resolveGameProviderForProject`) around the inert
|
|
257
302
|
* `createDesignTimeGame()` stand-in (UNTOUCHED by B2/B3 — see that function's
|
|
258
303
|
* own doc comment), inside a `UIDataProvider` (B2 — seeded from the active
|
|
259
304
|
* story's `data`, or `{}` with none applied) and a React error boundary.
|
|
@@ -296,7 +341,7 @@ interface LayerRenderState {
|
|
|
296
341
|
* reused.
|
|
297
342
|
*/
|
|
298
343
|
async function mountReactLayer(
|
|
299
|
-
candidate:
|
|
344
|
+
candidate: DesignTimeWorldDescriptor,
|
|
300
345
|
layer: HTMLElement,
|
|
301
346
|
projectRoot: string,
|
|
302
347
|
store: EditorStore,
|
|
@@ -306,38 +351,133 @@ async function mountReactLayer(
|
|
|
306
351
|
`design-time react layer "${candidate.worldId}": no \`entry\` declared — nothing to mount.`,
|
|
307
352
|
);
|
|
308
353
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
354
|
+
|
|
355
|
+
// D20 — portable Storybook CSF is React's canonical design-time state.
|
|
356
|
+
// Discovery feeds the first-class hierarchy documents below this world; the
|
|
357
|
+
// viewport mounts the SAME composed portable story rather than translating
|
|
358
|
+
// it into a VGAI fixture or constructing a fake runtime Game.
|
|
359
|
+
await refreshProjectStories({ rootPath: projectRoot });
|
|
360
|
+
const portableStories = getRootPreviewStories(candidate.worldId);
|
|
361
|
+
const defaultPortableStory = getDefaultRootPreviewStory(candidate.worldId);
|
|
362
|
+
if (defaultPortableStory) {
|
|
363
|
+
const selectionKey = portableStorySelectionKey(projectRoot, candidate.worldId);
|
|
364
|
+
const rememberedStoryId = selectedPortableStoryByRoot.get(selectionKey);
|
|
365
|
+
const initialPortableStory =
|
|
366
|
+
portableStories.find((story) => story.id === rememberedStoryId) ?? defaultPortableStory;
|
|
367
|
+
let disposed = false;
|
|
368
|
+
let mounted: Awaited<ReturnType<typeof mountIsolatedStory>> = null;
|
|
369
|
+
|
|
370
|
+
const mountPortable = async (
|
|
371
|
+
story: RootPreviewStory,
|
|
372
|
+
args: Record<string, unknown> = story.args,
|
|
373
|
+
): Promise<void> => {
|
|
374
|
+
const next = await mountIsolatedStory(
|
|
375
|
+
layer,
|
|
376
|
+
story.modulePath,
|
|
377
|
+
story.name,
|
|
378
|
+
story.Component,
|
|
379
|
+
args,
|
|
380
|
+
);
|
|
381
|
+
// A newer selection may claim the container while this story's loader
|
|
382
|
+
// is still in flight. `mountIsolatedStory` reports that supersession as
|
|
383
|
+
// null; never let the stale completion erase the newer mount handle,
|
|
384
|
+
// otherwise final teardown could no longer unmount the live React root.
|
|
385
|
+
if (!next) return;
|
|
386
|
+
if (disposed) {
|
|
387
|
+
next.unmount();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
mounted = next;
|
|
391
|
+
store.notifyIngestEdit();
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
await mountPortable(initialPortableStory);
|
|
395
|
+
if (!mounted) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`design-time react root "${candidate.worldId}": default CSF story ` +
|
|
398
|
+
`"${initialPortableStory.name}" was superseded before it mounted.`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const byId = new Map(portableStories.map((story) => [story.id, story]));
|
|
403
|
+
const documentsByPath = new Map<string, RootPreviewStory[]>();
|
|
404
|
+
for (const story of portableStories) {
|
|
405
|
+
const siblings = documentsByPath.get(story.modulePath);
|
|
406
|
+
if (siblings) siblings.push(story);
|
|
407
|
+
else documentsByPath.set(story.modulePath, [story]);
|
|
408
|
+
}
|
|
409
|
+
const adapter = new ReactWorldAuthoringAdapter(layer, store, {
|
|
410
|
+
...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
|
|
411
|
+
worldId: candidate.worldId,
|
|
412
|
+
portableStories: portableStories.map((story) => ({
|
|
413
|
+
id: story.id,
|
|
414
|
+
label: story.label,
|
|
415
|
+
args: story.args,
|
|
416
|
+
})),
|
|
417
|
+
portableDocuments: [...documentsByPath.entries()].map(([modulePath, stories]) => ({
|
|
418
|
+
id: `portable-document:${modulePath}`,
|
|
419
|
+
label: portableDocumentLabel(stories[0]!),
|
|
420
|
+
path: modulePath,
|
|
421
|
+
storyIds: stories.map((story) => story.id),
|
|
422
|
+
})),
|
|
423
|
+
activePortableStoryId: initialPortableStory.id,
|
|
424
|
+
onPortableStoryApplied: (storyId) => {
|
|
425
|
+
const story = (storyId ? byId.get(storyId) : null) ?? defaultPortableStory;
|
|
426
|
+
selectedPortableStoryByRoot.set(selectionKey, story.id);
|
|
427
|
+
void mountPortable(story).catch((error: unknown) => {
|
|
428
|
+
editorConsole.error(
|
|
429
|
+
`[design-time-layers] React root "${candidate.worldId}" failed applying portable ` +
|
|
430
|
+
`story "${story.name}": ${formatMountFailureMessage(error)}`,
|
|
431
|
+
'authoring',
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
onPortableStoryArgsChanged: (storyId, args) => {
|
|
436
|
+
const story = byId.get(storyId);
|
|
437
|
+
if (!story) return;
|
|
438
|
+
void mountPortable(story, args).catch((error: unknown) => {
|
|
439
|
+
editorConsole.error(
|
|
440
|
+
`[design-time-layers] React root "${candidate.worldId}" failed updating portable ` +
|
|
441
|
+
`story "${story.name}" args: ${formatMountFailureMessage(error)}`,
|
|
442
|
+
'authoring',
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
adapter,
|
|
450
|
+
dispose: () => {
|
|
451
|
+
disposed = true;
|
|
452
|
+
adapter.disposeReactRootAdapter();
|
|
453
|
+
mounted?.unmount();
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
editorConsole.warn(
|
|
459
|
+
`[design-time-layers] React root "${candidate.worldId}" has no portable CSF preview. ` +
|
|
460
|
+
'Add a *.stories.tsx module with parameters.vgai.root set to this root id. ' +
|
|
461
|
+
'Falling back to the deprecated inert-Game preview for compatibility.',
|
|
462
|
+
'authoring',
|
|
315
463
|
);
|
|
464
|
+
|
|
465
|
+
const worldStub = buildResolvedAdapterRootStub(candidate.worldId, candidate.path);
|
|
466
|
+
const Entry = await resolveReactAdapterRootComponent(worldStub, projectRoot);
|
|
467
|
+
const GameProvider = await resolveGameProviderForProject(projectRoot, candidate.worldId, 'react');
|
|
316
468
|
// C1 (phase-b/c1-react-mount): this layer's own react-world mount is a
|
|
317
469
|
// SECOND call site with the identical packaged-runtime dual-React-instance
|
|
318
|
-
// exposure `adapter-resolver.ts`'s `
|
|
470
|
+
// exposure `adapter-resolver.ts`'s `ReactRootAdapter.mount()` has — under
|
|
319
471
|
// the packaged runtime, `createElement`/`createRoot`/`flushSync` must come
|
|
320
472
|
// from the PROJECT's own react, not this module's (removed) static
|
|
321
473
|
// imports. See `resolveReactWorldMountRuntime`'s doc comment.
|
|
322
474
|
const { createElement, createRoot, flushSync } = await resolveReactWorldMountRuntime();
|
|
323
475
|
const game = createDesignTimeGame();
|
|
324
476
|
|
|
325
|
-
// D4 (B2) — story sources: the project's registry (states folded in as
|
|
326
|
-
// stories) first, an editor-side overlay last (wins on an id collision).
|
|
327
|
-
// `loadProjectUIRegistry` stays silent for the common no-registry case
|
|
328
|
-
// (its own `hasUiRegistry` probe) — awaited HERE (not the async cache slot
|
|
329
|
-
// `scene-ui-layer.ts`'s own registry load populates, which can still be
|
|
330
|
-
// settling when this layer mounts, since the two mounts race).
|
|
331
|
-
const registry = await loadProjectUIRegistry(projectRoot);
|
|
332
477
|
const storySources: ReadonlyArray<Record<string, UIStory>> = [
|
|
333
|
-
storiesFromRegistry(registry ?? {}),
|
|
334
478
|
getStoryOverlay(candidate.worldId) ?? {},
|
|
335
479
|
];
|
|
336
|
-
|
|
337
|
-
// and every pre-B3 fixture) keeps `ReactWorldAuthoringAdapter`'s hierarchy
|
|
338
|
-
// the plain DOM walk, byte-identical (§1's gate — "components NON-EMPTY
|
|
339
|
-
// specifically").
|
|
340
|
-
const catalogComponents = registry?.components;
|
|
480
|
+
const catalogComponents: UIComponentRegistry = {};
|
|
341
481
|
|
|
342
482
|
const root = createRoot(layer);
|
|
343
483
|
const renderState: LayerRenderState = { entryStory: null, isolation: null };
|
|
@@ -374,11 +514,7 @@ async function mountReactLayer(
|
|
|
374
514
|
? `story:${story.id}`
|
|
375
515
|
: '__none__',
|
|
376
516
|
onCaught,
|
|
377
|
-
children: createElement(
|
|
378
|
-
UIDataProvider,
|
|
379
|
-
{ data: createDataSource(story?.data ?? {}) },
|
|
380
|
-
createElement(GameProvider, { game }, view),
|
|
381
|
-
),
|
|
517
|
+
children: createElement(GameProvider, { game }, view),
|
|
382
518
|
}),
|
|
383
519
|
);
|
|
384
520
|
};
|
|
@@ -398,7 +534,6 @@ async function mountReactLayer(
|
|
|
398
534
|
...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
|
|
399
535
|
storySources,
|
|
400
536
|
worldId: candidate.worldId,
|
|
401
|
-
catalogComponents,
|
|
402
537
|
onStoryApplied: (story) => {
|
|
403
538
|
renderState.entryStory = story;
|
|
404
539
|
render((error: unknown) => {
|
|
@@ -427,7 +562,7 @@ async function mountReactLayer(
|
|
|
427
562
|
// subscription before dropping its DOM, so listeners don't accumulate across
|
|
428
563
|
// layer remounts (a leaked one is harmless — it early-returns on an empty echo —
|
|
429
564
|
// but this keeps teardown clean).
|
|
430
|
-
adapter.
|
|
565
|
+
adapter.disposeReactRootAdapter();
|
|
431
566
|
root.unmount();
|
|
432
567
|
},
|
|
433
568
|
};
|
|
@@ -464,23 +599,25 @@ function measure(container: HTMLElement): { width: number; height: number } {
|
|
|
464
599
|
* hit-test only, no hierarchy/stories) — an explicit, out-of-scope-by-design
|
|
465
600
|
* non-goal, not an oversight.
|
|
466
601
|
*
|
|
467
|
-
* B1 supports `scene`-declared pixi
|
|
602
|
+
* B1 supports `scene`-declared pixi roots only (the `.scn2d` display-only
|
|
468
603
|
* case named in the spec); an `entry`-based (imperative-setup) pixi world has
|
|
469
604
|
* no scene file to fetch and fails loudly here rather than being silently
|
|
470
605
|
* skipped — degrading to the same #18 error node as any other mount failure.
|
|
471
606
|
*/
|
|
472
607
|
async function mountPixiLayer(
|
|
473
|
-
candidate:
|
|
608
|
+
candidate: DesignTimeWorldDescriptor,
|
|
474
609
|
layer: HTMLElement,
|
|
475
610
|
container: HTMLElement,
|
|
611
|
+
projectRoot: string,
|
|
476
612
|
): Promise<LayerMountResult> {
|
|
477
613
|
if (!candidate.path) {
|
|
478
614
|
throw new Error(
|
|
479
615
|
`design-time pixi layer "${candidate.worldId}": no \`scene\` declared — nothing to mount ` +
|
|
480
|
-
'(B1 display-only mount supports scene-based pixi
|
|
616
|
+
'(B1 display-only mount supports scene-based pixi roots only, not `entry`).',
|
|
481
617
|
);
|
|
482
618
|
}
|
|
483
619
|
const sceneData = await fetchScene2DFile(candidate.path);
|
|
620
|
+
const registry = await loadPixiComponentRegistryForProject(projectRoot);
|
|
484
621
|
const canvas = document.createElement('canvas');
|
|
485
622
|
canvas.style.width = '100%';
|
|
486
623
|
canvas.style.height = '100%';
|
|
@@ -488,7 +625,7 @@ async function mountPixiLayer(
|
|
|
488
625
|
layer.appendChild(canvas);
|
|
489
626
|
|
|
490
627
|
const { width, height } = measure(container);
|
|
491
|
-
const adapter = new PixiSceneGameAdapter({ id: candidate.worldId, sceneData });
|
|
628
|
+
const adapter = new PixiSceneGameAdapter({ id: candidate.worldId, sceneData, registry });
|
|
492
629
|
const mounted = await adapter.mount({ canvas, width, height, transparent: true });
|
|
493
630
|
mounted.update?.(0); // one-shot render — see doc comment above
|
|
494
631
|
|
|
@@ -516,12 +653,11 @@ async function mountPixiLayer(
|
|
|
516
653
|
}
|
|
517
654
|
|
|
518
655
|
/**
|
|
519
|
-
* Mount
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
* (`exitEditModeAuthoring`) — see `ViewportPanel.tsx`'s wiring.
|
|
656
|
+
* Mount design-time React/Pixi worlds declared by `composite` into a caller's
|
|
657
|
+
* authoring `container`. `descriptors` narrows the mount to one world for the
|
|
658
|
+
* per-world document model; omission preserves the original multi-layer
|
|
659
|
+
* primitive for bounded callers and integration tests. Returns a disposer
|
|
660
|
+
* that must run before the owning composite is released.
|
|
525
661
|
*
|
|
526
662
|
* A no-op (returns a no-op disposer) when there is nothing to layer: a
|
|
527
663
|
* single-world project (back-compat floor, matches `installEditModeAuthoring`'s
|
|
@@ -537,16 +673,26 @@ async function mountPixiLayer(
|
|
|
537
673
|
* (whatever the entry's own JSX declares, e.g. tri-world's `hud-count`)
|
|
538
674
|
* simultaneously in one document — not just visually confusing, but a
|
|
539
675
|
* genuine test/automation hazard (two elements answering to one testid).
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
* touches the FOCUSED threejs world or the rest of the editor, which stay
|
|
544
|
-
* fully functional regardless of play state.
|
|
676
|
+
* Stop symmetrically rebuilds edit-mode authoring and restores the selected
|
|
677
|
+
* portable CSF preview. Play and edit roots never coexist, but neither does
|
|
678
|
+
* Play permanently destroy the authoring surface.
|
|
545
679
|
*/
|
|
680
|
+
let rebuildEventQueued = false;
|
|
681
|
+
|
|
682
|
+
function queueEditModeRebuild(): void {
|
|
683
|
+
if (rebuildEventQueued) return;
|
|
684
|
+
rebuildEventQueued = true;
|
|
685
|
+
queueMicrotask(() => {
|
|
686
|
+
rebuildEventQueued = false;
|
|
687
|
+
window.dispatchEvent(new CustomEvent(EDIT_MODE_REBUILD_EVENT));
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
546
691
|
export function mountDesignTimeLayers(
|
|
547
692
|
container: HTMLElement,
|
|
548
693
|
composite: CompositeAuthoringAdapter,
|
|
549
694
|
store: EditorStore,
|
|
695
|
+
descriptors?: readonly DesignTimeWorldDescriptor[],
|
|
550
696
|
): () => void {
|
|
551
697
|
// D4 (spec27 §8 "space-pan" row) — a fresh layer stack starts un-panned:
|
|
552
698
|
// every call here is a "world switch" (initial edit-mode install, or an
|
|
@@ -554,24 +700,66 @@ export function mountDesignTimeLayers(
|
|
|
554
700
|
// `installAll`), so a stale pan offset from a PREVIOUS layer stack must
|
|
555
701
|
// never carry over onto a different one.
|
|
556
702
|
resetWorldPan();
|
|
703
|
+
resetWorldCanvasViewport();
|
|
704
|
+
// ONE-WAY PLAY HANDOFF, ENTRY HALF (the fix for the CI-only
|
|
705
|
+
// `36-r3f-first-party.spec.ts` "roots: " hang — see below). The handoff
|
|
706
|
+
// documented in this module's doc comment was only ever implemented as an
|
|
707
|
+
// EXIT: the `store.subscribe` listener at the bottom of this function flips
|
|
708
|
+
// `playTeardownDone` and calls `suspendForPlay()` when play STARTS. That
|
|
709
|
+
// covers "layers were already mounted, then the user hit Play" — but it is
|
|
710
|
+
// a TRANSITION listener, so it does nothing at all when play is ALREADY
|
|
711
|
+
// running by the time this function is first called.
|
|
712
|
+
//
|
|
713
|
+
// That ordering is real, and it is exactly what CI hits. `ViewportPanel`'s
|
|
714
|
+
// `installAll()` (the only caller) runs at the tail of an async chain —
|
|
715
|
+
// `loadInitialScene()` -> `installEditModeAuthoringForProject()` (which
|
|
716
|
+
// itself fetches `vgai.game.json`) -> here. The e2e clicks Play the moment
|
|
717
|
+
// the editor shell is interactive, so on a slow/contended host (CI's
|
|
718
|
+
// 4-core SwiftShader runners) Play wins that race and `store.playState` is
|
|
719
|
+
// already `'playing'` when we arrive. Without this guard we then mount a
|
|
720
|
+
// design-time react layer anyway — rendering the project's REAL entry
|
|
721
|
+
// component (`examples/r3f-probe/src/hud.tsx`, `data-testid="hud-roots"`)
|
|
722
|
+
// against `createDesignTimeGame()`'s INERT `Game`, whose `roots` array is
|
|
723
|
+
// `[]` forever and whose loop never ticks (see that function's doc comment).
|
|
724
|
+
// The HUD therefore renders literally `"roots: "` — an empty list, from a
|
|
725
|
+
// Game that never registers a world — and, because no further playState
|
|
726
|
+
// TRANSITION ever arrives, `suspendForPlay()` never fires and the layer is
|
|
727
|
+
// never torn down. That is precisely the reported CI symptom ("the HUD
|
|
728
|
+
// element IS mounted; the roots list is EMPTY", for the full 15s budget),
|
|
729
|
+
// and it also carries the duplicate-`data-testid` hazard this module's own
|
|
730
|
+
// doc comment already calls out for the transition case.
|
|
731
|
+
//
|
|
732
|
+
// Play mode owns the react world outright while it runs (it mounts its own
|
|
733
|
+
// live tree over a REAL, ticking Game). So the correct behavior when play
|
|
734
|
+
// is already up is the same one `suspendForPlay()` produces: mount NOTHING.
|
|
735
|
+
// Stop requests a clean edit-mode rebuild below; this entry guard therefore
|
|
736
|
+
// needs to listen for Stop even though it mounted no layers itself.
|
|
737
|
+
if (store.playState !== 'stopped') {
|
|
738
|
+
let disposed = false;
|
|
739
|
+
let rebuildRequested = false;
|
|
740
|
+
const unsubscribe = store.subscribe(() => {
|
|
741
|
+
if (disposed || rebuildRequested || store.playState !== 'stopped') return;
|
|
742
|
+
rebuildRequested = true;
|
|
743
|
+
if (!disposed) queueEditModeRebuild();
|
|
744
|
+
});
|
|
745
|
+
return () => {
|
|
746
|
+
disposed = true;
|
|
747
|
+
unsubscribe();
|
|
748
|
+
};
|
|
749
|
+
}
|
|
557
750
|
const projectRoot = getCurrentProject()?.rootPath;
|
|
558
|
-
const candidates = readCandidates(composite);
|
|
751
|
+
const candidates = descriptors ? [...descriptors] : readCandidates(composite);
|
|
559
752
|
// C3 — the scene-UI child (at most one, the focused world's): already a
|
|
560
753
|
// LIVE `UIAuthoringAdapter` at composite-install time (`edit-mode-
|
|
561
754
|
// authoring.ts` — no Boundary-to-live upgrade dance needed here, unlike
|
|
562
755
|
// react/pixi), so it's a mount candidate in its own right rather than one
|
|
563
756
|
// of `readCandidates`'s Boundary-only entries.
|
|
564
|
-
|
|
565
|
-
const uiAdapter =
|
|
566
|
-
uiChildEntry && uiChildEntry.adapter instanceof UIAuthoringAdapter
|
|
567
|
-
? uiChildEntry.adapter
|
|
568
|
-
: null;
|
|
569
|
-
if ((candidates.length === 0 && !uiAdapter) || !projectRoot) {
|
|
757
|
+
if (candidates.length === 0 || !projectRoot) {
|
|
570
758
|
return () => {};
|
|
571
759
|
}
|
|
572
760
|
|
|
573
761
|
// Bottom-to-top paint order — mirrors `create-runtime.ts`'s own stacking
|
|
574
|
-
// pass, over exactly the
|
|
762
|
+
// pass, over exactly the roots this module lays out (react/pixi
|
|
575
763
|
// candidates only; the focused threejs world keeps its untouched canvas
|
|
576
764
|
// slot below all of these — see the module doc comment).
|
|
577
765
|
const sorted = stackOrder(candidates.map((c) => ({ id: c.worldId, zOrder: c.zOrder })));
|
|
@@ -587,21 +775,7 @@ export function mountDesignTimeLayers(
|
|
|
587
775
|
const upgraded = new Set<string>();
|
|
588
776
|
let torndown = false; // the whole-module disposer (ViewportPanel unmount) ran
|
|
589
777
|
let playTeardownDone = false; // the one-way play-mode handoff (see doc comment) ran
|
|
590
|
-
|
|
591
|
-
// C3 — the scene-UI layer's own disposer, tracked separately from
|
|
592
|
-
// `disposers`/`layers` (react/pixi's Boundary-upgrade bookkeeping doesn't
|
|
593
|
-
// apply to it — the UI child's adapter stays live throughout; only its DOM
|
|
594
|
-
// layer needs tearing down, here and on the play handoff below).
|
|
595
|
-
let disposeUILayer: (() => void) | undefined;
|
|
596
|
-
if (uiAdapter && uiChildEntry) {
|
|
597
|
-
disposeUILayer = mountSceneUILayer(
|
|
598
|
-
container,
|
|
599
|
-
uiChildEntry.worldId,
|
|
600
|
-
uiAdapter,
|
|
601
|
-
store,
|
|
602
|
-
BASE_Z_INDEX + orderedCandidates.length,
|
|
603
|
-
);
|
|
604
|
-
}
|
|
778
|
+
let rebuildRequested = false;
|
|
605
779
|
|
|
606
780
|
function disposeEverything(): void {
|
|
607
781
|
for (const dispose of disposers.splice(0)) {
|
|
@@ -616,22 +790,20 @@ export function mountDesignTimeLayers(
|
|
|
616
790
|
}
|
|
617
791
|
|
|
618
792
|
/**
|
|
619
|
-
*
|
|
793
|
+
* Play handoff: dispose the design-time layers AND revert every
|
|
620
794
|
* upgraded world back to a `BoundaryAuthoringAdapter` disclosure node
|
|
621
795
|
* (#18 — never silently empty). Not called on the whole-module disposer
|
|
622
796
|
* (`torndown`, ViewportPanel unmount), where the composite is going away
|
|
623
|
-
* anyway.
|
|
624
|
-
*
|
|
797
|
+
* anyway. Stop dispatches one edit-mode rebuild after the live runtime has
|
|
798
|
+
* torn down, restoring the CSF-backed design surface. The scene-UI layer's DOM is
|
|
625
799
|
* torn down the same way (play mode renders scene-UI through the real
|
|
626
800
|
* engine's HUD mount instead — two live DOM trees for the same testids
|
|
627
801
|
* would be the same hazard `design-time-layers.ts`'s own doc comment
|
|
628
|
-
* already calls out for react
|
|
802
|
+
* already calls out for react roots); its adapter is left alone (still
|
|
629
803
|
* live, just without a mounted layer) since there is no Boundary form of it.
|
|
630
804
|
*/
|
|
631
805
|
function suspendForPlay(): void {
|
|
632
806
|
disposeEverything();
|
|
633
|
-
disposeUILayer?.();
|
|
634
|
-
disposeUILayer = undefined;
|
|
635
807
|
for (const worldId of upgraded) {
|
|
636
808
|
const c = candidateByWorld.get(worldId);
|
|
637
809
|
if (!c) continue;
|
|
@@ -647,7 +819,7 @@ export function mountDesignTimeLayers(
|
|
|
647
819
|
new BoundaryAuthoringAdapter(
|
|
648
820
|
store,
|
|
649
821
|
info,
|
|
650
|
-
'Design-time layer suspended by play mode —
|
|
822
|
+
'Design-time layer suspended by play mode — Stop restores it.',
|
|
651
823
|
),
|
|
652
824
|
);
|
|
653
825
|
}
|
|
@@ -662,7 +834,7 @@ export function mountDesignTimeLayers(
|
|
|
662
834
|
const mountPromise: Promise<LayerMountResult> =
|
|
663
835
|
candidate.kind === 'react'
|
|
664
836
|
? mountReactLayer(candidate, layer, projectRoot, store)
|
|
665
|
-
: mountPixiLayer(candidate, layer, container);
|
|
837
|
+
: mountPixiLayer(candidate, layer, container, projectRoot);
|
|
666
838
|
|
|
667
839
|
mountPromise
|
|
668
840
|
.then((result) => {
|
|
@@ -672,7 +844,6 @@ export function mountDesignTimeLayers(
|
|
|
672
844
|
}
|
|
673
845
|
if (result.adapter) {
|
|
674
846
|
composite.replaceChild(candidate.worldId, result.adapter);
|
|
675
|
-
registerEditModeChild(result.adapter);
|
|
676
847
|
upgraded.add(candidate.worldId);
|
|
677
848
|
} else if (result.pick) {
|
|
678
849
|
// D12 (B4) — a pixi layer produces a `pick`, not a full adapter
|
|
@@ -738,6 +909,9 @@ export function mountDesignTimeLayers(
|
|
|
738
909
|
const unsubPan = subscribeWorldPan(() => {
|
|
739
910
|
for (const layer of layers.values()) applyPanTransform(layer);
|
|
740
911
|
});
|
|
912
|
+
const unsubViewport = subscribeWorldCanvasViewport(() => {
|
|
913
|
+
for (const layer of layers.values()) applyPanTransform(layer);
|
|
914
|
+
});
|
|
741
915
|
|
|
742
916
|
const unsubStore = store.subscribe(() => {
|
|
743
917
|
if (!playTeardownDone && store.playState !== 'stopped') {
|
|
@@ -745,6 +919,15 @@ export function mountDesignTimeLayers(
|
|
|
745
919
|
suspendForPlay();
|
|
746
920
|
return;
|
|
747
921
|
}
|
|
922
|
+
if (playTeardownDone && !rebuildRequested && store.playState === 'stopped' && !torndown) {
|
|
923
|
+
rebuildRequested = true;
|
|
924
|
+
// Reuse ViewportPanel's one authoritative reinstall path. The event
|
|
925
|
+
// causes this mount's disposer to run before the replacement composite
|
|
926
|
+
// and layers are installed, so no React root or adapter survives across
|
|
927
|
+
// the Play -> Stop boundary.
|
|
928
|
+
if (!torndown) queueEditModeRebuild();
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
748
931
|
// Session toggles (eye/interactive) change via `store.notifyIngestEdit()`
|
|
749
932
|
// (`GameHierarchy.tsx`'s row affordances) — this is a cheap style
|
|
750
933
|
// re-apply, never a remount.
|
|
@@ -770,7 +953,7 @@ export function mountDesignTimeLayers(
|
|
|
770
953
|
torndown = true;
|
|
771
954
|
unsubStore();
|
|
772
955
|
unsubPan();
|
|
956
|
+
unsubViewport();
|
|
773
957
|
disposeEverything();
|
|
774
|
-
disposeUILayer?.();
|
|
775
958
|
};
|
|
776
959
|
}
|