@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.
Files changed (430) hide show
  1. package/README.md +24 -0
  2. package/dist/assets/axe-C7YBtOuM.js +30 -0
  3. package/dist/assets/browserAll-DDtGGE48.js +1 -0
  4. package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
  5. package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
  6. package/dist/assets/game-BdTHR2_X.js +1 -0
  7. package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
  8. package/dist/assets/game-CcHfYxjP.js +1 -0
  9. package/dist/assets/game-Cqzj_lpD.js +1 -0
  10. package/dist/assets/game-DSZdeCHu.js +1 -0
  11. package/dist/assets/game-Dch9sAPc.js +1 -0
  12. package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
  13. package/dist/assets/game-loop-DMIgSMV-.js +1 -0
  14. package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
  15. package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
  16. package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
  17. package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
  18. package/dist/assets/index-CQEfi217.js +15412 -0
  19. package/dist/assets/index-Dvi-V0NE.css +1 -0
  20. package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
  21. package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
  22. package/dist/assets/react-18-C9eHn8x1.js +1 -0
  23. package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
  24. package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
  25. package/dist/index.html +15 -13
  26. package/dist/ingest/racing-game/vgai.game.json +5 -4
  27. package/dist/ingest/simcity/vgai.game.json +5 -4
  28. package/dist/ingest/tanks/vgai.game.json +5 -4
  29. package/dist/project-scripts.bundle.js +3917 -0
  30. package/dist/scenes/untitled.vscn.json +2 -0
  31. package/dist-server/packaged.mjs +7697 -3435
  32. package/package.json +31 -4
  33. package/scripts/probe-reporter.ts +349 -0
  34. package/scripts/run-probe.ts +2260 -0
  35. package/server/asset-catalog-audit.ts +268 -0
  36. package/server/asset-catalog-v2.ts +146 -0
  37. package/server/asset-library-routes.ts +276 -58
  38. package/server/canonical-path.ts +1 -1
  39. package/server/dev.ts +148 -44
  40. package/server/editor-server.ts +888 -61
  41. package/server/editor-sse.ts +115 -4
  42. package/server/harness-chat-service.ts +796 -0
  43. package/server/local-asset-catalog.ts +157 -22
  44. package/server/packaged.ts +51 -21
  45. package/server/process-shutdown.ts +136 -0
  46. package/server/prod.ts +9 -6
  47. package/server/project-hmr-files.ts +41 -0
  48. package/server/project-operations.ts +343 -0
  49. package/server/project-optimize-deps-entries.ts +4 -4
  50. package/server/project-output-writer.ts +156 -0
  51. package/server/project-validation.ts +140 -0
  52. package/server/project-view.ts +8 -50
  53. package/server/server-utils.ts +75 -10
  54. package/server/session-registry.ts +12 -0
  55. package/src/EditorContext.tsx +107 -3
  56. package/src/action-registry.ts +115 -17
  57. package/src/adapter-resolver.ts +213 -478
  58. package/src/asset-events.ts +64 -1
  59. package/src/asset-preview.ts +906 -0
  60. package/src/asset-selection.ts +121 -0
  61. package/src/asset-workflow/asset-capabilities.ts +259 -0
  62. package/src/asset-workflow/asset-editor-errors.ts +46 -0
  63. package/src/asset-workflow/asset-import-pipeline.ts +176 -0
  64. package/src/asset-workflow/asset-library-query.ts +107 -0
  65. package/src/asset-workflow/asset-types.ts +192 -0
  66. package/src/asset-workflow/asset-workflow-quality.ts +65 -0
  67. package/src/asset-workflow/import-contract.ts +113 -0
  68. package/src/asset-workflow/model-conversion.ts +126 -0
  69. package/src/asset-workflow/model-inspection.ts +185 -0
  70. package/src/asset-workflow/project-asset-commands.ts +23 -0
  71. package/src/asset-workflow/project-asset-health.ts +153 -0
  72. package/src/asset-workflow/project-asset-operations.ts +119 -0
  73. package/src/asset-workflow/thumbnail-system.ts +172 -0
  74. package/src/authoring/active-systems.ts +15 -0
  75. package/src/authoring/boundary-authoring-adapter.ts +16 -5
  76. package/src/authoring/composite-authoring-adapter.ts +305 -124
  77. package/src/authoring/design-time-layers.ts +300 -117
  78. package/src/authoring/edit-mode-authoring.ts +278 -293
  79. package/src/authoring/embed-only-adapter.ts +4 -2
  80. package/src/authoring/first-party-hierarchy-menu.ts +7 -2
  81. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
  82. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
  83. package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
  84. package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
  85. package/src/authoring/first-party-inspector/register.tsx +13 -2
  86. package/src/authoring/ingest-authoring-adapter.ts +142 -45
  87. package/src/authoring/mount-failure-report.ts +5 -5
  88. package/src/authoring/provenance.ts +41 -0
  89. package/src/authoring/react-dom-authoring-adapter.ts +92 -92
  90. package/src/authoring/react-inspector-section.tsx +489 -51
  91. package/src/authoring/react-world-authoring-adapter.ts +541 -306
  92. package/src/authoring/story-overlay.ts +1 -1
  93. package/src/authoring/story-types.ts +11 -0
  94. package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
  95. package/src/authoring/viewport-tool-context.ts +78 -0
  96. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  97. package/src/authoring/world-hidden-viewport.ts +186 -0
  98. package/src/authoring/world-pan-state.ts +33 -17
  99. package/src/authoring/world-session-state.ts +1 -1
  100. package/src/authoring/world2d-authoring-adapter.ts +109 -52
  101. package/src/availability-tick.ts +56 -0
  102. package/src/browser-play-entry.ts +5 -5
  103. package/src/browser-project-scripts.ts +1 -0
  104. package/src/build-identity.ts +15 -0
  105. package/src/command-listener.ts +893 -19
  106. package/src/components/AlignToolbar.tsx +74 -63
  107. package/src/components/AppRoot.tsx +193 -26
  108. package/src/components/ApplicationMenus.tsx +272 -0
  109. package/src/components/AssetBrowser.tsx +1450 -293
  110. package/src/components/AssetEditorShell.tsx +141 -0
  111. package/src/components/AssetImportDetails.tsx +218 -0
  112. package/src/components/AssetLibraryPanel.tsx +14 -0
  113. package/src/components/CameraInfo.tsx +11 -20
  114. package/src/components/CenterDocuments.tsx +585 -0
  115. package/src/components/CommandPalette.tsx +75 -30
  116. package/src/components/ConsolePanel.css +55 -0
  117. package/src/components/ConsolePanel.tsx +57 -112
  118. package/src/components/DebugConsolePanel.tsx +461 -0
  119. package/src/components/DebuggerPanel.tsx +165 -0
  120. package/src/components/DefaultEditorLayout.tsx +167 -231
  121. package/src/components/GameHierarchy.tsx +873 -290
  122. package/src/components/GamePerformanceOverlay.tsx +46 -0
  123. package/src/components/HarnessChatPanel.tsx +2352 -0
  124. package/src/components/HistoryPanel.tsx +226 -0
  125. package/src/components/Inspector.tsx +153 -99
  126. package/src/components/InspectorToolSection.tsx +6 -4
  127. package/src/components/MountFailureBanner.tsx +31 -48
  128. package/src/components/NetworkSection.tsx +25 -14
  129. package/src/components/OnlineAssetBrowser.tsx +794 -343
  130. package/src/components/OverlayReportBanner.tsx +38 -64
  131. package/src/components/PerformancePanel.tsx +206 -0
  132. package/src/components/PlayBar.tsx +93 -263
  133. package/src/components/PrefabSaveDialog.tsx +55 -127
  134. package/src/components/ProjectHeader.css +60 -0
  135. package/src/components/ProjectHeader.tsx +170 -105
  136. package/src/components/ProjectScreen.tsx +246 -184
  137. package/src/components/ReactCanvasControls.tsx +185 -0
  138. package/src/components/ResolutionPicker.tsx +85 -0
  139. package/src/components/RootActivateDialog.tsx +39 -68
  140. package/src/components/SaveDialog.tsx +55 -127
  141. package/src/components/SaveStatus.tsx +11 -29
  142. package/src/components/StartupLoadingScreen.tsx +96 -0
  143. package/src/components/StateWatchPanel.tsx +302 -0
  144. package/src/components/StatsOverlay.tsx +12 -5
  145. package/src/components/StatusBar.tsx +64 -0
  146. package/src/components/ThemeManagerDialog.css +89 -0
  147. package/src/components/ThemeManagerDialog.tsx +371 -0
  148. package/src/components/ToolHost.tsx +26 -15
  149. package/src/components/Toolbar.tsx +117 -214
  150. package/src/components/VgaiVoxelLogo.tsx +89 -0
  151. package/src/components/ViewportOverlay.tsx +97 -172
  152. package/src/components/ViewportPanel.tsx +130 -31
  153. package/src/components/WorkspaceDock.tsx +891 -0
  154. package/src/components/WorldSelectionOverlay.tsx +51 -63
  155. package/src/components/WorldTextEditor.tsx +9 -5
  156. package/src/components/asset-documents.tsx +390 -0
  157. package/src/components/asset-editor-persistence.ts +44 -0
  158. package/src/components/asset-selection-section.tsx +338 -0
  159. package/src/components/asset-viewers/AudioViewer.tsx +73 -21
  160. package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
  161. package/src/components/asset-viewers/ImageViewer.tsx +43 -11
  162. package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
  163. package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
  164. package/src/components/asset-viewers/ModelViewer.tsx +805 -121
  165. package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
  166. package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
  167. package/src/components/asset-workflow.css +506 -0
  168. package/src/components/build-documents.tsx +696 -0
  169. package/src/components/core-utilities.tsx +75 -0
  170. package/src/components/data-documents.tsx +364 -0
  171. package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
  172. package/src/components/debugger-panel-state.ts +57 -0
  173. package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
  174. package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
  175. package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
  176. package/src/components/inspector-widgets/FontPicker.tsx +13 -21
  177. package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
  178. package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
  179. package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
  180. package/src/components/inspector-widgets/shared.tsx +90 -49
  181. package/src/components/inspectors/AnimationSection.tsx +39 -40
  182. package/src/components/inspectors/AudioSection.tsx +28 -39
  183. package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
  184. package/src/components/inspectors/CameraSection.tsx +14 -23
  185. package/src/components/inspectors/ComponentsSection.tsx +39 -43
  186. package/src/components/inspectors/JointsSection.tsx +34 -43
  187. package/src/components/inspectors/LightSection.tsx +90 -35
  188. package/src/components/inspectors/MaterialSection.tsx +137 -174
  189. package/src/components/inspectors/MeshSection.tsx +9 -17
  190. package/src/components/inspectors/NavigationSection.tsx +8 -15
  191. package/src/components/inspectors/ParticleSection.tsx +91 -76
  192. package/src/components/inspectors/PhysicsSection.tsx +42 -46
  193. package/src/components/inspectors/PostProcessingSection.tsx +28 -36
  194. package/src/components/inspectors/RenderingSection.tsx +12 -20
  195. package/src/components/inspectors/ShadowSection.tsx +7 -8
  196. package/src/components/inspectors/SplineSection.tsx +31 -42
  197. package/src/components/inspectors/TransformSection.tsx +14 -22
  198. package/src/components/patterns/Dialog.tsx +95 -0
  199. package/src/components/patterns/Fields.tsx +44 -0
  200. package/src/components/patterns/List.tsx +25 -0
  201. package/src/components/patterns/StateSurface.tsx +40 -0
  202. package/src/components/patterns/Surfaces.tsx +115 -0
  203. package/src/components/patterns/Tabs.tsx +80 -0
  204. package/src/components/patterns/Toolbar.tsx +51 -0
  205. package/src/components/patterns/Tree.tsx +72 -0
  206. package/src/components/primitives/Button.tsx +46 -0
  207. package/src/components/primitives/ColorInput.tsx +11 -47
  208. package/src/components/primitives/EditorIcon.tsx +99 -0
  209. package/src/components/primitives/FormControls.tsx +71 -0
  210. package/src/components/primitives/JsonInput.tsx +15 -2
  211. package/src/components/primitives/Layout.tsx +100 -0
  212. package/src/components/primitives/Menu.tsx +105 -0
  213. package/src/components/primitives/NumberInput.tsx +8 -35
  214. package/src/components/primitives/Panel.tsx +49 -44
  215. package/src/components/primitives/SectionHeader.tsx +10 -28
  216. package/src/components/primitives/Text.tsx +46 -0
  217. package/src/components/primitives/Tooltip.tsx +20 -12
  218. package/src/components/primitives/Vec3Input.tsx +3 -0
  219. package/src/components/primitives/banner-tones.ts +33 -0
  220. package/src/components/primitives/editor-icons.ts +102 -0
  221. package/src/components/primitives/panel-header-styles.ts +30 -0
  222. package/src/components/project-operation-documents.tsx +226 -0
  223. package/src/components/status-contributions.tsx +175 -0
  224. package/src/components/story-documents-register.ts +15 -0
  225. package/src/components/story-documents.tsx +1185 -0
  226. package/src/components/tool-documents.tsx +110 -0
  227. package/src/components/workspace-dock.css +293 -0
  228. package/src/components/workspace-history.ts +33 -0
  229. package/src/components/world-documents.tsx +145 -0
  230. package/src/components/xstate-documents.tsx +209 -0
  231. package/src/composite-screenshot.ts +248 -0
  232. package/src/design-system/index.ts +70 -0
  233. package/src/design-system-stories/Foundation.stories.tsx +183 -0
  234. package/src/design-system-stories/Patterns.stories.tsx +251 -0
  235. package/src/design-system-stories/Workspace.stories.tsx +538 -0
  236. package/src/doctor/report.ts +57 -13
  237. package/src/editor-api.ts +295 -31
  238. package/src/editor-compatibility.ts +200 -0
  239. package/src/editor-console.ts +35 -17
  240. package/src/editor-hotkeys.ts +85 -33
  241. package/src/editor-store.ts +538 -233
  242. package/src/editor-viewport.ts +160 -34
  243. package/src/entity-factory.ts +14 -3
  244. package/src/gizmo-registry.ts +21 -5
  245. package/src/harness-chat-editor-context.ts +37 -0
  246. package/src/harness-chat-indicator.ts +68 -0
  247. package/src/harness-chat-layout.ts +53 -0
  248. package/src/harness-chat-lifecycle.ts +170 -0
  249. package/src/harness-chat-reconnect.ts +85 -0
  250. package/src/harness-chat-types.ts +409 -0
  251. package/src/hierarchy-drop.ts +50 -0
  252. package/src/hierarchy-expansion-state.ts +100 -0
  253. package/src/hierarchy-menu-registry.ts +1 -1
  254. package/src/hierarchy-projection.ts +76 -0
  255. package/src/history/editor-session.ts +21 -0
  256. package/src/history/history-commands.ts +147 -0
  257. package/src/history/history-service.ts +904 -0
  258. package/src/history/json-history-resource.ts +165 -0
  259. package/src/history/persistence-coordinator.ts +35 -0
  260. package/src/history/project-file-history.ts +266 -0
  261. package/src/history/project-root-history-backends.ts +53 -0
  262. package/src/history/resource-registry.ts +209 -0
  263. package/src/history/snapshot-store.ts +108 -0
  264. package/src/history/source-history-backend.ts +374 -0
  265. package/src/history/types.ts +100 -0
  266. package/src/hmr-registration-group.ts +67 -0
  267. package/src/hmr-stable-react-context.ts +23 -0
  268. package/src/hosted-example.ts +12 -2
  269. package/src/hotkeys.ts +89 -18
  270. package/src/ingest/discovery-react.ts +9 -10
  271. package/src/ingest/discovery.ts +4 -4
  272. package/src/ingest/discovery2d.ts +5 -5
  273. package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
  274. package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
  275. package/src/ingest/games/games-fps/vgai.game.json +3 -3
  276. package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
  277. package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
  278. package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
  279. package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
  280. package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
  281. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
  282. package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
  283. package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
  284. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
  285. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
  286. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
  287. package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
  288. package/src/ingest/types.ts +1 -1
  289. package/src/ingest-mode.ts +47 -52
  290. package/src/ingest-siblings.ts +23 -33
  291. package/src/initial-scene.ts +1 -2
  292. package/src/main.tsx +16 -8
  293. package/src/manifest-project.ts +10 -12
  294. package/src/model-thumbnail.ts +261 -26
  295. package/src/module-mode.ts +44 -50
  296. package/src/play-log-events.ts +26 -0
  297. package/src/play-mode.ts +495 -401
  298. package/src/play-scene-adoption.ts +23 -0
  299. package/src/playground/main.ts +68 -24
  300. package/src/playwright-shim.ts +473 -0
  301. package/src/project-manager.ts +90 -10
  302. package/src/project-operations.ts +112 -0
  303. package/src/project-story-discovery.ts +25 -0
  304. package/src/project-tool-discovery.ts +43 -0
  305. package/src/scene-serializer.ts +0 -6
  306. package/src/scene-sync.ts +7 -2
  307. package/src/script-loader.ts +3 -3
  308. package/src/skeleton-helper.ts +57 -0
  309. package/src/storage/seed.ts +68 -8
  310. package/src/stories/StoryPreviewMount.tsx +265 -0
  311. package/src/stories/action-stories.fixture.tsx +44 -0
  312. package/src/stories/button-stories.fixture.tsx +50 -0
  313. package/src/stories/compose-project-stories.ts +182 -0
  314. package/src/stories/loader-stories.fixture.tsx +53 -0
  315. package/src/stories/story-discovery.ts +130 -0
  316. package/src/stories/story-registry.ts +146 -0
  317. package/src/theme-css.ts +1049 -0
  318. package/src/theme-library.ts +238 -0
  319. package/src/theme-preference.ts +130 -0
  320. package/src/theme.ts +527 -0
  321. package/src/tool-loader.ts +80 -24
  322. package/src/ui-source/inspect.ts +27 -4
  323. package/src/ui-source/source-write-backend.ts +83 -22
  324. package/src/widgets/index.ts +9 -18
  325. package/src/workspace-aux-commands.ts +11 -0
  326. package/src/workspace-dock-controller.ts +64 -0
  327. package/src/workspace-document-ids.ts +5 -0
  328. package/src/workspace-document-registry.ts +367 -0
  329. package/src/workspace-state-persistence.ts +455 -0
  330. package/src/workspace-status-registry.ts +110 -0
  331. package/src/workspace-utility-commands.ts +49 -0
  332. package/src/workspace-utility-registry.ts +161 -0
  333. package/src/world-document-routing.ts +16 -0
  334. package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
  335. package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
  336. package/src/xstate-inspect/use-live-actor-state.ts +39 -0
  337. package/src/xstate-inspect/xstate-graph.ts +265 -0
  338. package/src/xstate-inspect/xstate-layout.ts +76 -0
  339. package/src/z-index.ts +26 -0
  340. package/template/.claude/skills/editor/SKILL.md +129 -90
  341. package/template/AGENTS.md +1110 -0
  342. package/template/CLAUDE.md +1 -512
  343. package/template/IDIOMS.md +50 -0
  344. package/template/check-idioms.ts +706 -0
  345. package/template/manifest-entry-modules-plugin.ts +53 -0
  346. package/template/package.json +13 -4
  347. package/template/public/scenes/default.scn2d.json +6 -0
  348. package/template/public/scenes/default.vscn.json +7 -1
  349. package/template/public/scenes/untitled.vscn.json +10 -0
  350. package/template/scripts/playtest.ts +40 -0
  351. package/template/server/loopback.ts +75 -0
  352. package/template/server/rooms/arena-room.ts +69 -1
  353. package/template/server/rooms/game-room.ts +68 -1
  354. package/template/src/data/README.md +9 -4
  355. package/template/src/data/data-ref.ts +35 -0
  356. package/template/src/data/tuning.schema.ts +1 -1
  357. package/template/src/main.ts +41 -62
  358. package/template/src/manifest-entry-modules.d.ts +3 -0
  359. package/template/src/operations/README.md +88 -0
  360. package/template/src/runtime/pixi-adapter.ts +24 -0
  361. package/template/src/scripts/components/data-spinner.ts +43 -2
  362. package/template/src/scripts/components/spin-lap-math.ts +27 -0
  363. package/template/src/tools/README.md +5 -3
  364. package/template/src/tools/example.tool.tsx +13 -7
  365. package/template/src/ui/Button.stories.tsx +66 -0
  366. package/template/src/ui/RebindPanel.tsx +2 -6
  367. package/template/src/ui/game.tsx +17 -0
  368. package/template/tests/acceptance/example.spec.ts +64 -0
  369. package/template/tests/logic/example.test.ts +31 -0
  370. package/template/tests/logic/room.test.ts +100 -0
  371. package/template/tests/tsconfig.json +30 -0
  372. package/template/validate-manifest.ts +5 -5
  373. package/template/validate-scenes.ts +50 -18
  374. package/template/vgai.game.json +20 -13
  375. package/template/vite.config.ts +77 -16
  376. package/vite-plugin-ui-oid.ts +261 -17
  377. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
  378. package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
  379. package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
  380. package/dist/assets/browserAll-CEkWYlFk.js +0 -1
  381. package/dist/assets/create-runtime-DYIWr3va.js +0 -1
  382. package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
  383. package/dist/assets/game-8SDZPPgH.js +0 -1
  384. package/dist/assets/game-CevorxyV.js +0 -1
  385. package/dist/assets/game-Di7XlywD.js +0 -1
  386. package/dist/assets/game-DjnCaw8R.js +0 -1
  387. package/dist/assets/game-Q9A0aXSV.js +0 -1
  388. package/dist/assets/index-CPVV6pjw.js +0 -1
  389. package/dist/assets/index-CpOnYyeD.js +0 -213
  390. package/dist/assets/index-Dr8H_6Rl.js +0 -1
  391. package/dist/assets/index-_bCaipUk.js +0 -13650
  392. package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
  393. package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
  394. package/dist/assets/scene-query-BivAHhJu.js +0 -1
  395. package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
  396. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
  397. package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
  398. package/src/authoring/scene-ui-layer.ts +0 -485
  399. package/src/authoring/ui-authoring-adapter.ts +0 -325
  400. package/src/authoring/ui-hierarchy-menu.ts +0 -40
  401. package/src/authoring/ui-inspector-sections.tsx +0 -156
  402. package/src/authoring/undo-timeline.ts +0 -145
  403. package/src/components/BottomPanel.tsx +0 -326
  404. package/src/components/BuildPanel.tsx +0 -455
  405. package/src/components/FolderTree.tsx +0 -179
  406. package/src/components/RightPanel.tsx +0 -166
  407. package/src/components/WorldContextMenu.tsx +0 -257
  408. package/src/components/asset-viewers/AssetViewer.tsx +0 -38
  409. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
  410. package/src/scene-ui-mode/active-scene-ui.ts +0 -58
  411. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
  412. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
  413. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
  414. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
  415. package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
  416. package/src/ui-editor/inspector.tsx +0 -563
  417. package/src/ui-editor/node-ops.ts +0 -266
  418. package/src/ui-editor/overlay.tsx +0 -380
  419. package/src/ui-editor/palette.tsx +0 -114
  420. package/src/ui-editor/react-store.ts +0 -78
  421. package/src/ui-editor/timeline-panel.tsx +0 -443
  422. package/src/ui-editor/ui-edit-store.ts +0 -552
  423. package/src/ui-editor/ui-prefab.ts +0 -95
  424. package/template/project.json +0 -9
  425. package/template/src/scripts/main.ts +0 -94
  426. package/template/src/ui/DefaultHud.tsx +0 -40
  427. package/template/src/ui/game-state.tsx +0 -37
  428. package/template/src/ui/mount-default-hud.tsx +0 -19
  429. package/template/src/ui/mount-ui.ts +0 -15
  430. 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
+ };