@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
@@ -1,76 +1,125 @@
1
+ import type { ActionValueType, InputBinding, InputMapFile } from '@engine/input/input-types';
1
2
  import { InputMapFileSchema } from '@engine/input/schema';
2
3
  import { resolveUrl } from '@engine/loader';
3
- import { useEffect, useState } from 'react';
4
- import { SectionHeader } from '../primitives/SectionHeader';
5
-
6
- const labelStyle: React.CSSProperties = { fontSize: 11, color: '#8c8c8c', marginBottom: 2 };
7
- const cellStyle: React.CSSProperties = {
8
- fontSize: 11,
9
- color: '#c5c8ce',
10
- padding: '3px 4px',
11
- borderBottom: '1px solid #333',
4
+ import { useCallback, useEffect, useState } from 'react';
5
+ import { Button, Select, TextInput, themeVars } from '../../design-system';
6
+ import {
7
+ registerWorkspaceDocumentSaveHandler,
8
+ setWorkspaceDocumentDirty,
9
+ } from '../../workspace-document-registry';
10
+ import { subscribeProjectAsset, writeProjectAsset } from '../asset-editor-persistence';
11
+
12
+ const VALUE_TYPES: ActionValueType[] = [
13
+ 'digital',
14
+ 'scalar',
15
+ 'vector2',
16
+ 'pointerDelta',
17
+ 'pointerPosition',
18
+ ];
19
+ const BINDING_TYPES_BY_VALUE_TYPE: Record<ActionValueType, InputBinding['type'][]> = {
20
+ digital: ['key', 'mouse_button', 'gamepad_button', 'gamepad_axis', 'touch_button'],
21
+ scalar: ['gamepad_axis', 'test_axis'],
22
+ vector2: ['mouse_move', 'gamepad_axis_pair', 'touch_stick', 'test_vector2'],
23
+ pointerDelta: ['mouse_move', 'test_pointer_delta'],
24
+ pointerPosition: ['test_pointer_position'],
12
25
  };
13
26
 
14
- interface Binding {
15
- type: string;
16
- code?: string;
17
- button?: number;
18
- axis?: number;
19
- xAxis?: number;
20
- yAxis?: number;
21
- direction?: string;
22
- deadzone?: number;
27
+ export function compatibleBindingTypes(
28
+ valueType: ActionValueType,
29
+ ): readonly InputBinding['type'][] {
30
+ return BINDING_TYPES_BY_VALUE_TYPE[valueType];
23
31
  }
24
32
 
25
- interface InputMapData {
26
- version?: number;
27
- actions?: Record<string, { bindings: Binding[] }>;
33
+ export function createInputBindingForValueType(valueType: ActionValueType): InputBinding {
34
+ return createInputBinding(BINDING_TYPES_BY_VALUE_TYPE[valueType][0]!);
28
35
  }
29
36
 
30
- function describeBinding(b: Binding): string {
31
- switch (b.type) {
37
+ export function createInputBinding(type: InputBinding['type']): InputBinding {
38
+ switch (type) {
32
39
  case 'key':
33
- return b.code?.replace('Key', '') ?? 'Key';
40
+ return { type, code: 'Space' };
34
41
  case 'mouse_button':
35
- return `Mouse ${b.button === 0 ? 'Left' : b.button === 1 ? 'Middle' : 'Right'}`;
42
+ return { type, button: 0 };
43
+ case 'mouse_move':
44
+ return { type };
36
45
  case 'gamepad_button':
37
- return `Gamepad Button ${b.button}`;
46
+ return { type, button: 0 };
38
47
  case 'gamepad_axis':
39
- return `Gamepad Axis ${b.axis}${b.direction ? ` ${b.direction}` : ''}`;
40
- // No `mouse_move`/`gamepad_axis_pair` cases: those binding kinds are
41
- // REJECTED at parse by InputMapFileSchema (T4.1/T4.6 dead stubs), so a
42
- // file containing them never reaches this preview — it shows the
43
- // invalid-file error state instead (R3, §7.2: this panel used to echo
44
- // them from a raw fetch, past the schema).
45
- default:
46
- return b.type;
48
+ return { type, axis: 0, direction: 'positive', deadzone: 0.15 };
49
+ case 'gamepad_axis_pair':
50
+ return { type, xAxis: 0, yAxis: 1, deadzone: 0.15 };
51
+ case 'touch_button':
52
+ return { type, sourceId: 'button' };
53
+ case 'touch_stick':
54
+ return { type, sourceId: 'stick', deadzone: 0.15 };
55
+ case 'test_axis':
56
+ return { type, sourceId: 'axis', deadzone: 0.15 };
57
+ case 'test_vector2':
58
+ return { type, sourceId: 'vector', deadzone: 0.15 };
59
+ case 'test_pointer_delta':
60
+ return { type, sourceId: 'pointer-delta' };
61
+ case 'test_pointer_position':
62
+ return { type, sourceId: 'pointer-position' };
63
+ }
64
+ }
65
+
66
+ export function describeBinding(binding: InputBinding): string {
67
+ switch (binding.type) {
68
+ case 'key':
69
+ return binding.code;
70
+ case 'mouse_button':
71
+ return `Mouse button ${binding.button}`;
72
+ case 'mouse_move':
73
+ return 'Mouse movement';
74
+ case 'gamepad_button':
75
+ return `Gamepad button ${binding.button}`;
76
+ case 'gamepad_axis':
77
+ return `Gamepad axis ${binding.axis} ${binding.direction}`;
78
+ case 'gamepad_axis_pair':
79
+ return `Gamepad axes ${binding.xAxis}/${binding.yAxis}`;
80
+ case 'touch_button':
81
+ return `Touch button “${binding.sourceId}”`;
82
+ case 'touch_stick':
83
+ return `Touch stick “${binding.sourceId}”`;
84
+ case 'test_axis':
85
+ case 'test_vector2':
86
+ case 'test_pointer_delta':
87
+ case 'test_pointer_position':
88
+ return `${binding.type.replaceAll('_', ' ')} “${binding.sourceId}”`;
47
89
  }
48
90
  }
49
91
 
50
- /**
51
- * R3 (MASTER-ARCHITECTURE-REVIEW.md §7.2): the pure validate-before-preview
52
- * data path, exported for unit tests (the repo's component-test convention —
53
- * see network-inspector.test.ts: node environment, no jsdom; test the data
54
- * function the component renders from). The panel used to raw-fetch and
55
- * render whatever JSON came back — a file the runtime will reject (T4.6
56
- * fetch-validation) showed a normal-looking preview.
57
- */
58
92
  export function validateInputMapForViewer(
59
93
  json: unknown,
60
- ): { ok: true; data: InputMapData } | { ok: false; issues: string[] } {
94
+ ): { ok: true; data: InputMapFile } | { ok: false; issues: string[] } {
61
95
  const result = InputMapFileSchema.safeParse(json);
62
- if (!result.success) {
63
- return {
64
- ok: false,
65
- issues: result.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`),
66
- };
67
- }
68
- // Render the original JSON (now proven valid) — the local view interfaces
69
- // are a structural subset of InputMapFile.
70
- return { ok: true, data: json as InputMapData };
96
+ // Preserve the authored bytes' optional/default shape; validation is the
97
+ // gate, while the editor presents `digital` when valueType is omitted.
98
+ if (result.success) return { ok: true, data: json as InputMapFile };
99
+ return {
100
+ ok: false,
101
+ issues: result.error.issues.map(
102
+ (issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`,
103
+ ),
104
+ };
105
+ }
106
+
107
+ export function renameInputAction(
108
+ inputMap: InputMapFile,
109
+ currentName: string,
110
+ requestedName: string,
111
+ ): InputMapFile | null {
112
+ if (requestedName === currentName) return inputMap;
113
+ if (requestedName.trim() === '' || inputMap.actions[requestedName]) return null;
114
+ const actions = { ...inputMap.actions };
115
+ const action = actions[currentName];
116
+ if (!action) return null;
117
+ delete actions[currentName];
118
+ actions[requestedName] = action;
119
+ return { ...inputMap, actions };
71
120
  }
72
121
 
73
- /** Loud invalid-file state: file name + first issues (a reusable asset-viewer error panel). */
122
+ /** Loud invalid-file state shared by schema-backed asset editors. */
74
123
  export function AssetValidationError({
75
124
  kind,
76
125
  fileName,
@@ -80,21 +129,23 @@ export function AssetValidationError({
80
129
  fileName: string;
81
130
  issues: string[];
82
131
  }) {
83
- const shown = issues.slice(0, 3);
132
+ const shown = issues.slice(0, 5);
84
133
  return (
85
134
  <div style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 6 }}>
86
- <div style={{ color: '#F53352', fontSize: 12, fontWeight: 700 }}>
135
+ <div style={{ color: themeVars.semantic.danger, fontSize: 12, fontWeight: 700 }}>
87
136
  Invalid {kind} — the runtime will reject this file
88
137
  </div>
89
- <div style={{ color: '#c5c8ce', fontSize: 11, wordBreak: 'break-all' }}>{fileName}</div>
138
+ <div style={{ color: themeVars.content.primary, fontSize: 11, wordBreak: 'break-all' }}>
139
+ {fileName}
140
+ </div>
90
141
  <div
91
142
  style={{
92
- color: '#F53352',
143
+ color: themeVars.semantic.danger,
93
144
  fontSize: 11,
94
- fontFamily: 'monospace',
145
+ fontFamily: themeVars.typography.mono,
95
146
  whiteSpace: 'pre-wrap',
96
- border: '1px solid #F53352',
97
- borderRadius: 3,
147
+ border: `1px solid ${themeVars.semantic.danger}`,
148
+ borderRadius: themeVars.shape.small,
98
149
  padding: 8,
99
150
  }}
100
151
  >
@@ -107,100 +158,427 @@ export function AssetValidationError({
107
158
  );
108
159
  }
109
160
 
110
- export function InputMapViewer({ assetPath }: { assetPath: string }) {
111
- const [data, setData] = useState<InputMapData | null>(null);
112
- const [error, setError] = useState(false);
113
- const [invalid, setInvalid] = useState<string[] | null>(null);
161
+ function NumberField({
162
+ label,
163
+ value,
164
+ onChange,
165
+ }: {
166
+ label: string;
167
+ value: number | undefined;
168
+ onChange: (value: number | undefined) => void;
169
+ }) {
170
+ return (
171
+ <label
172
+ style={{
173
+ display: 'flex',
174
+ alignItems: 'center',
175
+ gap: 4,
176
+ fontSize: 10,
177
+ color: themeVars.content.muted,
178
+ }}
179
+ >
180
+ {label}
181
+ <TextInput
182
+ className="vgai-input"
183
+ type="number"
184
+ value={value ?? ''}
185
+ onChange={(event) =>
186
+ onChange(event.target.value === '' ? undefined : Number(event.target.value))
187
+ }
188
+ style={{ width: 68 }}
189
+ />
190
+ </label>
191
+ );
192
+ }
193
+
194
+ function BindingFields({
195
+ binding,
196
+ onChange,
197
+ }: {
198
+ binding: InputBinding;
199
+ onChange: (binding: InputBinding) => void;
200
+ }) {
201
+ const number = (label: string, key: 'button' | 'axis' | 'xAxis' | 'yAxis' | 'deadzone') => (
202
+ <NumberField
203
+ label={label}
204
+ value={(binding as unknown as Record<string, number | undefined>)[key]}
205
+ onChange={(value) => {
206
+ const next = { ...binding } as unknown as Record<string, unknown>;
207
+ if (value === undefined) delete next[key];
208
+ else next[key] = value;
209
+ onChange(next as unknown as InputBinding);
210
+ }}
211
+ />
212
+ );
213
+ if (binding.type === 'key') {
214
+ return (
215
+ <TextInput
216
+ aria-label="Keyboard code"
217
+ className="vgai-input"
218
+ value={binding.code}
219
+ onChange={(event) => onChange({ ...binding, code: event.target.value })}
220
+ style={{ width: 120 }}
221
+ />
222
+ );
223
+ }
224
+ if ('sourceId' in binding) {
225
+ return (
226
+ <>
227
+ <TextInput
228
+ aria-label="Source id"
229
+ className="vgai-input"
230
+ value={binding.sourceId}
231
+ onChange={(event) => onChange({ ...binding, sourceId: event.target.value })}
232
+ style={{ width: 120 }}
233
+ />
234
+ {'deadzone' in binding && number('Deadzone', 'deadzone')}
235
+ </>
236
+ );
237
+ }
238
+ switch (binding.type) {
239
+ case 'mouse_button':
240
+ case 'gamepad_button':
241
+ return number('Button', 'button');
242
+ case 'mouse_move':
243
+ return number('Deadzone', 'deadzone');
244
+ case 'gamepad_axis':
245
+ return (
246
+ <>
247
+ {number('Axis', 'axis')}
248
+ <Select
249
+ className="vgai-select"
250
+ aria-label="Axis direction"
251
+ value={binding.direction}
252
+ onChange={(event) =>
253
+ onChange({ ...binding, direction: event.target.value as 'positive' | 'negative' })
254
+ }
255
+ >
256
+ <option value="positive">positive</option>
257
+ <option value="negative">negative</option>
258
+ </Select>
259
+ {number('Deadzone', 'deadzone')}
260
+ </>
261
+ );
262
+ case 'gamepad_axis_pair':
263
+ return (
264
+ <>
265
+ {number('X axis', 'xAxis')}
266
+ {number('Y axis', 'yAxis')}
267
+ {number('Deadzone', 'deadzone')}
268
+ </>
269
+ );
270
+ default:
271
+ return null;
272
+ }
273
+ }
274
+
275
+ function BindingRow({
276
+ binding,
277
+ valueType,
278
+ onChange,
279
+ onRemove,
280
+ }: {
281
+ binding: InputBinding;
282
+ valueType: ActionValueType;
283
+ onChange: (binding: InputBinding) => void;
284
+ onRemove: () => void;
285
+ }) {
286
+ const compatibleTypes = compatibleBindingTypes(valueType);
287
+ const compatible = compatibleTypes.includes(binding.type);
288
+ const shownTypes = compatible
289
+ ? compatibleTypes
290
+ : [binding.type, ...compatibleTypes.filter((type) => type !== binding.type)];
291
+ return (
292
+ <div
293
+ style={{
294
+ display: 'flex',
295
+ flexWrap: 'wrap',
296
+ alignItems: 'center',
297
+ gap: 6,
298
+ padding: 7,
299
+ background: themeVars.surface.inset,
300
+ border: `1px solid ${themeVars.boundary.default}`,
301
+ borderRadius: themeVars.shape.small,
302
+ }}
303
+ >
304
+ <Select
305
+ className="vgai-select"
306
+ aria-label="Binding type"
307
+ value={binding.type}
308
+ onChange={(event) =>
309
+ onChange(createInputBinding(event.target.value as InputBinding['type']))
310
+ }
311
+ >
312
+ {shownTypes.map((type) => (
313
+ <option key={type} value={type}>
314
+ {type}
315
+ </option>
316
+ ))}
317
+ </Select>
318
+ <BindingFields binding={binding} onChange={onChange} />
319
+ <span
320
+ title={describeBinding(binding)}
321
+ style={{
322
+ flex: 1,
323
+ minWidth: 100,
324
+ color: compatible ? themeVars.content.dim : themeVars.semantic.danger,
325
+ fontSize: 10,
326
+ }}
327
+ >
328
+ {describeBinding(binding)}
329
+ {!compatible && ` · not read by ${valueType} actions`}
330
+ </span>
331
+ <Button type="button" variant="danger" size="compact" onClick={onRemove}>
332
+ Remove
333
+ </Button>
334
+ </div>
335
+ );
336
+ }
337
+
338
+ export function InputMapViewer({
339
+ assetPath,
340
+ documentId = assetPath,
341
+ }: {
342
+ assetPath: string;
343
+ documentId?: string;
344
+ }) {
345
+ const [data, setData] = useState<InputMapFile | null>(null);
346
+ const [loadIssues, setLoadIssues] = useState<string[] | null>(null);
347
+ const [saveError, setSaveError] = useState<string | null>(null);
348
+ const [saved, setSaved] = useState(false);
349
+ const [revision, setRevision] = useState(0);
114
350
  const fileName = assetPath.split('/').pop() ?? assetPath;
115
351
 
352
+ useEffect(
353
+ () => subscribeProjectAsset(assetPath, () => setRevision((value) => value + 1)),
354
+ [assetPath],
355
+ );
116
356
  useEffect(() => {
117
357
  let cancelled = false;
118
358
  setData(null);
119
- setError(false);
120
- setInvalid(null);
121
- fetch(resolveUrl(assetPath))
122
- .then((r) => r.json())
359
+ setLoadIssues(null);
360
+ fetch(resolveUrl(assetPath), { cache: 'no-store' })
361
+ .then((response) => {
362
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
363
+ return response.json();
364
+ })
123
365
  .then((json) => {
124
366
  if (cancelled) return;
125
367
  const result = validateInputMapForViewer(json);
126
- if (result.ok) setData(result.data);
127
- else setInvalid(result.issues);
368
+ if (result.ok) {
369
+ setData(result.data);
370
+ setWorkspaceDocumentDirty(documentId, false);
371
+ } else setLoadIssues(result.issues);
128
372
  })
129
- .catch(() => {
130
- if (!cancelled) setError(true);
373
+ .catch((reason) => {
374
+ if (!cancelled) setLoadIssues([reason instanceof Error ? reason.message : String(reason)]);
131
375
  });
132
376
  return () => {
133
377
  cancelled = true;
134
378
  };
135
- }, [assetPath]);
379
+ }, [assetPath, documentId, revision]);
136
380
 
137
- if (error) {
138
- return <div style={{ padding: 16, color: '#F53352', fontSize: 12 }}>Failed to load file</div>;
139
- }
140
- if (invalid) {
141
- return <AssetValidationError kind="input map" fileName={fileName} issues={invalid} />;
142
- }
143
- if (!data) {
144
- return <div style={{ padding: 16, color: '#8c8c8c', fontSize: 12 }}>Loading...</div>;
145
- }
381
+ const change = (next: InputMapFile) => {
382
+ setData(next);
383
+ setSaved(false);
384
+ setSaveError(null);
385
+ setWorkspaceDocumentDirty(documentId, true);
386
+ };
146
387
 
147
- const actions = data.actions ? Object.entries(data.actions) : [];
388
+ const save = useCallback(async (): Promise<boolean> => {
389
+ if (!data) return false;
390
+ const result = validateInputMapForViewer(data);
391
+ if (!result.ok) {
392
+ setSaveError(result.issues.join('\n'));
393
+ return false;
394
+ }
395
+ try {
396
+ const ok = await writeProjectAsset(assetPath, `${JSON.stringify(result.data, null, 2)}\n`, {
397
+ label: `Edit ${fileName}`,
398
+ kind: 'settings',
399
+ });
400
+ if (!ok) throw new Error('The project history rejected the input-map write.');
401
+ setData(result.data);
402
+ setWorkspaceDocumentDirty(documentId, false);
403
+ setSaved(true);
404
+ return true;
405
+ } catch (reason) {
406
+ setSaveError(reason instanceof Error ? reason.message : String(reason));
407
+ return false;
408
+ }
409
+ }, [assetPath, data, documentId, fileName]);
410
+
411
+ useEffect(() => registerWorkspaceDocumentSaveHandler(documentId, save), [documentId, save]);
412
+
413
+ if (loadIssues)
414
+ return <AssetValidationError kind="input map" fileName={fileName} issues={loadIssues} />;
415
+ if (!data)
416
+ return (
417
+ <div style={{ padding: 16, color: themeVars.content.muted, fontSize: 12 }}>Loading…</div>
418
+ );
419
+
420
+ const entries = Object.entries(data.actions);
421
+ const updateAction = (name: string, valueType: ActionValueType, bindings: InputBinding[]) => {
422
+ const actions = { ...data.actions };
423
+ actions[name] = { valueType, bindings };
424
+ change({ ...data, actions });
425
+ };
148
426
 
149
427
  return (
150
- <div style={{ padding: 8, display: 'flex', flexDirection: 'column', gap: 8 }}>
151
- <div style={{ fontWeight: 600, fontSize: 12, color: '#c5c8ce', wordBreak: 'break-all' }}>
152
- {fileName}
428
+ <div style={{ padding: 14, display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
429
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
430
+ <div style={{ color: themeVars.content.primary, fontSize: 13, fontWeight: 600, flex: 1 }}>
431
+ {fileName}
432
+ </div>
433
+ <span style={{ color: themeVars.content.dim, fontSize: 10 }}>version {data.version}</span>
434
+ {saved && <span style={{ color: themeVars.semantic.success, fontSize: 11 }}>Saved</span>}
435
+ <Button type="button" variant="primary" size="compact" onClick={() => void save()}>
436
+ Save
437
+ </Button>
153
438
  </div>
154
-
155
- <SectionHeader label="Actions">
156
- <div style={{ display: 'flex', flexDirection: 'column' }}>
157
- {actions.map(([name, action]) => (
158
- <div
159
- key={name}
160
- style={{ ...cellStyle, display: 'flex', flexDirection: 'column', gap: 2 }}
161
- >
162
- <span style={{ fontWeight: 600 }}>{name}</span>
163
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
164
- {action.bindings.map((b, i) => (
165
- <span
166
- key={i}
167
- style={{
168
- background: '#333',
169
- border: '1px solid #444',
170
- borderRadius: 3,
171
- padding: '1px 6px',
172
- fontSize: 10,
173
- color: '#c5c8ce',
174
- }}
175
- >
176
- {describeBinding(b)}
177
- </span>
439
+ {entries.map(([name, action]) => {
440
+ const valueType = action.valueType ?? 'digital';
441
+ return (
442
+ <section
443
+ key={name}
444
+ style={{
445
+ border: `1px solid ${themeVars.boundary.default}`,
446
+ borderRadius: themeVars.shape.medium,
447
+ background: themeVars.surface.chrome,
448
+ padding: 9,
449
+ display: 'flex',
450
+ flexDirection: 'column',
451
+ gap: 7,
452
+ }}
453
+ >
454
+ <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
455
+ <TextInput
456
+ aria-label="Action name"
457
+ className="vgai-input"
458
+ defaultValue={name}
459
+ onBlur={(event) => {
460
+ const renamed = renameInputAction(data, name, event.target.value);
461
+ if (renamed) {
462
+ if (renamed !== data) change(renamed);
463
+ return;
464
+ }
465
+ event.target.value = name;
466
+ setSaveError('Action names must be non-empty and unique.');
467
+ }}
468
+ onKeyDown={(event) => {
469
+ if (event.key === 'Enter') event.currentTarget.blur();
470
+ if (event.key === 'Escape') {
471
+ event.currentTarget.value = name;
472
+ event.currentTarget.blur();
473
+ }
474
+ }}
475
+ style={{ flex: 1, minWidth: 120, fontWeight: 600 }}
476
+ />
477
+ <Select
478
+ aria-label="Action value type"
479
+ className="vgai-select"
480
+ value={valueType}
481
+ onChange={(event) =>
482
+ updateAction(name, event.target.value as ActionValueType, action.bindings)
483
+ }
484
+ >
485
+ {VALUE_TYPES.map((type) => (
486
+ <option key={type} value={type}>
487
+ {type}
488
+ </option>
178
489
  ))}
179
- </div>
490
+ </Select>
491
+ <Button
492
+ type="button"
493
+ variant="danger"
494
+ size="compact"
495
+ onClick={() => {
496
+ const actions = { ...data.actions };
497
+ delete actions[name];
498
+ change({ ...data, actions });
499
+ }}
500
+ >
501
+ Delete action
502
+ </Button>
180
503
  </div>
181
- ))}
182
- {actions.length === 0 && (
183
- <div style={{ fontSize: 11, color: '#8c8c8c', padding: 4 }}>No actions defined</div>
184
- )}
185
- </div>
186
- </SectionHeader>
187
-
188
- <SectionHeader label="Info" defaultOpen={false}>
189
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
190
- <div>
191
- <div style={labelStyle}>Path</div>
192
- <div style={{ fontSize: 11, color: '#c5c8ce', wordBreak: 'break-all' }}>
193
- {assetPath}
194
- </div>
195
- </div>
196
- {data.version != null && (
197
- <div>
198
- <div style={labelStyle}>Version</div>
199
- <div style={{ fontSize: 11, color: '#c5c8ce' }}>{data.version}</div>
200
- </div>
201
- )}
202
- </div>
203
- </SectionHeader>
504
+ {action.bindings.map((binding, index) => (
505
+ <BindingRow
506
+ key={`${binding.type}:${index}`}
507
+ binding={binding}
508
+ valueType={valueType}
509
+ onChange={(nextBinding) =>
510
+ updateAction(
511
+ name,
512
+ valueType,
513
+ action.bindings.map((item, itemIndex) =>
514
+ itemIndex === index ? nextBinding : item,
515
+ ),
516
+ )
517
+ }
518
+ onRemove={() =>
519
+ updateAction(
520
+ name,
521
+ valueType,
522
+ action.bindings.filter((_, itemIndex) => itemIndex !== index),
523
+ )
524
+ }
525
+ />
526
+ ))}
527
+ <Button
528
+ type="button"
529
+ variant="secondary"
530
+ size="compact"
531
+ onClick={() =>
532
+ updateAction(name, valueType, [
533
+ ...action.bindings,
534
+ createInputBindingForValueType(valueType),
535
+ ])
536
+ }
537
+ style={{ alignSelf: 'flex-start' }}
538
+ >
539
+ + Add binding
540
+ </Button>
541
+ </section>
542
+ );
543
+ })}
544
+ <Button
545
+ type="button"
546
+ variant="primary"
547
+ size="compact"
548
+ onClick={() => {
549
+ let index = entries.length + 1;
550
+ while (data.actions[`action_${index}`]) index++;
551
+ change({
552
+ ...data,
553
+ actions: {
554
+ ...data.actions,
555
+ [`action_${index}`]: { valueType: 'digital', bindings: [] },
556
+ },
557
+ });
558
+ }}
559
+ style={{ alignSelf: 'flex-start' }}
560
+ >
561
+ + Add action
562
+ </Button>
563
+ {entries.length === 0 && (
564
+ <div style={{ color: themeVars.content.dim, fontSize: 11 }}>No actions defined.</div>
565
+ )}
566
+ {saveError && (
567
+ <pre
568
+ style={{
569
+ margin: 0,
570
+ color: themeVars.semantic.danger,
571
+ fontFamily: themeVars.typography.mono,
572
+ whiteSpace: 'pre-wrap',
573
+ fontSize: 10,
574
+ }}
575
+ >
576
+ {saveError}
577
+ </pre>
578
+ )}
579
+ <div style={{ color: themeVars.content.dim, fontSize: 10, wordBreak: 'break-all' }}>
580
+ {assetPath}
581
+ </div>
204
582
  </div>
205
583
  );
206
584
  }