@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
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated story preview mount — C3 (spec §9 "A story may be opened in an
|
|
3
|
+
* isolated editor preview"). Renders one composed story (`ComposedProjectStory`
|
|
4
|
+
* from `compose-project-stories.ts`) into a SEPARATE `react-dom/client` root,
|
|
5
|
+
* not a child of the editor's own React tree: the preview container div is
|
|
6
|
+
* handed to its own `createRoot`, so a story's own React version/context/
|
|
7
|
+
* error state can never leak into (or be affected by) the editor shell's
|
|
8
|
+
* tree — the same isolation a Storybook iframe gives you, achieved here with
|
|
9
|
+
* a dedicated root instead (this editor has no existing iframe-preview
|
|
10
|
+
* pattern to reuse — see the module doc comment on `StoriesPanel.tsx`).
|
|
11
|
+
*
|
|
12
|
+
* Error handling (spec §9 "Errors identify the source module and story
|
|
13
|
+
* export"): a story that throws while rendering is caught by
|
|
14
|
+
* `StoryPreviewErrorBoundary` (mirrors `ToolHost.tsx`'s `ToolErrorBoundary`)
|
|
15
|
+
* and reported with BOTH the module path and the story export name — one
|
|
16
|
+
* throwing story never takes down the panel or another story's preview.
|
|
17
|
+
*
|
|
18
|
+
* Play functions (spec §9 "supported play functions mount... optional but
|
|
19
|
+
* nice"): `runPlay` below calls the composed story's own `.play()` (Storybook
|
|
20
|
+
* populates it via `composeStories` when the CSF export declares one) inside
|
|
21
|
+
* the SAME error containment, so a throwing play function reports the same
|
|
22
|
+
* module+export naming instead of crashing the preview. `play()` is called
|
|
23
|
+
* with `{ canvasElement }` set to the isolated preview root's own container —
|
|
24
|
+
* without it, Storybook's portable-story `play()` defaults `canvasElement` to
|
|
25
|
+
* `document.body` (confirmed in `storybook/dist/preview-api/index.cjs`'s
|
|
26
|
+
* `composeStory`: `g.canvasElement ??= globalThis?.document?.body`), which
|
|
27
|
+
* would let a story's play() query/interact with the ENTIRE page instead of
|
|
28
|
+
* just its own isolated preview.
|
|
29
|
+
*
|
|
30
|
+
* Loaders (spec §9 "loaders ... mount through Storybook portable-story
|
|
31
|
+
* APIs"): a composed story only runs its `loaders` when `.load()` is
|
|
32
|
+
* explicitly awaited before render. Confirmed directly in Storybook 9's own
|
|
33
|
+
* `composeStory` (`storybook/dist/preview-api/index.cjs`): the render
|
|
34
|
+
* function reads a module-closure variable (`x`) for `loaded` data, and that
|
|
35
|
+
* variable is populated ONLY inside `load: async () => { ...; f.loaded =
|
|
36
|
+
* await c.applyLoaders(f); ...; x = f; }` — i.e. `applyLoaders` (which runs
|
|
37
|
+
* every `loaders` entry) never runs unless `.load()` itself runs first.
|
|
38
|
+
* Rendering the composed component directly (no `.load()`) leaves `loaded`
|
|
39
|
+
* as `{}`. `mountIsolatedStory` below is therefore async and awaits
|
|
40
|
+
* `StoryComponent.load()` before calling `root.render(...)`.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
44
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
45
|
+
import type { ComposedStoryFn } from 'storybook/internal/types';
|
|
46
|
+
import { themeVars } from '../design-system';
|
|
47
|
+
|
|
48
|
+
interface StoryPreviewErrorBoundaryProps {
|
|
49
|
+
modulePath: string;
|
|
50
|
+
storyName: string;
|
|
51
|
+
children: ReactNode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface StoryPreviewErrorBoundaryState {
|
|
55
|
+
error: Error | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Crash containment for one mounted story — names both the source module
|
|
59
|
+
* AND the story export in the fallback UI and the console, per spec §9. */
|
|
60
|
+
export class StoryPreviewErrorBoundary extends Component<
|
|
61
|
+
StoryPreviewErrorBoundaryProps,
|
|
62
|
+
StoryPreviewErrorBoundaryState
|
|
63
|
+
> {
|
|
64
|
+
override state: StoryPreviewErrorBoundaryState = { error: null };
|
|
65
|
+
|
|
66
|
+
static getDerivedStateFromError(error: Error): StoryPreviewErrorBoundaryState {
|
|
67
|
+
return { error };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
71
|
+
// biome-ignore lint/suspicious/noConsole: deliberate, greppable — a crashed story preview must be diagnosable from the console (spec §9 "errors identify the source module and story export"), mirroring ToolHost.tsx's ToolErrorBoundary
|
|
72
|
+
console.error(
|
|
73
|
+
`[stories] ${this.props.modulePath} — story "${this.props.storyName}" crashed while rendering:`,
|
|
74
|
+
error,
|
|
75
|
+
info.componentStack,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override render(): ReactNode {
|
|
80
|
+
if (this.state.error) {
|
|
81
|
+
return (
|
|
82
|
+
<div style={{ padding: 12, fontSize: 12, color: themeVars.semantic.danger, maxWidth: 640 }}>
|
|
83
|
+
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
|
84
|
+
Story crashed: <code>{this.props.modulePath}</code> — export{' '}
|
|
85
|
+
<code>{this.props.storyName}</code>
|
|
86
|
+
</div>
|
|
87
|
+
<div style={{ whiteSpace: 'pre-wrap' }}>{String(this.state.error)}</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return this.props.children;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** One container's isolated root plus a generation counter — see
|
|
96
|
+
* `mountIsolatedStory` below for why the counter (not just the `Root`
|
|
97
|
+
* object's identity) is what a stale `unmount()` must check. `root` is
|
|
98
|
+
* `undefined` for the brief window between a mount call CLAIMING a
|
|
99
|
+
* generation (synchronously) and that same call's `await
|
|
100
|
+
* StoryComponent.load()` resolving — no `Root` exists yet for a container
|
|
101
|
+
* that has never been rendered into before (the very first mount), so the
|
|
102
|
+
* reservation itself can't supply one. */
|
|
103
|
+
interface ContainerMount {
|
|
104
|
+
root: Root | undefined;
|
|
105
|
+
generation: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// One isolated `react-dom/client` root per container, reused across repeated
|
|
109
|
+
// `mountIsolatedStory` calls (e.g. a story switch: `StoriesPanel.tsx`'s
|
|
110
|
+
// `StoryPreviewHost` effect re-fires for the SAME DOM node with a new
|
|
111
|
+
// `StoryComponent`). Calling `createRoot` twice on the SAME node is a hard
|
|
112
|
+
// react-dom warning ("already been passed to createRoot() before") — this
|
|
113
|
+
// map is the guard: a second `mountIsolatedStory` on the same container
|
|
114
|
+
// `.render()`s onto the EXISTING root instead of creating a new one, exactly
|
|
115
|
+
// react-dom's own documented fix for that warning.
|
|
116
|
+
const mountsByContainer = new WeakMap<HTMLElement, ContainerMount>();
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Mount one composed story into `container` via its OWN `createRoot` —
|
|
120
|
+
* isolated from whatever React root `container` itself lives inside.
|
|
121
|
+
* Returns an `unmount` the caller runs on cleanup (e.g. story switch or
|
|
122
|
+
* panel close) — same lifecycle discipline every other editor mount point
|
|
123
|
+
* (`XStateMachineInspector.test.tsx`'s harness, `ToolHost`) uses. Safe to
|
|
124
|
+
* call again on the same `container` before a previous `unmount()` has
|
|
125
|
+
* landed (see `mountsByContainer` above) — the isolated root is reused, and
|
|
126
|
+
* `unmount()` (below) removes it from the container entirely rather than
|
|
127
|
+
* merely clearing its content, so callers who intend a REAL teardown (panel
|
|
128
|
+
* close) still get one.
|
|
129
|
+
*
|
|
130
|
+
* Async: awaits `StoryComponent.load()` (runs the story's `loaders`, per the
|
|
131
|
+
* module doc comment above) BEFORE the first render. The generation counter
|
|
132
|
+
* is claimed SYNCHRONOUSLY (before the `await`), exactly like the sync
|
|
133
|
+
* version did — so a second `mountIsolatedStory` call on the same container
|
|
134
|
+
* (e.g. the user switches stories again while this one's `.load()` is still
|
|
135
|
+
* in flight) still bumps the generation immediately and makes it visible to
|
|
136
|
+
* this in-flight call. After the await, this call re-reads the container's
|
|
137
|
+
* CURRENT generation and bails out (never creates/renders a root, never
|
|
138
|
+
* touches `mountsByContainer`) if it's no longer the current one — so a
|
|
139
|
+
* stale `.load()` resolving late can never win a render race against a
|
|
140
|
+
* newer story switch, even though nothing was mounted yet for it to
|
|
141
|
+
* unmount.
|
|
142
|
+
*/
|
|
143
|
+
export async function mountIsolatedStory(
|
|
144
|
+
container: HTMLElement,
|
|
145
|
+
modulePath: string,
|
|
146
|
+
storyName: string,
|
|
147
|
+
StoryComponent: ComposedStoryFn,
|
|
148
|
+
// W2 (story documents): optional per-mount prop overrides. Portable
|
|
149
|
+
// stories are plain React components whose composed args are DEFAULTS —
|
|
150
|
+
// Storybook's own documented portable-story contract lets a caller pass
|
|
151
|
+
// partial props over them (`<Story label="…" />`). The story-document
|
|
152
|
+
// Inspector's editable args and its action-logging spies ride this;
|
|
153
|
+
// omitted (every pre-W2 caller) the render is byte-identical to before.
|
|
154
|
+
overrideProps?: Record<string, unknown>,
|
|
155
|
+
): Promise<{ root: Root; unmount: () => void } | null> {
|
|
156
|
+
const existing = mountsByContainer.get(container);
|
|
157
|
+
// Bumped on EVERY mount, including a reuse of the same `Root` object (a
|
|
158
|
+
// story switch) — this is the piece a same-root reuse needs that plain
|
|
159
|
+
// `Root`-identity checking (an earlier version of this function) doesn't
|
|
160
|
+
// give you: reusing the root means two DIFFERENT stories' mounts can share
|
|
161
|
+
// the exact same `Root` instance, so a stale `unmount()` comparing
|
|
162
|
+
// `Root`s would see "yes, that's still the registered root" and tear down
|
|
163
|
+
// the NEWER story's render a moment after it landed. Comparing the
|
|
164
|
+
// generation this specific `mountIsolatedStory` call captured against the
|
|
165
|
+
// container's CURRENT generation instead correctly recognizes "a newer
|
|
166
|
+
// mount has since claimed this container" even when the root itself
|
|
167
|
+
// didn't change. Claimed HERE, synchronously, before the `load()` await
|
|
168
|
+
// below — so a newer call arriving while this one awaits sees (and bumps
|
|
169
|
+
// past) this generation immediately, rather than racing to claim the same
|
|
170
|
+
// number once both awaits resolve.
|
|
171
|
+
const generation = (existing?.generation ?? 0) + 1;
|
|
172
|
+
mountsByContainer.set(container, { root: existing?.root, generation });
|
|
173
|
+
|
|
174
|
+
// Loaders (spec §9): must run — and their `loaded` data become part of the
|
|
175
|
+
// composed story's render-time context — before the story is ever
|
|
176
|
+
// rendered. See the module doc comment for why this can't be skipped.
|
|
177
|
+
await StoryComponent.load();
|
|
178
|
+
|
|
179
|
+
// Re-check AFTER the await: if a newer `mountIsolatedStory` call (story
|
|
180
|
+
// switch) has since claimed this container, its generation will have
|
|
181
|
+
// moved past `generation` above — this stale call must not create a root
|
|
182
|
+
// or render into a container a newer story already owns (or is about to).
|
|
183
|
+
const afterLoad = mountsByContainer.get(container);
|
|
184
|
+
if (!afterLoad || afterLoad.generation !== generation) return null;
|
|
185
|
+
|
|
186
|
+
const root = existing?.root ?? createRoot(container);
|
|
187
|
+
mountsByContainer.set(container, { root, generation });
|
|
188
|
+
|
|
189
|
+
root.render(
|
|
190
|
+
<StoryPreviewErrorBoundary modulePath={modulePath} storyName={storyName}>
|
|
191
|
+
{/* `ComposedStoryFn` is a callable React function component
|
|
192
|
+
(Storybook's portable-story contract) — its own composed
|
|
193
|
+
args/decorators are already baked in by `composeStories`;
|
|
194
|
+
`overrideProps` (when given) override individual args per that same
|
|
195
|
+
contract. */}
|
|
196
|
+
<StoryComponent {...(overrideProps ?? {})} />
|
|
197
|
+
</StoryPreviewErrorBoundary>,
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
root,
|
|
201
|
+
// Deferred to a microtask, deliberately NOT called synchronously:
|
|
202
|
+
// `unmount` is returned as `StoryPreviewHost`'s effect cleanup, and
|
|
203
|
+
// React runs an effect's cleanup SYNCHRONOUSLY as part of its own
|
|
204
|
+
// render/commit pass whenever the OUTER tree unmounts (e.g. the whole
|
|
205
|
+
// Stories panel closing). React tracks "is any root currently
|
|
206
|
+
// rendering" as a single GLOBAL flag for the whole call stack
|
|
207
|
+
// (`executionContext`, react-dom-client.development.js) — so calling
|
|
208
|
+
// THIS (independent, inner) root's `.unmount()` synchronously from
|
|
209
|
+
// inside that outer pass trips "Attempted to synchronously unmount a
|
|
210
|
+
// root while React was already rendering" even though the inner root
|
|
211
|
+
// itself has nothing in flight. Queuing the call for the next microtask
|
|
212
|
+
// runs it once the outer commit has fully finished and cleared that
|
|
213
|
+
// flag, which is the documented fix for exactly this nested-root
|
|
214
|
+
// pattern (no functional delay — the isolated root is still torn down
|
|
215
|
+
// effectively immediately).
|
|
216
|
+
unmount: () => {
|
|
217
|
+
queueMicrotask(() => {
|
|
218
|
+
// Only the mount that's STILL the container's current generation
|
|
219
|
+
// actually tears the root down — see the `generation` doc comment
|
|
220
|
+
// above for why this can't just compare `Root` identity.
|
|
221
|
+
const current = mountsByContainer.get(container);
|
|
222
|
+
if (!current || current.generation !== generation || !current.root) return;
|
|
223
|
+
mountsByContainer.delete(container);
|
|
224
|
+
current.root.unmount();
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Run a composed story's `.play()` if it declared one, inside the same
|
|
232
|
+
* module+export-naming error containment as render crashes. Returns whether
|
|
233
|
+
* a play function existed and ran ok — the caller (`StoriesPanel`) uses this
|
|
234
|
+
* to show a "no play function" state distinctly from a failure.
|
|
235
|
+
*
|
|
236
|
+
* `canvasElement` is passed through to `.play({ canvasElement })` — the
|
|
237
|
+
* composed story's own `play` accepts `Partial<StoryContext>` (confirmed in
|
|
238
|
+
* `storybook/dist/types/index.d.ts`: `play?: (context?: Partial<StoryContext<...>>)
|
|
239
|
+
* => Promise<void>`, and `StoryContext.canvasElement: TRenderer['canvasElement']`).
|
|
240
|
+
* Without it, Storybook's `composeStory` defaults `canvasElement` to
|
|
241
|
+
* `globalThis.document.body` (`storybook/dist/preview-api/index.cjs`:
|
|
242
|
+
* `g.canvasElement ??= globalThis?.document?.body`), which would let a
|
|
243
|
+
* story's play() query/interact with the WHOLE page instead of just its own
|
|
244
|
+
* isolated preview root.
|
|
245
|
+
*/
|
|
246
|
+
export async function runStoryPlay(
|
|
247
|
+
modulePath: string,
|
|
248
|
+
storyName: string,
|
|
249
|
+
StoryComponent: ComposedStoryFn,
|
|
250
|
+
canvasElement: HTMLElement,
|
|
251
|
+
): Promise<{ ran: boolean; error?: string }> {
|
|
252
|
+
if (typeof StoryComponent.play !== 'function') return { ran: false };
|
|
253
|
+
try {
|
|
254
|
+
await StoryComponent.play({ canvasElement });
|
|
255
|
+
return { ran: true };
|
|
256
|
+
} catch (err) {
|
|
257
|
+
const message = String(err instanceof Error ? err.message : err);
|
|
258
|
+
// biome-ignore lint/suspicious/noConsole: deliberate, greppable — a throwing play() must be diagnosable from the console, same naming discipline as a render crash
|
|
259
|
+
console.error(`[stories] ${modulePath} — story "${storyName}"'s play() threw:`, err);
|
|
260
|
+
return {
|
|
261
|
+
ran: true,
|
|
262
|
+
error: `[stories] ${modulePath} — story "${storyName}"'s play() failed: ${message}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture CSF module for `story-documents.test.tsx` — like
|
|
3
|
+
* `button-stories.fixture.tsx` but with a FUNCTION-valued arg (`onFire`), so
|
|
4
|
+
* the story-document action-logging spies (W2 — the actions-addon semantic:
|
|
5
|
+
* function args are wrapped at mount and calls from the story's own rendered
|
|
6
|
+
* component are logged) can be exercised against real composed CSF. Kept
|
|
7
|
+
* separate from the button fixture so `compose-project-stories.test.tsx`'s
|
|
8
|
+
* assertions over that module's args stay untouched.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
12
|
+
|
|
13
|
+
interface FixtureActionButtonProps {
|
|
14
|
+
label: string;
|
|
15
|
+
onFire?: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function FixtureActionButton({ label, onFire }: FixtureActionButtonProps) {
|
|
19
|
+
return (
|
|
20
|
+
<button type="button" data-testid="fixture-action-button" onClick={() => onFire?.()}>
|
|
21
|
+
{label}
|
|
22
|
+
</button>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const meta = {
|
|
27
|
+
title: 'Fixture/ActionButton',
|
|
28
|
+
component: FixtureActionButton,
|
|
29
|
+
args: {
|
|
30
|
+
onFire: () => {
|
|
31
|
+
// Authored handler — the W2 spy must still call through to this.
|
|
32
|
+
(globalThis as { __fixtureActionFired?: number }).__fixtureActionFired =
|
|
33
|
+
((globalThis as { __fixtureActionFired?: number }).__fixtureActionFired ?? 0) + 1;
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} satisfies Meta<typeof FixtureActionButton>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
type Story = StoryObj<typeof meta>;
|
|
41
|
+
|
|
42
|
+
export const Primary: Story = {
|
|
43
|
+
args: { label: 'Fire' },
|
|
44
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture CSF module for `compose-project-stories.test.tsx` — a minimal,
|
|
3
|
+
* real CSF3 file (title, component, args, a decorator, two named stories)
|
|
4
|
+
* mirroring `packages/editor/template/src/ui/Button.stories.tsx` in shape,
|
|
5
|
+
* kept local (rather than imported) so this test doesn't reach across into
|
|
6
|
+
* the template package and stays fast/isolated, same precedent
|
|
7
|
+
* `character-animation-machine.fixture.ts` sets for `XStateMachineInspector.test.tsx`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
11
|
+
|
|
12
|
+
interface FixtureButtonProps {
|
|
13
|
+
label: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function FixtureButton({ label, disabled = false }: FixtureButtonProps) {
|
|
18
|
+
return (
|
|
19
|
+
<button type="button" disabled={disabled} data-testid="fixture-button">
|
|
20
|
+
{label}
|
|
21
|
+
</button>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const meta = {
|
|
26
|
+
title: 'Fixture/Button',
|
|
27
|
+
component: FixtureButton,
|
|
28
|
+
parameters: {
|
|
29
|
+
vgai: { root: 'hud', defaultStory: 'Default' },
|
|
30
|
+
},
|
|
31
|
+
decorators: [
|
|
32
|
+
(Story) => (
|
|
33
|
+
<div data-testid="fixture-decorator">
|
|
34
|
+
<Story />
|
|
35
|
+
</div>
|
|
36
|
+
),
|
|
37
|
+
],
|
|
38
|
+
} satisfies Meta<typeof FixtureButton>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
|
|
42
|
+
type Story = StoryObj<typeof meta>;
|
|
43
|
+
|
|
44
|
+
export const Default: Story = {
|
|
45
|
+
args: { label: 'Fire' },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const DisabledStory: Story = {
|
|
49
|
+
args: { label: 'Fire', disabled: true },
|
|
50
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable-story mounting — C3 (spec §9). Wraps the REAL Storybook 9
|
|
3
|
+
* portable-stories API (`composeStories`/`setProjectAnnotations` from
|
|
4
|
+
* `@storybook/react`, confirmed against `node_modules/@storybook/react/dist/
|
|
5
|
+
* index.d.ts`) so a dynamically-imported CSF module (`*.stories.tsx`/
|
|
6
|
+
* `*.stories.ts`, found by `story-discovery.ts`) becomes a map of named,
|
|
7
|
+
* fully-composed React components — args/decorators/play all applied by
|
|
8
|
+
* Storybook itself, never re-implemented here. `loaders` are ALSO applied by
|
|
9
|
+
* Storybook itself, but only once the caller awaits the composed story's own
|
|
10
|
+
* `.load()` — Storybook 9's portable-story contract requires that explicit
|
|
11
|
+
* call before render (confirmed in `storybook/dist/preview-api/index.cjs`'s
|
|
12
|
+
* `composeStory`: loader data is populated inside `load()`, and the render
|
|
13
|
+
* function only picks it up if `load()` already ran). `mountIsolatedStory`
|
|
14
|
+
* (`StoryPreviewMount.tsx`) is the caller that does this, awaiting `.load()`
|
|
15
|
+
* before its first render.
|
|
16
|
+
*
|
|
17
|
+
* `setProjectAnnotations` "should be run a single time" (its own doc
|
|
18
|
+
* comment) — `ensureProjectAnnotations` below does that exactly once per
|
|
19
|
+
* editor session, from the project's `.storybook/preview.ts(x)` if the
|
|
20
|
+
* project has one, else an empty array (Storybook's own default). This is
|
|
21
|
+
* the ONLY place project annotations are threaded; no VGAI-specific
|
|
22
|
+
* override.
|
|
23
|
+
*
|
|
24
|
+
* No duplicate story format anywhere: `composeProjectStories` takes the
|
|
25
|
+
* loaded module namespace as-is (whatever `story-discovery.ts`'s `load()`
|
|
26
|
+
* returned from Vite's `/@fs/` dynamic import) and hands it straight to
|
|
27
|
+
* `composeStories` — the CSF module on disk is read directly and is the only
|
|
28
|
+
* story definition that ever exists.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { composeStories, setProjectAnnotations } from '@storybook/react';
|
|
32
|
+
import type { ComposedStoryFn } from 'storybook/internal/types';
|
|
33
|
+
|
|
34
|
+
let projectAnnotationsApplied = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Apply the project's `.storybook/preview` annotations to the portable-story
|
|
38
|
+
* runtime exactly once per session (repeat calls are no-ops — `composeStories`
|
|
39
|
+
* itself picks up whatever `setProjectAnnotations` last set, and re-calling
|
|
40
|
+
* with a DIFFERENT project mid-session would be a footgun, not a feature. A
|
|
41
|
+
* project switch reloads the whole editor page today, which resets this
|
|
42
|
+
* module-level flag along with everything else).
|
|
43
|
+
*
|
|
44
|
+
* `previewAnnotations` is `undefined` when the project has no
|
|
45
|
+
* `.storybook/preview.ts(x)` (this repo ships none — no `.storybook` folder
|
|
46
|
+
* anywhere, confirmed by search) — Storybook's own documented default is an
|
|
47
|
+
* empty project-annotations object, which `setProjectAnnotations([])`
|
|
48
|
+
* already models via `composeStories`'s own `?? []` fallback.
|
|
49
|
+
*/
|
|
50
|
+
export function ensureProjectAnnotations(previewAnnotations?: unknown): void {
|
|
51
|
+
if (projectAnnotationsApplied) return;
|
|
52
|
+
projectAnnotationsApplied = true;
|
|
53
|
+
// biome-ignore lint/suspicious/noExplicitAny: `setProjectAnnotations` accepts the project's raw preview module namespace, whose shape is Storybook's own (NamedOrDefaultProjectAnnotations) — not something this editor declares.
|
|
54
|
+
setProjectAnnotations((previewAnnotations ?? []) as any);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Test-only escape hatch: `ensureProjectAnnotations` is deliberately
|
|
58
|
+
* once-per-session: this resets that latch so each test file gets a clean
|
|
59
|
+
* slate rather than leaking a fixture's annotations into a later assertion. */
|
|
60
|
+
export function _resetProjectAnnotationsForTest(): void {
|
|
61
|
+
projectAnnotationsApplied = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One successfully composed named story, ready for the preview UI to mount. */
|
|
65
|
+
export interface ComposedProjectStory {
|
|
66
|
+
/** The CSF export name, e.g. `Primary`. */
|
|
67
|
+
name: string;
|
|
68
|
+
/** Storybook's stable composed id (title + export), when supplied. */
|
|
69
|
+
id: string;
|
|
70
|
+
/** Storybook's human-facing story name, falling back to the export name. */
|
|
71
|
+
label: string;
|
|
72
|
+
/** CSF meta title when explicitly authored (e.g. `Game/2048`). */
|
|
73
|
+
title?: string;
|
|
74
|
+
/** Fully-composed Storybook parameters. VGAI only reads the namespaced
|
|
75
|
+
* `parameters.vgai` association; every other parameter remains Storybook's. */
|
|
76
|
+
parameters: Record<string, unknown>;
|
|
77
|
+
/** Fully composed initial args. Kept as Storybook produced them so the
|
|
78
|
+
* editor can render idiomatic Controls for an active story without
|
|
79
|
+
* inventing a second fixture/state format. */
|
|
80
|
+
args: Record<string, unknown>;
|
|
81
|
+
/** The real Storybook portable-story component — args/decorators/loaders
|
|
82
|
+
* already applied; `.play` present when the original story declared one. */
|
|
83
|
+
Component: ComposedStoryFn;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The result of composing one loaded CSF module. */
|
|
87
|
+
export type ComposeResult =
|
|
88
|
+
| { ok: true; stories: ComposedProjectStory[] }
|
|
89
|
+
| { ok: false; error: string };
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Compose a dynamically-imported CSF module (`story-discovery.ts`'s
|
|
93
|
+
* `load()` result) into its named, portable-story components via the real
|
|
94
|
+
* `composeStories` (spec §9 "mount through Storybook portable-story APIs").
|
|
95
|
+
*
|
|
96
|
+
* Error handling (spec §9 "Errors identify the source module and story
|
|
97
|
+
* export"): a module with no default export (no Meta — every CSF file needs
|
|
98
|
+
* one), or a `composeStories` call that throws (e.g. a story referencing a
|
|
99
|
+
* component that itself throws on import), surfaces an error naming
|
|
100
|
+
* `modulePath`. A per-story failure is narrower still — `composeStories`
|
|
101
|
+
* composes every export independently, so one bad story doesn't take the
|
|
102
|
+
* rest of the module down; a story whose export isn't a valid CSF
|
|
103
|
+
* annotation is simply not a function/object Storybook recognizes and is
|
|
104
|
+
* skipped rather than crashing the whole module (mirrors `composeStories`'s
|
|
105
|
+
* own `Store_CSFExports`-keyed-mapped-type contract, which already excludes
|
|
106
|
+
* anything that isn't shaped like a story).
|
|
107
|
+
*/
|
|
108
|
+
export function composeProjectStories(modulePath: string, mod: unknown): ComposeResult {
|
|
109
|
+
const record = (mod ?? {}) as Record<string, unknown>;
|
|
110
|
+
if (record['default'] === null || typeof record['default'] !== 'object') {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
error:
|
|
114
|
+
`[stories] ${modulePath} has no default export — every CSF file exports ` +
|
|
115
|
+
`\`export default { title, component, ... } satisfies Meta<...>\` ` +
|
|
116
|
+
'(see the template example: src/ui/Button.stories.tsx). Skipped.',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let composed: Record<string, unknown>;
|
|
121
|
+
try {
|
|
122
|
+
// biome-ignore lint/suspicious/noExplicitAny: `composeStories` is generically typed over the module's own CSF export shape (Store_CSFExports<ReactRenderer, TModule>), which we can't name statically for an arbitrary dynamically-imported project module.
|
|
123
|
+
composed = composeStories(record as any) as Record<string, unknown>;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: `[stories] ${modulePath} failed to compose — ${String(err instanceof Error ? err.message : err)}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const meta = record['default'] as Record<string, unknown>;
|
|
132
|
+
const title = typeof meta['title'] === 'string' ? meta['title'] : undefined;
|
|
133
|
+
const humanizeStoryName = (name: string): string =>
|
|
134
|
+
name
|
|
135
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
136
|
+
.replace(/[-_]+/g, ' ')
|
|
137
|
+
.trim();
|
|
138
|
+
|
|
139
|
+
const stories: ComposedProjectStory[] = Object.entries(composed)
|
|
140
|
+
.filter((entry): entry is [string, ComposedStoryFn] => typeof entry[1] === 'function')
|
|
141
|
+
.map(([name, Component]) => {
|
|
142
|
+
const portable = Component as unknown as Record<string, unknown>;
|
|
143
|
+
const authoredStory = record[name];
|
|
144
|
+
const explicitLabel =
|
|
145
|
+
authoredStory && typeof authoredStory === 'object'
|
|
146
|
+
? (authoredStory as Record<string, unknown>)['name']
|
|
147
|
+
: undefined;
|
|
148
|
+
return {
|
|
149
|
+
name,
|
|
150
|
+
id: typeof portable['id'] === 'string' ? portable['id'] : name,
|
|
151
|
+
label:
|
|
152
|
+
typeof explicitLabel === 'string'
|
|
153
|
+
? explicitLabel
|
|
154
|
+
: humanizeStoryName(
|
|
155
|
+
typeof portable['storyName'] === 'string' ? portable['storyName'] : name,
|
|
156
|
+
),
|
|
157
|
+
...(title ? { title } : {}),
|
|
158
|
+
parameters:
|
|
159
|
+
portable['parameters'] && typeof portable['parameters'] === 'object'
|
|
160
|
+
? (portable['parameters'] as Record<string, unknown>)
|
|
161
|
+
: {},
|
|
162
|
+
args:
|
|
163
|
+
portable['args'] && typeof portable['args'] === 'object'
|
|
164
|
+
? { ...(portable['args'] as Record<string, unknown>) }
|
|
165
|
+
: {},
|
|
166
|
+
// Object.entries() loses the predicate's value refinement under the
|
|
167
|
+
// root build tsconfig; the filter above is the runtime proof.
|
|
168
|
+
Component: Component as ComposedStoryFn,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (stories.length === 0) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
error:
|
|
176
|
+
`[stories] ${modulePath} exports no named stories — a CSF file needs at least one ` +
|
|
177
|
+
'named export alongside the default Meta export (see src/ui/Button.stories.tsx). Skipped.',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { ok: true, stories };
|
|
182
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture CSF module for the C3 loaders regression test
|
|
3
|
+
* (`StoryPreviewMount.loaders.test.tsx`) — a minimal, real CSF3 file with a
|
|
4
|
+
* `loaders` entry whose resolved value is rendered into the DOM. This is the
|
|
5
|
+
* proof surface for "loaders mount through Storybook portable-story APIs"
|
|
6
|
+
* (spec §9): the loaded value can ONLY appear in the rendered output if the
|
|
7
|
+
* composed story's `.load()` actually ran before render (see
|
|
8
|
+
* `StoryPreviewMount.tsx`'s module doc comment for why — confirmed against
|
|
9
|
+
* Storybook 9's own `composeStory` implementation). Mirrors
|
|
10
|
+
* `button-stories.fixture.tsx`'s shape/precedent, kept local for the same
|
|
11
|
+
* "fast, isolated, no cross-package reach" reasons.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
15
|
+
|
|
16
|
+
interface LoaderFixtureProps {
|
|
17
|
+
loaded?: { greeting: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function LoaderFixture({ loaded }: LoaderFixtureProps) {
|
|
21
|
+
// Storybook's own composed-story context defaults `loaded` to `{}` (an
|
|
22
|
+
// empty but truthy object) BEFORE `.load()` has ever run, and only
|
|
23
|
+
// replaces it with the loader's real return value once `.load()` resolves
|
|
24
|
+
// (see `StoryPreviewMount.tsx`'s module doc comment) — so this checks the
|
|
25
|
+
// actual KEY the loader supplies, not just object presence.
|
|
26
|
+
return (
|
|
27
|
+
<div data-testid="loader-fixture">
|
|
28
|
+
{loaded?.greeting ?? '(no loaded data — .load() did not run)'}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const meta = {
|
|
34
|
+
title: 'Fixture/Loader',
|
|
35
|
+
component: LoaderFixture,
|
|
36
|
+
} satisfies Meta<typeof LoaderFixture>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
type Story = StoryObj<typeof meta>;
|
|
41
|
+
|
|
42
|
+
export const WithLoader: Story = {
|
|
43
|
+
loaders: [
|
|
44
|
+
async () => {
|
|
45
|
+
// A real async loader — proves this isn't just a synchronous default
|
|
46
|
+
// arg. `composeStories` puts this return value on `context.loaded`,
|
|
47
|
+
// which the story below reads and renders.
|
|
48
|
+
await Promise.resolve();
|
|
49
|
+
return { greeting: 'hello from loader' };
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
render: (_args, { loaded }) => <LoaderFixture loaded={loaded as { greeting: string }} />,
|
|
53
|
+
};
|