@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,891 @@
1
+ import {
2
+ type DockviewApi,
3
+ DockviewDefaultTab,
4
+ type DockviewPanelApi,
5
+ DockviewReact,
6
+ type DockviewTheme,
7
+ type IDockviewPanelHeaderProps,
8
+ type IDockviewPanelProps,
9
+ } from 'dockview-react';
10
+ import {
11
+ createContext,
12
+ useCallback,
13
+ useContext,
14
+ useEffect,
15
+ useRef,
16
+ useState,
17
+ useSyncExternalStore,
18
+ } from 'react';
19
+ import { availabilityTickVersion, subscribeAvailabilityTick } from '../availability-tick';
20
+ import { Button } from '../design-system';
21
+ import { useEditorStore } from '../EditorContext';
22
+ import {
23
+ harnessChat,
24
+ harnessChatLayoutVersion,
25
+ setHarnessChatCollapsed,
26
+ subscribeHarnessChatLayout,
27
+ } from '../harness-chat-layout';
28
+ import type { HotkeyScope } from '../hotkeys';
29
+ import {
30
+ installWorkspaceDockCommands,
31
+ type WorkspaceDockCommands,
32
+ } from '../workspace-dock-controller';
33
+ import {
34
+ activateWorkspaceDocument,
35
+ activeWorkspaceDocumentId,
36
+ closeWorkspaceDocument,
37
+ openWorkspaceDocuments,
38
+ saveWorkspaceDocument,
39
+ subscribeWorkspaceDocuments,
40
+ workspaceDocumentRegistryVersion,
41
+ } from '../workspace-document-registry';
42
+ import { installWorkspaceStatePersistence } from '../workspace-state-persistence';
43
+ import {
44
+ availableWorkspaceUtilities,
45
+ registeredWorkspaceUtilities,
46
+ subscribeWorkspaceUtilities,
47
+ type WorkspaceUtilityRegistration,
48
+ workspaceUtilityBadge,
49
+ workspaceUtilityRegistryVersion,
50
+ } from '../workspace-utility-registry';
51
+ import { AssetBrowser } from './AssetBrowser';
52
+ import { AssetLibraryPanel } from './AssetLibraryPanel';
53
+ import { GameHierarchy } from './GameHierarchy';
54
+ import { HarnessChatPanel } from './HarnessChatPanel';
55
+ import { HistoryPanel } from './HistoryPanel';
56
+ import { Inspector } from './Inspector';
57
+ import 'dockview-react/dist/styles/dockview.css';
58
+ import './workspace-dock.css';
59
+
60
+ const COMPONENT_ID = 'vgai-workspace-panel';
61
+ const TAB_COMPONENT_ID = 'vgai-workspace-tab';
62
+
63
+ const HIERARCHY_PANEL_ID = 'navigator:hierarchy';
64
+ const AGENT_PANEL_ID = 'workspace:agent';
65
+ const ASSETS_PANEL_ID = 'navigator:assets';
66
+ const ASSET_LIBRARY_PANEL_ID = 'navigator:asset-library';
67
+ const INSPECTOR_PANEL_ID = 'workspace:inspector';
68
+ const HISTORY_PANEL_ID = 'aux:undo-history';
69
+
70
+ const LEFT_GROUP_ID = 'vgai:left';
71
+ const AGENT_GROUP_ID = 'vgai:agent';
72
+ const ASSETS_GROUP_ID = 'vgai:assets';
73
+ const RIGHT_GROUP_ID = 'vgai:right';
74
+ const BOTTOM_GROUP_ID = 'vgai:bottom';
75
+ const CENTER_GROUP_ID = 'vgai:center';
76
+ const normalizedAgentGroups = new WeakSet<object>();
77
+
78
+ type StaticPanelKind = 'agent' | 'hierarchy' | 'assets' | 'asset-library' | 'inspector' | 'history';
79
+ type WorkspacePanelParams =
80
+ | { kind: 'static'; staticKind: StaticPanelKind; closeable: boolean }
81
+ | { kind: 'document'; documentId: string; closeable: boolean }
82
+ | { kind: 'utility'; utilityId: string; closeable: boolean };
83
+
84
+ function hotkeyScopeForPanel(params: WorkspacePanelParams): HotkeyScope {
85
+ if (params.kind === 'utility') return 'workspace';
86
+ if (params.kind === 'document') {
87
+ return isWorldSurfaceDocument(params.documentId) ? 'viewport' : 'workspace';
88
+ }
89
+ switch (params.staticKind) {
90
+ case 'hierarchy':
91
+ return 'hierarchy';
92
+ case 'assets':
93
+ case 'asset-library':
94
+ return 'asset-browser';
95
+ case 'inspector':
96
+ return 'inspector';
97
+ case 'agent':
98
+ case 'history':
99
+ return 'workspace';
100
+ }
101
+ }
102
+
103
+ const OpenSceneContext = createContext<(assetPath: string) => void>(() => undefined);
104
+
105
+ export const vgaiDockviewTheme: DockviewTheme = {
106
+ name: 'vgai',
107
+ className: 'dockview-theme-vgai',
108
+ colorScheme: 'dark',
109
+ gap: 1,
110
+ edgeGroupCollapsedSize: 28,
111
+ dndOverlayMounting: 'absolute',
112
+ dndPanelOverlay: 'group',
113
+ dndTabIndicator: 'line',
114
+ dndOverlayBorder: '1px solid var(--dv-active-sash-color)',
115
+ tabGroupIndicator: 'none',
116
+ tabAnimation: 'smooth',
117
+ };
118
+
119
+ function usePanelActive(api: DockviewPanelApi): boolean {
120
+ const [active, setActive] = useState(api.isActive);
121
+ useEffect(() => {
122
+ setActive(api.isActive);
123
+ const disposable = api.onDidActiveChange((event) => setActive(event.isActive));
124
+ return () => disposable.dispose();
125
+ }, [api]);
126
+ return active;
127
+ }
128
+
129
+ function StaticPanelContent({ staticKind }: { staticKind: StaticPanelKind }) {
130
+ const onOpenScene = useContext(OpenSceneContext);
131
+ switch (staticKind) {
132
+ case 'agent':
133
+ return <HarnessChatPanel />;
134
+ case 'hierarchy':
135
+ return <GameHierarchy />;
136
+ case 'assets':
137
+ return <AssetBrowser onOpenScene={onOpenScene} />;
138
+ case 'asset-library':
139
+ return <AssetLibraryPanel />;
140
+ case 'inspector':
141
+ return <Inspector />;
142
+ case 'history':
143
+ return <HistoryPanel />;
144
+ }
145
+ }
146
+
147
+ function WorkspacePanel({ api, params }: IDockviewPanelProps<WorkspacePanelParams>) {
148
+ const active = usePanelActive(api);
149
+ useSyncExternalStore(
150
+ subscribeWorkspaceDocuments,
151
+ workspaceDocumentRegistryVersion,
152
+ workspaceDocumentRegistryVersion,
153
+ );
154
+ useSyncExternalStore(
155
+ subscribeWorkspaceUtilities,
156
+ workspaceUtilityRegistryVersion,
157
+ workspaceUtilityRegistryVersion,
158
+ );
159
+
160
+ if (params.kind === 'static') {
161
+ return (
162
+ <div
163
+ className="vgai-dock-panel"
164
+ data-testid={`dock-panel:${api.id}`}
165
+ data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
166
+ >
167
+ <StaticPanelContent staticKind={params.staticKind} />
168
+ </div>
169
+ );
170
+ }
171
+
172
+ if (params.kind === 'utility') {
173
+ const utility = availableWorkspaceUtilities().find((item) => item.id === params.utilityId);
174
+ if (!utility) {
175
+ return <div className="vgai-dock-empty">This utility is not available in this session.</div>;
176
+ }
177
+ const Content = utility.Content;
178
+ return (
179
+ <div
180
+ className="vgai-dock-panel"
181
+ data-testid={`bottom-panel-utility-${params.utilityId}`}
182
+ data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
183
+ >
184
+ <Content />
185
+ </div>
186
+ );
187
+ }
188
+
189
+ const document = openWorkspaceDocuments().find(
190
+ (item) => item.descriptor.id === params.documentId,
191
+ );
192
+ if (!document) return <div className="vgai-dock-empty">Restoring document…</div>;
193
+ const Content = document.descriptor.Content;
194
+ const Toolbar = document.descriptor.Toolbar;
195
+ return (
196
+ <div
197
+ className="vgai-dock-document"
198
+ data-testid={`workspace-doc:${params.documentId}`}
199
+ data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
200
+ >
201
+ {Toolbar && (
202
+ <div className="vgai-dock-document-toolbar">
203
+ <Toolbar documentId={params.documentId} active={active} />
204
+ </div>
205
+ )}
206
+ <div className="vgai-dock-document-content">
207
+ <Content documentId={params.documentId} active={active} />
208
+ </div>
209
+ </div>
210
+ );
211
+ }
212
+
213
+ function workspaceTabTestId(params: WorkspacePanelParams, panelId: string): string {
214
+ if (params.kind === 'document') return `workspace-tab:${params.documentId}`;
215
+ if (params.kind === 'utility') return `bottom-tab-utility-${params.utilityId}`;
216
+ return `dock-tab:${panelId}`;
217
+ }
218
+
219
+ function isWorldSurfaceDocument(documentId: string): boolean {
220
+ const kind = openWorkspaceDocuments().find((item) => item.descriptor.id === documentId)
221
+ ?.descriptor.kind;
222
+ return kind === 'game' || kind === 'scene' || kind === 'world';
223
+ }
224
+
225
+ function documentTabFamily(params: WorkspacePanelParams): 'world' | 'resource' | undefined {
226
+ if (params.kind !== 'document') return undefined;
227
+ return isWorldSurfaceDocument(params.documentId) ? 'world' : 'resource';
228
+ }
229
+
230
+ /** An older saved layout can restore resource tabs before world documents
231
+ * that did not exist when it was written. Stable-partition only that upgrade
232
+ * case; ordinary user ordering inside each family remains untouched. */
233
+ function normalizeDocumentSectionOrder(
234
+ api: DockviewApi,
235
+ centerGroupId: string,
236
+ documentIds: readonly string[],
237
+ ): void {
238
+ const centerDocumentIds = documentIds.filter(
239
+ (documentId) => api.getPanel(documentId)?.api.group.id === centerGroupId,
240
+ );
241
+ const firstDocumentId = centerDocumentIds[0];
242
+ const group = firstDocumentId ? api.getPanel(firstDocumentId)?.api.group : undefined;
243
+ if (!group) return;
244
+ for (const [index, documentId] of centerDocumentIds.entries()) {
245
+ const panel = api.getPanel(documentId);
246
+ if (!panel) continue;
247
+ if (group.panels[index]?.id === documentId) continue;
248
+ panel.api.moveTo({ group, index, skipSetActive: true });
249
+ }
250
+ }
251
+
252
+ const subscribeToNothing = () => () => undefined;
253
+ const staticBadgeSnapshot = () => 0;
254
+
255
+ /** Badge values often come from a store independent of the registration
256
+ * registry (the editor console is the first-party example). Keep that live
257
+ * source attached to the tab even while its utility panel is collapsed. */
258
+ function useWorkspaceUtilityBadge(utilityId: string | null) {
259
+ const utility = utilityId
260
+ ? registeredWorkspaceUtilities().find((item) => item.id === utilityId)
261
+ : undefined;
262
+ useSyncExternalStore(
263
+ utility?.subscribeBadge ?? subscribeToNothing,
264
+ utility?.badgeSnapshot ?? staticBadgeSnapshot,
265
+ utility?.badgeSnapshot ?? staticBadgeSnapshot,
266
+ );
267
+ return utilityId ? workspaceUtilityBadge(utilityId) : null;
268
+ }
269
+
270
+ function closeDockviewDocumentTab(
271
+ props: IDockviewPanelHeaderProps<WorkspacePanelParams>,
272
+ documentId: string,
273
+ discardDirty = false,
274
+ ): boolean {
275
+ if (!closeWorkspaceDocument(documentId, { discardDirty })) return false;
276
+ // Close through Dockview immediately so its physical tab order—including
277
+ // user moves—chooses the visible neighbor. If that neighbor is another
278
+ // document, mirror it back after the registry's generic fallback ran.
279
+ props.api.close();
280
+ const next = props.containerApi.activePanel;
281
+ if (!next) return true;
282
+ const nextParams = next.api.getParameters<WorkspacePanelParams>();
283
+ if (nextParams.kind === 'document') activateWorkspaceDocument(nextParams.documentId);
284
+ return true;
285
+ }
286
+
287
+ function WorkspaceTab(props: IDockviewPanelHeaderProps<WorkspacePanelParams>) {
288
+ const { api, params } = props;
289
+ const [confirmingClose, setConfirmingClose] = useState(false);
290
+ const [savingClose, setSavingClose] = useState(false);
291
+ useSyncExternalStore(
292
+ subscribeWorkspaceDocuments,
293
+ workspaceDocumentRegistryVersion,
294
+ workspaceDocumentRegistryVersion,
295
+ );
296
+ useSyncExternalStore(
297
+ subscribeWorkspaceUtilities,
298
+ workspaceUtilityRegistryVersion,
299
+ workspaceUtilityRegistryVersion,
300
+ );
301
+
302
+ const document =
303
+ params.kind === 'document'
304
+ ? openWorkspaceDocuments().find((item) => item.descriptor.id === params.documentId)
305
+ : null;
306
+ const badge = useWorkspaceUtilityBadge(params.kind === 'utility' ? params.utilityId : null);
307
+ const testId = workspaceTabTestId(params, api.id);
308
+ const tabFamily = documentTabFamily(params);
309
+ const closeProps =
310
+ params.kind === 'document' && params.closeable
311
+ ? {
312
+ closeActionOverride: () => {
313
+ if (document?.dirty) {
314
+ setConfirmingClose(true);
315
+ return;
316
+ }
317
+ closeDockviewDocumentTab(props, params.documentId);
318
+ },
319
+ }
320
+ : params.kind === 'static' && params.staticKind === 'agent'
321
+ ? {
322
+ closeActionOverride: () => {
323
+ setHarnessChatCollapsed(true);
324
+ props.api.close();
325
+ },
326
+ }
327
+ : {};
328
+ if (params.kind === 'document' && document && confirmingClose) {
329
+ return (
330
+ <div
331
+ className="vgai-dock-tab vgai-dock-tab-unsaved"
332
+ data-testid={`workspace-tab-unsaved:${params.documentId}`}
333
+ data-document-family={tabFamily}
334
+ role="group"
335
+ aria-label={`Save changes to ${document.title}?`}
336
+ onPointerDown={(event) => event.stopPropagation()}
337
+ >
338
+ <span className="vgai-dock-tab-unsaved-label">Unsaved</span>
339
+ <Button
340
+ type="button"
341
+ disabled={savingClose}
342
+ onClick={() => {
343
+ setSavingClose(true);
344
+ void saveWorkspaceDocument(params.documentId).then((saved) => {
345
+ setSavingClose(false);
346
+ if (saved) closeDockviewDocumentTab(props, params.documentId);
347
+ });
348
+ }}
349
+ >
350
+ {savingClose ? 'Saving…' : 'Save'}
351
+ </Button>
352
+ <Button
353
+ type="button"
354
+ disabled={savingClose}
355
+ onClick={() => closeDockviewDocumentTab(props, params.documentId, true)}
356
+ >
357
+ Discard
358
+ </Button>
359
+ <Button type="button" disabled={savingClose} onClick={() => setConfirmingClose(false)}>
360
+ Cancel
361
+ </Button>
362
+ </div>
363
+ );
364
+ }
365
+
366
+ return (
367
+ <div
368
+ className="vgai-dock-tab"
369
+ data-testid={testId}
370
+ data-dirty={document?.dirty || undefined}
371
+ data-document-family={tabFamily}
372
+ data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
373
+ >
374
+ <DockviewDefaultTab {...props} hideClose={!params.closeable} {...closeProps} />
375
+ {document?.dirty && (
376
+ <span className="vgai-dock-tab-dirty" title="Unsaved" aria-hidden="true" />
377
+ )}
378
+ {badge && badge.count > 0 && (
379
+ <span className={`vgai-dock-tab-badge vgai-dock-tab-badge-${badge.severity}`}>
380
+ {badge.count}
381
+ </span>
382
+ )}
383
+ </div>
384
+ );
385
+ }
386
+
387
+ function addStaticPanel(
388
+ api: DockviewApi,
389
+ options: {
390
+ id: string;
391
+ title: string;
392
+ staticKind: StaticPanelKind;
393
+ groupId: string;
394
+ inactive?: boolean;
395
+ closeable?: boolean;
396
+ },
397
+ ): void {
398
+ if (api.getPanel(options.id)) return;
399
+ api.addPanel<WorkspacePanelParams>({
400
+ id: options.id,
401
+ title: options.title,
402
+ component: COMPONENT_ID,
403
+ tabComponent: TAB_COMPONENT_ID,
404
+ renderer: 'always',
405
+ inactive: options.inactive ?? false,
406
+ position: { referenceGroup: options.groupId },
407
+ params: {
408
+ kind: 'static',
409
+ staticKind: options.staticKind,
410
+ closeable: options.closeable ?? false,
411
+ },
412
+ });
413
+ }
414
+
415
+ /** Permanent editor columns are normal Dockview grid groups. Edge groups put
416
+ * their headers on the outside edge, producing rotated vertical labels and a
417
+ * second rail beside the actual panel. Grid groups preserve all Dockview
418
+ * resize/drag/float behavior while using the conventional horizontal header. */
419
+ function ensureSideGroups(api: DockviewApi, centerGroupId: string): void {
420
+ let addedLeft = false;
421
+ let addedRight = false;
422
+ if (!api.getGroup(LEFT_GROUP_ID)) {
423
+ api.addGroup({
424
+ id: LEFT_GROUP_ID,
425
+ referenceGroup: centerGroupId,
426
+ direction: 'left',
427
+ initialWidth: 260,
428
+ constraints: { minimumWidth: 220, maximumWidth: 320 },
429
+ });
430
+ addedLeft = true;
431
+ }
432
+ if (!api.getGroup(RIGHT_GROUP_ID)) {
433
+ api.addGroup({
434
+ id: RIGHT_GROUP_ID,
435
+ referenceGroup: centerGroupId,
436
+ direction: 'right',
437
+ initialWidth: 280,
438
+ constraints: { minimumWidth: 240, maximumWidth: 340 },
439
+ });
440
+ addedRight = true;
441
+ }
442
+ // Adding the second sibling can redistribute the first split before the
443
+ // root has completed its initial layout. Set the intended editor widths
444
+ // after both groups exist; Dockview queues these values if still unmeasured.
445
+ if (addedLeft) api.getGroup(LEFT_GROUP_ID)?.api.setSize({ width: 260 });
446
+ if (addedRight) api.getGroup(RIGHT_GROUP_ID)?.api.setSize({ width: 280 });
447
+ }
448
+
449
+ function ensureAgentGroup(api: DockviewApi): void {
450
+ const existing = api.getGroup(AGENT_GROUP_ID);
451
+ if (existing) {
452
+ // Dockview keeps an empty group after the optional Agent panel is hidden.
453
+ // Refresh its constraints before reusing it: serialized/older constraints
454
+ // can otherwise let a 360px chat regrow to ~450px on restore.
455
+ existing.api.setConstraints({ minimumWidth: 300, maximumWidth: 380 });
456
+ existing.api.setSize({ width: 360 });
457
+ normalizedAgentGroups.add(existing);
458
+ requestAnimationFrame(() => {
459
+ api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
460
+ });
461
+ return;
462
+ }
463
+ // Absolute `right` makes Agent a sibling of the entire existing grid tree.
464
+ // Referencing the Hierarchy group here is subtly wrong: once Assets splits
465
+ // below Hierarchy, Dockview expands that lower split beneath Agent too and
466
+ // leaves chat occupying only one corner instead of a full-height column.
467
+ const agentGroup = api.addGroup({
468
+ id: AGENT_GROUP_ID,
469
+ direction: 'right',
470
+ initialWidth: 360,
471
+ constraints: { minimumWidth: 300, maximumWidth: 380 },
472
+ });
473
+ normalizedAgentGroups.add(agentGroup);
474
+ // Agent is added after the navigator and inspector. Reassert their semantic
475
+ // defaults after Dockview introduces the fourth column so the root split
476
+ // does not redistribute every tool surface toward equal widths.
477
+ api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
478
+ api.getGroup(LEFT_GROUP_ID)?.api.setSize({ width: 260 });
479
+ api.getGroup(RIGHT_GROUP_ID)?.api.setSize({ width: 280 });
480
+ // Adding the Agent panel after this group returns causes Dockview to run one
481
+ // more root layout pass. Reapply once that pass has landed; otherwise the
482
+ // inspector absorbs the spare width (observed as ~399px at a 1280px
483
+ // viewport) and leaves the authored document narrower than every tool.
484
+ requestAnimationFrame(() => {
485
+ if (!api.getGroup(AGENT_GROUP_ID)) return;
486
+ api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
487
+ api.getGroup(LEFT_GROUP_ID)?.api.setSize({ width: 260 });
488
+ api.getGroup(RIGHT_GROUP_ID)?.api.setSize({ width: 280 });
489
+ });
490
+ }
491
+
492
+ function ensureBottomEdgeGroup(api: DockviewApi): void {
493
+ if (!api.getEdgeGroup('bottom')) {
494
+ api.addEdgeGroup('bottom', {
495
+ id: BOTTOM_GROUP_ID,
496
+ initialSize: 260,
497
+ minimumSize: 120,
498
+ collapsedSize: 28,
499
+ collapsed: true,
500
+ });
501
+ }
502
+ }
503
+
504
+ function ensureCenterGroup(api: DockviewApi): string {
505
+ // Restored layouts contain several grid groups. "First grid group" is
506
+ // usually the Hierarchy navigator, so resolving generically would append
507
+ // every newly discovered world document there and leave the real center
508
+ // empty. The semantic group id is the durable layout contract.
509
+ const existing = api.getGroup(CENTER_GROUP_ID);
510
+ if (existing) return existing.id;
511
+ return api.addGroup({ id: CENTER_GROUP_ID, direction: 'right' }).id;
512
+ }
513
+
514
+ function ensureAssetNavigators(api: DockviewApi): void {
515
+ if (!api.getPanel(ASSETS_PANEL_ID)) {
516
+ if (!api.getGroup(ASSETS_GROUP_ID)) {
517
+ const hierarchyGroup = api.getPanel(HIERARCHY_PANEL_ID)?.api.group;
518
+ api.addGroup({
519
+ id: ASSETS_GROUP_ID,
520
+ referenceGroup: hierarchyGroup ?? LEFT_GROUP_ID,
521
+ direction: 'below',
522
+ initialHeight: 340,
523
+ constraints: { minimumHeight: 180 },
524
+ });
525
+ }
526
+ addStaticPanel(api, {
527
+ id: ASSETS_PANEL_ID,
528
+ title: 'Assets',
529
+ staticKind: 'assets',
530
+ groupId: ASSETS_GROUP_ID,
531
+ });
532
+ }
533
+ const assetsPanel = api.getPanel(ASSETS_PANEL_ID);
534
+ if (!api.getPanel(ASSET_LIBRARY_PANEL_ID)) {
535
+ addStaticPanel(api, {
536
+ id: ASSET_LIBRARY_PANEL_ID,
537
+ title: 'Library',
538
+ staticKind: 'asset-library',
539
+ groupId: assetsPanel?.api.group.id ?? ASSETS_GROUP_ID,
540
+ inactive: true,
541
+ });
542
+ }
543
+ const libraryPanel = api.getPanel(ASSET_LIBRARY_PANEL_ID);
544
+ if (assetsPanel && libraryPanel && libraryPanel.api.group.id !== assetsPanel.api.group.id) {
545
+ libraryPanel.api.moveTo({ group: assetsPanel.api.group, index: 1, skipSetActive: true });
546
+ }
547
+ }
548
+
549
+ function reconcileStaticPanels(api: DockviewApi): void {
550
+ // The scene-UI insertion catalog used to be promoted to a permanent
551
+ // navigator tab by the first Dockview migration. Creation now comes from
552
+ // the selected node's adapter through Hierarchy's one generic Add menu;
553
+ // discard any older layout that restores the legacy blank panel.
554
+ const legacyUIPalette = api.getPanel('navigator:ui-palette');
555
+ if (legacyUIPalette) api.removePanel(legacyUIPalette);
556
+ const legacyProject = api.getPanel('navigator:project');
557
+ if (legacyProject) api.removePanel(legacyProject);
558
+ const restoredAgentGroup = api.getGroup(AGENT_GROUP_ID);
559
+ if (restoredAgentGroup && !normalizedAgentGroups.has(restoredAgentGroup)) {
560
+ // `fromJSON` restores the constraints serialized by the version that
561
+ // wrote the layout. Refresh them so v7's width correction also applies
562
+ // to an already-present panel, not only to a newly created/restored one.
563
+ restoredAgentGroup.api.setConstraints({ minimumWidth: 300, maximumWidth: 380 });
564
+ restoredAgentGroup.api.setSize({ width: 360 });
565
+ normalizedAgentGroups.add(restoredAgentGroup);
566
+ requestAnimationFrame(() => {
567
+ api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
568
+ });
569
+ }
570
+ const agentPanel = api.getPanel(AGENT_PANEL_ID);
571
+ if (harnessChat().collapsed) {
572
+ if (agentPanel) api.removePanel(agentPanel);
573
+ } else if (!agentPanel) {
574
+ ensureAgentGroup(api);
575
+ addStaticPanel(api, {
576
+ id: AGENT_PANEL_ID,
577
+ title: 'Agent',
578
+ staticKind: 'agent',
579
+ groupId: AGENT_GROUP_ID,
580
+ closeable: true,
581
+ });
582
+ }
583
+ addStaticPanel(api, {
584
+ id: HIERARCHY_PANEL_ID,
585
+ title: 'Hierarchy',
586
+ staticKind: 'hierarchy',
587
+ groupId: LEFT_GROUP_ID,
588
+ });
589
+ // Local Assets and the external Library are permanent peer navigators below
590
+ // Hierarchy. Opening a Library result creates a center asset document; the
591
+ // acquisition surface itself never disappears from the shell.
592
+ ensureAssetNavigators(api);
593
+ addStaticPanel(api, {
594
+ id: INSPECTOR_PANEL_ID,
595
+ title: 'Inspector',
596
+ staticKind: 'inspector',
597
+ groupId: RIGHT_GROUP_ID,
598
+ });
599
+ }
600
+
601
+ function reconcileDocumentPanels(api: DockviewApi, centerGroupId: string): void {
602
+ const documents = openWorkspaceDocuments();
603
+ const documentIds = new Set(documents.map((item) => item.descriptor.id));
604
+ let structureChanged = false;
605
+ for (const panel of api.panels) {
606
+ const params = panel.api.getParameters<WorkspacePanelParams>();
607
+ if (params.kind === 'document' && !documentIds.has(params.documentId)) {
608
+ api.removePanel(panel);
609
+ structureChanged = true;
610
+ }
611
+ }
612
+ let previousPanel = undefined as ReturnType<DockviewApi['getPanel']>;
613
+ for (const document of documents) {
614
+ const existing = api.getPanel(document.descriptor.id);
615
+ if (existing) {
616
+ if (existing.api.title !== document.title) existing.api.setTitle(document.title);
617
+ previousPanel = existing;
618
+ continue;
619
+ }
620
+ previousPanel = api.addPanel<WorkspacePanelParams>({
621
+ id: document.descriptor.id,
622
+ title: document.title,
623
+ component: COMPONENT_ID,
624
+ tabComponent: TAB_COMPONENT_ID,
625
+ renderer: 'always',
626
+ inactive: true,
627
+ position: previousPanel
628
+ ? { referencePanel: previousPanel }
629
+ : { referenceGroup: centerGroupId },
630
+ params: {
631
+ kind: 'document',
632
+ documentId: document.descriptor.id,
633
+ closeable: document.descriptor.closeable !== false,
634
+ },
635
+ });
636
+ structureChanged = true;
637
+ }
638
+
639
+ // Reordering an already-correct rail on every availability/utility tick
640
+ // causes Dockview to visibly rotate active tabs despite `skipSetActive`.
641
+ // The family partition only needs repair after the document structure
642
+ // changes; ordinary reconciliation and user tab focus are read-only.
643
+ if (structureChanged) {
644
+ const orderedDocumentIds = [
645
+ ...documents.filter((document) => isWorldSurfaceDocument(document.descriptor.id)),
646
+ ...documents.filter((document) => !isWorldSurfaceDocument(document.descriptor.id)),
647
+ ].map((document) => document.descriptor.id);
648
+ normalizeDocumentSectionOrder(api, centerGroupId, orderedDocumentIds);
649
+ }
650
+
651
+ // Game is a pinned player-facing anchor, not just whichever document was
652
+ // restored first. Keep it at index zero in its current tab group while
653
+ // leaving user-chosen group placement/docking intact.
654
+ const gamePanel = api.getPanel('workspace:game');
655
+ if (gamePanel && gamePanel.api.group.panels[0]?.id !== gamePanel.id) {
656
+ gamePanel.api.moveTo({ group: gamePanel.api.group, index: 0, skipSetActive: true });
657
+ }
658
+ }
659
+
660
+ function ensureUtilityPanel(
661
+ api: DockviewApi,
662
+ utility: WorkspaceUtilityRegistration,
663
+ inactive = true,
664
+ ): void {
665
+ const panelId = `utility:${utility.id}`;
666
+ const existing = api.getPanel(panelId);
667
+ if (existing) {
668
+ if (existing.api.title !== utility.title) existing.api.setTitle(utility.title);
669
+ return;
670
+ }
671
+ api.addPanel<WorkspacePanelParams>({
672
+ id: panelId,
673
+ title: utility.title,
674
+ component: COMPONENT_ID,
675
+ tabComponent: TAB_COMPONENT_ID,
676
+ renderer: 'always',
677
+ inactive,
678
+ position: { referenceGroup: BOTTOM_GROUP_ID },
679
+ params: {
680
+ kind: 'utility',
681
+ utilityId: utility.id,
682
+ closeable: utility.closeable ?? false,
683
+ },
684
+ });
685
+ }
686
+
687
+ function reconcileUtilityPanels(api: DockviewApi): void {
688
+ const utilities = availableWorkspaceUtilities();
689
+ const utilityIds = new Set(utilities.map((item) => item.id));
690
+ for (const panel of api.panels) {
691
+ const params = panel.api.getParameters<WorkspacePanelParams>();
692
+ if (params.kind === 'utility' && !utilityIds.has(params.utilityId)) api.removePanel(panel);
693
+ }
694
+ for (const utility of utilities) {
695
+ if (utility.visibleByDefault !== false) ensureUtilityPanel(api, utility);
696
+ }
697
+ }
698
+
699
+ function reconcileWorkspace(api: DockviewApi): void {
700
+ const centerGroupId = ensureCenterGroup(api);
701
+ ensureSideGroups(api, centerGroupId);
702
+ ensureBottomEdgeGroup(api);
703
+ reconcileStaticPanels(api);
704
+ reconcileDocumentPanels(api, centerGroupId);
705
+ reconcileUtilityPanels(api);
706
+ }
707
+
708
+ export function WorkspaceDock({ onOpenScene }: { onOpenScene: (assetPath: string) => void }) {
709
+ const store = useEditorStore();
710
+ const apiRef = useRef<DockviewApi | null>(null);
711
+ const readyCleanupRef = useRef<(() => void) | null>(null);
712
+ const lastSyncedDocumentRef = useRef<string | null>(null);
713
+ const documentVersion = useSyncExternalStore(
714
+ subscribeWorkspaceDocuments,
715
+ workspaceDocumentRegistryVersion,
716
+ workspaceDocumentRegistryVersion,
717
+ );
718
+ const utilityVersion = useSyncExternalStore(
719
+ subscribeWorkspaceUtilities,
720
+ workspaceUtilityRegistryVersion,
721
+ workspaceUtilityRegistryVersion,
722
+ );
723
+ const availabilityVersion = useSyncExternalStore(
724
+ subscribeAvailabilityTick,
725
+ availabilityTickVersion,
726
+ availabilityTickVersion,
727
+ );
728
+ const agentLayoutVersion = useSyncExternalStore(
729
+ subscribeHarnessChatLayout,
730
+ harnessChatLayoutVersion,
731
+ harnessChatLayoutVersion,
732
+ );
733
+ const activeDocumentId = activeWorkspaceDocumentId();
734
+
735
+ const resetLayout = useCallback(() => {
736
+ const api = apiRef.current;
737
+ if (!api) return;
738
+ api.clear();
739
+ // Dockview disposes panel renderers after `clear()` returns. Reusing the
740
+ // same panel ids in that call stack can therefore leave fresh headers
741
+ // attached to renderers that are about to be torn down. Rebuild on the
742
+ // next frame, once the cleared React panel tree has unmounted.
743
+ requestAnimationFrame(() => {
744
+ if (apiRef.current !== api) return;
745
+ reconcileWorkspace(api);
746
+ api.getEdgeGroup('bottom')?.collapse();
747
+ const activeDocumentId = activeWorkspaceDocumentId();
748
+ if (activeDocumentId) api.getPanel(activeDocumentId)?.api.setActive();
749
+ });
750
+ }, []);
751
+
752
+ useEffect(() => {
753
+ if (apiRef.current) reconcileWorkspace(apiRef.current);
754
+ }, [documentVersion, utilityVersion, availabilityVersion, agentLayoutVersion]);
755
+
756
+ useEffect(() => {
757
+ const api = apiRef.current;
758
+ if (!api || activeDocumentId === lastSyncedDocumentRef.current) return;
759
+ lastSyncedDocumentRef.current = activeDocumentId;
760
+ if (activeDocumentId) api.getPanel(activeDocumentId)?.api.setActive();
761
+ }, [activeDocumentId, documentVersion]);
762
+
763
+ useEffect(
764
+ () => () => {
765
+ readyCleanupRef.current?.();
766
+ readyCleanupRef.current = null;
767
+ },
768
+ [],
769
+ );
770
+
771
+ const onReady = useCallback(
772
+ ({ api }: { api: DockviewApi }) => {
773
+ apiRef.current = api;
774
+ reconcileWorkspace(api);
775
+ const uninstallPersistence = installWorkspaceStatePersistence(store, api, () =>
776
+ reconcileWorkspace(api),
777
+ );
778
+ const initialDocumentId = activeWorkspaceDocumentId();
779
+ lastSyncedDocumentRef.current = initialDocumentId;
780
+ if (initialDocumentId) api.getPanel(initialDocumentId)?.api.setActive();
781
+
782
+ const activeDisposable = api.onDidActivePanelChange((event) => {
783
+ if (event.origin !== 'user' || !event.panel) return;
784
+ const params = event.panel.api.getParameters<WorkspacePanelParams>();
785
+ if (params.kind === 'document') activateWorkspaceDocument(params.documentId);
786
+ });
787
+
788
+ const commands: WorkspaceDockCommands = {
789
+ showNavigator(id) {
790
+ const panelId =
791
+ id === 'hierarchy'
792
+ ? HIERARCHY_PANEL_ID
793
+ : id === 'asset-library'
794
+ ? ASSET_LIBRARY_PANEL_ID
795
+ : ASSETS_PANEL_ID;
796
+ const panel = api.getPanel(panelId);
797
+ panel?.api.setActive();
798
+ },
799
+ showUtility(id) {
800
+ reconcileWorkspace(api);
801
+ const utility = availableWorkspaceUtilities().find((item) => item.id === id);
802
+ if (!utility) return;
803
+ ensureUtilityPanel(api, utility, false);
804
+ const panel = api.getPanel(`utility:${id}`);
805
+ if (!panel) return;
806
+ panel.api.setActive();
807
+ if (panel.api.location.type === 'edge') panel.api.group.api.expand();
808
+ },
809
+ toggleUtility(id) {
810
+ reconcileWorkspace(api);
811
+ const utility = availableWorkspaceUtilities().find((item) => item.id === id);
812
+ if (!utility) return;
813
+ ensureUtilityPanel(api, utility, false);
814
+ const panel = api.getPanel(`utility:${id}`);
815
+ if (!panel) return;
816
+ const group = panel.api.group.api;
817
+ if (panel.api.isActive && panel.api.location.type === 'edge' && !group.isCollapsed()) {
818
+ group.collapse();
819
+ } else {
820
+ panel.api.setActive();
821
+ if (panel.api.location.type === 'edge') group.expand();
822
+ }
823
+ },
824
+ showAuxiliary(id) {
825
+ if (id !== 'undo-history') return;
826
+ if (!api.getPanel(HISTORY_PANEL_ID)) {
827
+ addStaticPanel(api, {
828
+ id: HISTORY_PANEL_ID,
829
+ title: 'Undo History',
830
+ staticKind: 'history',
831
+ groupId: RIGHT_GROUP_ID,
832
+ closeable: true,
833
+ });
834
+ }
835
+ api.getPanel(HISTORY_PANEL_ID)?.api.setActive();
836
+ api.getPanel(HISTORY_PANEL_ID)?.api.group.api.expand();
837
+ },
838
+ toggleFocus() {
839
+ if (api.hasMaximizedGroup()) {
840
+ api.exitMaximizedGroup();
841
+ api.setEdgeGroupVisible('bottom', true);
842
+ } else {
843
+ const activeDocument = activeWorkspaceDocumentId();
844
+ const focusPanel =
845
+ (activeDocument ? api.getPanel(activeDocument) : undefined) ?? api.activePanel;
846
+ if (!focusPanel) return;
847
+ api.setEdgeGroupVisible('bottom', false);
848
+ api.maximizeGroup(focusPanel);
849
+ }
850
+ },
851
+ resetLayout,
852
+ };
853
+ const uninstallCommands = installWorkspaceDockCommands(commands);
854
+ readyCleanupRef.current?.();
855
+ readyCleanupRef.current = () => {
856
+ activeDisposable.dispose();
857
+ uninstallPersistence();
858
+ uninstallCommands();
859
+ if (apiRef.current === api) apiRef.current = null;
860
+ };
861
+ },
862
+ [resetLayout, store],
863
+ );
864
+
865
+ const components = useRef({ [COMPONENT_ID]: WorkspacePanel }).current;
866
+ const tabs = useRef({ [TAB_COMPONENT_ID]: WorkspaceTab }).current;
867
+
868
+ return (
869
+ <main
870
+ className="vgai-workspace-dock"
871
+ aria-label="Editor workspace"
872
+ data-testid="workspace-dock"
873
+ data-editor-hotkey-scope="workspace"
874
+ data-editor-hotkey-fallback
875
+ >
876
+ <OpenSceneContext.Provider value={onOpenScene}>
877
+ <DockviewReact
878
+ components={components}
879
+ tabComponents={tabs}
880
+ onReady={onReady}
881
+ theme={vgaiDockviewTheme}
882
+ defaultRenderer="always"
883
+ keyboardNavigation
884
+ disableFloatingGroups={false}
885
+ singleTabMode="fullwidth"
886
+ tabGroupAccent="off"
887
+ />
888
+ </OpenSceneContext.Provider>
889
+ </main>
890
+ );
891
+ }