@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,3 +1,4 @@
1
+ import { Button, Checkbox, Select, TextInput } from '../design-system';
1
2
  /**
2
3
  * React-world inspector section (C2, spec 27 §5 C2,
3
4
  * docs/unified-world-editor/27-visual-react-editing.md) — the "wire the
@@ -19,7 +20,7 @@
19
20
  * `PropertyDescriptor.group` (falling back to one flat "Style" group for an
20
21
  * adapter, like `ReactDomAuthoringAdapter`, whose descriptors declare none),
21
22
  * mapping each descriptor to the right C1 widget instead of a generic
22
- * `<input>` — see `widgetKindFor`'s doc comment for the exact mapping table.
23
+ * `<TextInput>` — see `widgetKindFor`'s doc comment for the exact mapping table.
23
24
  * `suppresses: ['properties']` replaces the shell's generic property grid
24
25
  * for a matched node (Rule zero: this section still talks to the node ONLY
25
26
  * through `adapter.inspector`/`adapter.hierarchy`, never the DOM/fiber the
@@ -39,7 +40,7 @@
39
40
  */
40
41
 
41
42
  import type { AuthoringAdapter, EditorNode, PropertyDescriptor } from '@engine/adapter';
42
- import { useMemo } from 'react';
43
+ import { useMemo, useState } from 'react';
43
44
  import { groupProperties } from '../components/inspector-property-grouping';
44
45
  import {
45
46
  AlignmentGrid,
@@ -71,12 +72,15 @@ import {
71
72
  TextShadowEditor,
72
73
  THEME,
73
74
  } from '../components/inspector-widgets';
75
+ import { LABEL_COL_WIDTH } from '../components/inspector-widgets/shared';
76
+ import { DisclosureIcon, themeVars } from '../design-system';
74
77
  import { useEditorStore } from '../EditorContext';
75
78
  import {
76
79
  type InspectorSectionProps,
77
80
  registerInspectorSections,
78
81
  } from '../inspector-section-registry';
79
82
  import { effectiveColorFromChain, getAvailableFonts } from '../ui-source/inspect';
83
+ import type { OidEntry } from '../ui-source/oid-transform';
80
84
  import { CompositeAuthoringAdapter } from './composite-authoring-adapter';
81
85
  import { beginEyedropperSession } from './eyedropper-session';
82
86
  import { ReactDomAuthoringAdapter } from './react-dom-authoring-adapter';
@@ -294,7 +298,7 @@ function Field({
294
298
  data-testid={testId ? `${testId}-row` : undefined}
295
299
  style={{ ...rowStyle, padding: '4px 8px' }}
296
300
  >
297
- <span style={{ width: 76, flexShrink: 0, fontSize: 11, color: THEME.textMuted }}>
301
+ <span style={{ width: LABEL_COL_WIDTH, flexShrink: 0, fontSize: 11, color: THEME.textMuted }}>
298
302
  {label}
299
303
  </span>
300
304
  <div style={{ flex: 1, minWidth: 0 }}>{children}</div>
@@ -374,21 +378,16 @@ function GradientRow({ prop, raw, disabled, testId, commit }: KindRowProps): Rea
374
378
  if (!parsed) {
375
379
  return (
376
380
  <Field label={prop.label} testId={testId}>
377
- <button
381
+ <Button
378
382
  type="button"
383
+ variant="secondary"
384
+ className="vgai-field-trigger"
379
385
  data-testid={testId}
380
386
  disabled={disabled}
381
387
  onClick={() => commit(prop.path, composeGradient(DEFAULT_GRADIENT))}
382
- style={{
383
- ...inputStyle(disabled),
384
- width: '100%',
385
- textAlign: 'left',
386
- cursor: disabled ? 'not-allowed' : 'pointer',
387
- color: THEME.textMuted,
388
- }}
389
388
  >
390
389
  No gradient — click to add
391
- </button>
390
+ </Button>
392
391
  </Field>
393
392
  );
394
393
  }
@@ -538,15 +537,15 @@ function ScrubRow({ prop, raw, disabled, testId, commit }: KindRowProps): React.
538
537
  function SelectRow({ prop, raw, disabled, testId, commit }: KindRowProps): React.ReactElement {
539
538
  return (
540
539
  <Field label={prop.label} testId={testId}>
541
- <select
540
+ <Select
541
+ className="vgai-select"
542
542
  data-testid={testId}
543
543
  value={String(raw ?? '')}
544
544
  disabled={disabled}
545
545
  onChange={(e) => commit(prop.path, e.target.value)}
546
546
  style={{
547
- ...inputStyle(disabled),
548
547
  width: '100%',
549
- cursor: disabled ? 'not-allowed' : 'pointer',
548
+ ...(disabled ? { background: THEME.dynamicBg, color: THEME.dynamic } : null),
550
549
  }}
551
550
  >
552
551
  {(prop.options ?? []).map((o) => (
@@ -554,7 +553,7 @@ function SelectRow({ prop, raw, disabled, testId, commit }: KindRowProps): React
554
553
  {String(o)}
555
554
  </option>
556
555
  ))}
557
- </select>
556
+ </Select>
558
557
  </Field>
559
558
  );
560
559
  }
@@ -564,8 +563,8 @@ function CheckboxRow({ prop, raw, disabled, testId, commit }: KindRowProps): Rea
564
563
  <label
565
564
  style={{ display: 'flex', gap: 6, alignItems: 'center', padding: '6px 8px', fontSize: 12 }}
566
565
  >
567
- <input
568
- type="checkbox"
566
+ <Checkbox
567
+ className="vgai-checkbox"
569
568
  data-testid={testId}
570
569
  checked={raw !== false}
571
570
  disabled={disabled}
@@ -599,7 +598,7 @@ function JsonRow({ prop, raw, disabled, testId, commit }: KindRowProps): React.R
599
598
  function TextRow({ prop, raw, disabled, testId, commit }: KindRowProps): React.ReactElement {
600
599
  return (
601
600
  <Field label={prop.label} testId={testId}>
602
- <input
601
+ <TextInput
603
602
  type="text"
604
603
  data-testid={testId}
605
604
  readOnly={disabled}
@@ -897,6 +896,256 @@ function rowKey(row: PropertyRow): string {
897
896
  return `align:${row.justify.path}`;
898
897
  }
899
898
 
899
+ function compactScrub(
900
+ adapter: AuthoringAdapter,
901
+ id: string,
902
+ prop: PropertyDescriptor | undefined,
903
+ label: string,
904
+ commit: (path: string, value: unknown) => void,
905
+ ): React.ReactElement | null {
906
+ if (!prop) return null;
907
+ const unit = UNITLESS_NUMBER_PROPS.has(propNameFromPath(prop.path)) ? '' : 'px';
908
+ const testId = testIdFor(prop.path);
909
+ return (
910
+ <ScrubbableInput
911
+ key={prop.path}
912
+ testId={testId}
913
+ label={label}
914
+ value={numericValue(adapter.inspector?.get(id, prop.path))}
915
+ unit={unit}
916
+ disabled={prop.readonly}
917
+ onChange={(value) => commit(prop.path, value)}
918
+ onChangeEnd={(value) => commit(prop.path, value)}
919
+ style={{ flex: 1, minWidth: 0 }}
920
+ />
921
+ );
922
+ }
923
+
924
+ function SemanticLayout({
925
+ adapter,
926
+ id,
927
+ props,
928
+ fonts,
929
+ showConstraints,
930
+ onToggleConstraints,
931
+ }: {
932
+ adapter: AuthoringAdapter;
933
+ id: string;
934
+ props: readonly PropertyDescriptor[];
935
+ fonts: readonly string[];
936
+ showConstraints: boolean;
937
+ onToggleConstraints: () => void;
938
+ }): React.ReactElement {
939
+ const store = useEditorStore();
940
+ const byName = new Map(props.map((prop) => [propNameFromPath(prop.path), prop] as const));
941
+ const commit = (path: string, value: unknown): void => {
942
+ adapter.inspector?.set(id, path, value);
943
+ store.notifyIngestEdit();
944
+ };
945
+ const display = String(
946
+ adapter.inspector?.get(id, byName.get('display')?.path ?? 'style.display') ?? '',
947
+ );
948
+ const isFlex = display === 'flex' || display === 'inline-flex';
949
+ const displayProp = byName.get('display');
950
+ const gapProp = byName.get('gap');
951
+ const alignRows = buildPropertyRows(
952
+ props.filter((prop) => ['justifyContent', 'alignItems'].includes(propNameFromPath(prop.path))),
953
+ );
954
+ const ordinary = ['flexDirection', 'flexWrap', 'overflow']
955
+ .map((name) => byName.get(name))
956
+ .filter((prop): prop is PropertyDescriptor => !!prop);
957
+ return (
958
+ <div style={{ padding: '2px 8px 7px' }}>
959
+ <div style={{ display: 'flex', gap: 6 }}>
960
+ {compactScrub(adapter, id, byName.get('width'), 'W', commit)}
961
+ {compactScrub(adapter, id, byName.get('height'), 'H', commit)}
962
+ </div>
963
+ {showConstraints && (
964
+ <div data-testid="react-layout-constraints" style={{ marginTop: 5 }}>
965
+ <div style={{ display: 'flex', gap: 6 }}>
966
+ {compactScrub(adapter, id, byName.get('minWidth'), 'Min W', commit)}
967
+ {compactScrub(adapter, id, byName.get('minHeight'), 'Min H', commit)}
968
+ </div>
969
+ <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
970
+ {compactScrub(adapter, id, byName.get('maxWidth'), 'Max W', commit)}
971
+ {compactScrub(adapter, id, byName.get('maxHeight'), 'Max H', commit)}
972
+ </div>
973
+ </div>
974
+ )}
975
+ <Button
976
+ type="button"
977
+ variant="ghost"
978
+ size="compact"
979
+ data-testid="react-layout-toggle-constraints"
980
+ onClick={onToggleConstraints}
981
+ style={{ display: 'flex', margin: '2px auto 5px' }}
982
+ >
983
+ {showConstraints ? '− constraints' : '··· constraints'}
984
+ </Button>
985
+ <div
986
+ style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.35fr) minmax(0, 1fr)', gap: 6 }}
987
+ >
988
+ {displayProp && (
989
+ <Select
990
+ data-testid={testIdFor(displayProp.path)}
991
+ value={display}
992
+ disabled={displayProp.readonly}
993
+ onChange={(event) => commit(displayProp.path, event.target.value)}
994
+ style={{ ...inputStyle(displayProp.readonly), width: '100%', cursor: 'pointer' }}
995
+ >
996
+ {(displayProp.options ?? []).map((option) => (
997
+ <option key={String(option)} value={String(option)}>
998
+ {String(option)}
999
+ </option>
1000
+ ))}
1001
+ </Select>
1002
+ )}
1003
+ {compactScrub(adapter, id, gapProp, 'Gap', commit)}
1004
+ </div>
1005
+ {isFlex && (
1006
+ <div data-testid="react-layout-flex-controls" style={{ marginTop: 5 }}>
1007
+ {ordinary
1008
+ .filter((prop) => propNameFromPath(prop.path) !== 'overflow')
1009
+ .map((prop) => (
1010
+ <SingleRow key={prop.path} adapter={adapter} id={id} prop={prop} fonts={fonts} />
1011
+ ))}
1012
+ {alignRows.map((row) =>
1013
+ row.kind === 'single' ? (
1014
+ <SingleRow
1015
+ key={rowKey(row)}
1016
+ adapter={adapter}
1017
+ id={id}
1018
+ prop={row.prop}
1019
+ fonts={fonts}
1020
+ />
1021
+ ) : (
1022
+ <ComboRow key={rowKey(row)} adapter={adapter} id={id} row={row} />
1023
+ ),
1024
+ )}
1025
+ </div>
1026
+ )}
1027
+ {ordinary
1028
+ .filter((prop) => propNameFromPath(prop.path) === 'overflow')
1029
+ .map((prop) => (
1030
+ <SingleRow key={prop.path} adapter={adapter} id={id} prop={prop} fonts={fonts} />
1031
+ ))}
1032
+ </div>
1033
+ );
1034
+ }
1035
+
1036
+ function SemanticPosition({
1037
+ adapter,
1038
+ id,
1039
+ props,
1040
+ fonts,
1041
+ }: {
1042
+ adapter: AuthoringAdapter;
1043
+ id: string;
1044
+ props: readonly PropertyDescriptor[];
1045
+ fonts: readonly string[];
1046
+ }): React.ReactElement {
1047
+ const store = useEditorStore();
1048
+ const byName = new Map(props.map((prop) => [propNameFromPath(prop.path), prop] as const));
1049
+ const position = byName.get('position');
1050
+ const positionValue = String(
1051
+ position ? (adapter.inspector?.get(id, position.path) ?? 'static') : 'static',
1052
+ );
1053
+ const commit = (path: string, value: unknown): void => {
1054
+ adapter.inspector?.set(id, path, value);
1055
+ store.notifyIngestEdit();
1056
+ };
1057
+ const offsets = ['top', 'right', 'bottom', 'left'];
1058
+ const itemProps = ['zIndex', 'flexGrow', 'flexShrink', 'flexBasis', 'alignSelf']
1059
+ .map((name) => byName.get(name))
1060
+ .filter((prop): prop is PropertyDescriptor => !!prop);
1061
+ return (
1062
+ <div style={{ padding: '2px 8px 7px' }}>
1063
+ {position && (
1064
+ <div
1065
+ data-testid={testIdFor(position.path)}
1066
+ style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 2 }}
1067
+ >
1068
+ {(position.options ?? []).map((option) => {
1069
+ const value = String(option);
1070
+ const active = value === positionValue;
1071
+ return (
1072
+ <Button
1073
+ key={value}
1074
+ type="button"
1075
+ variant={active ? 'primary' : 'secondary'}
1076
+ size="compact"
1077
+ aria-pressed={active}
1078
+ title={value}
1079
+ disabled={position.readonly}
1080
+ onClick={() => commit(position.path, value)}
1081
+ >
1082
+ {value.slice(0, 3)}
1083
+ </Button>
1084
+ );
1085
+ })}
1086
+ </div>
1087
+ )}
1088
+ {positionValue !== 'static' && (
1089
+ <div
1090
+ data-testid="react-position-offsets"
1091
+ style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, marginTop: 6 }}
1092
+ >
1093
+ {offsets.map((name) =>
1094
+ compactScrub(adapter, id, byName.get(name), name[0]!.toUpperCase(), commit),
1095
+ )}
1096
+ </div>
1097
+ )}
1098
+ {itemProps.length > 0 && (
1099
+ <div style={{ borderTop: `1px solid ${THEME.border}`, marginTop: 7, paddingTop: 3 }}>
1100
+ <div style={{ color: THEME.textMuted, fontSize: 9, padding: '2px 0 1px' }}>
1101
+ STACKING &amp; FLEX ITEM
1102
+ </div>
1103
+ {itemProps.map((prop) => (
1104
+ <SingleRow key={prop.path} adapter={adapter} id={id} prop={prop} fonts={fonts} />
1105
+ ))}
1106
+ </div>
1107
+ )}
1108
+ </div>
1109
+ );
1110
+ }
1111
+
1112
+ function SemanticSpacing({
1113
+ adapter,
1114
+ id,
1115
+ props,
1116
+ }: {
1117
+ adapter: AuthoringAdapter;
1118
+ id: string;
1119
+ props: readonly PropertyDescriptor[];
1120
+ }): React.ReactElement {
1121
+ const store = useEditorStore();
1122
+ const byName = new Map(props.map((prop) => [propNameFromPath(prop.path), prop] as const));
1123
+ const commit = (path: string, value: unknown): void => {
1124
+ adapter.inspector?.set(id, path, value);
1125
+ store.notifyIngestEdit();
1126
+ };
1127
+ const cells = [
1128
+ ['marginTop', 'M T'],
1129
+ ['marginRight', 'M R'],
1130
+ ['marginBottom', 'M B'],
1131
+ ['marginLeft', 'M L'],
1132
+ ['paddingTop', 'P T'],
1133
+ ['paddingRight', 'P R'],
1134
+ ['paddingBottom', 'P B'],
1135
+ ['paddingLeft', 'P L'],
1136
+ ] as const;
1137
+ return (
1138
+ <div style={{ padding: '2px 8px 8px' }}>
1139
+ <div style={{ color: THEME.textMuted, fontSize: 9, marginBottom: 4 }}>
1140
+ MARGIN · OUTSIDE / PADDING · INSIDE
1141
+ </div>
1142
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }}>
1143
+ {cells.map(([name, label]) => compactScrub(adapter, id, byName.get(name), label, commit))}
1144
+ </div>
1145
+ </div>
1146
+ );
1147
+ }
1148
+
900
1149
  function GroupBlock({
901
1150
  name,
902
1151
  groupId,
@@ -912,31 +1161,142 @@ function GroupBlock({
912
1161
  id: string;
913
1162
  fonts: readonly string[];
914
1163
  }): React.ReactElement {
1164
+ const [collapsed, setCollapsed] = useState(false);
1165
+ const [showConstraints, setShowConstraints] = useState(false);
915
1166
  const rows = buildPropertyRows(props);
916
1167
  return (
917
1168
  <div
918
1169
  data-testid={`react-inspector-group-${groupId}`}
919
1170
  style={{ borderTop: `1px solid ${THEME.border}`, marginTop: 4 }}
920
1171
  >
921
- <div
922
- style={{
923
- fontSize: 11,
924
- fontWeight: 600,
925
- color: THEME.textMuted,
926
- padding: '6px 8px 2px',
927
- textTransform: 'uppercase',
928
- letterSpacing: 0.4,
929
- }}
1172
+ <Button
1173
+ type="button"
1174
+ variant="ghost"
1175
+ className="vgai-inspector-group-trigger"
1176
+ aria-expanded={!collapsed}
1177
+ onClick={() => setCollapsed((value) => !value)}
930
1178
  >
931
- {name}
932
- </div>
933
- {rows.map((row) =>
934
- row.kind === 'single' ? (
935
- <SingleRow key={rowKey(row)} adapter={adapter} id={id} prop={row.prop} fonts={fonts} />
936
- ) : (
937
- <ComboRow key={rowKey(row)} adapter={adapter} id={id} row={row} />
938
- ),
1179
+ <span
1180
+ aria-hidden="true"
1181
+ style={{ display: 'inline-flex', justifyContent: 'center', width: 8 }}
1182
+ >
1183
+ <DisclosureIcon direction={collapsed ? 'right' : 'down'} />
1184
+ </span>
1185
+ <span>{name}</span>
1186
+ </Button>
1187
+ {!collapsed && name === 'Layout' && (
1188
+ <SemanticLayout
1189
+ adapter={adapter}
1190
+ id={id}
1191
+ props={props}
1192
+ fonts={fonts}
1193
+ showConstraints={showConstraints}
1194
+ onToggleConstraints={() => setShowConstraints((value) => !value)}
1195
+ />
1196
+ )}
1197
+ {!collapsed && name === 'Position' && (
1198
+ <SemanticPosition adapter={adapter} id={id} props={props} fonts={fonts} />
1199
+ )}
1200
+ {!collapsed && name === 'Spacing' && (
1201
+ <SemanticSpacing adapter={adapter} id={id} props={props} />
939
1202
  )}
1203
+ {!collapsed &&
1204
+ name !== 'Layout' &&
1205
+ name !== 'Position' &&
1206
+ name !== 'Spacing' &&
1207
+ rows.map((row) =>
1208
+ row.kind === 'single' ? (
1209
+ <SingleRow key={rowKey(row)} adapter={adapter} id={id} prop={row.prop} fonts={fonts} />
1210
+ ) : (
1211
+ <ComboRow key={rowKey(row)} adapter={adapter} id={id} row={row} />
1212
+ ),
1213
+ )}
1214
+ </div>
1215
+ );
1216
+ }
1217
+
1218
+ function BreadcrumbBar({
1219
+ adapter,
1220
+ node,
1221
+ }: {
1222
+ adapter: AuthoringAdapter;
1223
+ node: EditorNode;
1224
+ }): React.ReactElement {
1225
+ const chain: EditorNode[] = [];
1226
+ let current: EditorNode | null = node;
1227
+ let guard = 0;
1228
+ while (current && guard++ < 100) {
1229
+ chain.unshift(current);
1230
+ current = current.parentId ? adapter.hierarchy.node(current.parentId) : null;
1231
+ }
1232
+ return (
1233
+ <nav
1234
+ aria-label="Selection ancestry"
1235
+ data-testid="react-inspector-breadcrumbs"
1236
+ style={{
1237
+ display: 'flex',
1238
+ alignItems: 'center',
1239
+ gap: 3,
1240
+ padding: '6px 8px',
1241
+ overflowX: 'auto',
1242
+ borderBottom: `1px solid ${THEME.border}`,
1243
+ whiteSpace: 'nowrap',
1244
+ }}
1245
+ >
1246
+ {chain.map((entry, index) => (
1247
+ <span key={entry.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 3 }}>
1248
+ {index > 0 && <span style={{ color: THEME.textMuted }}>/</span>}
1249
+ <Button
1250
+ type="button"
1251
+ variant="ghost"
1252
+ size="compact"
1253
+ aria-current={entry.id === node.id ? 'page' : undefined}
1254
+ title={entry.secondaryLabel ?? entry.label}
1255
+ onClick={() => adapter.selection?.set([entry.id])}
1256
+ >
1257
+ {entry.label}
1258
+ </Button>
1259
+ </span>
1260
+ ))}
1261
+ </nav>
1262
+ );
1263
+ }
1264
+
1265
+ function StoryActions({
1266
+ owner,
1267
+ node,
1268
+ }: {
1269
+ owner: ReactWorldAuthoringAdapter;
1270
+ node: EditorNode;
1271
+ }): React.ReactElement {
1272
+ const args = owner.portableArgs(node.id);
1273
+ return (
1274
+ <div
1275
+ data-testid="react-story-controls-header"
1276
+ style={{
1277
+ display: 'flex',
1278
+ alignItems: 'center',
1279
+ justifyContent: 'space-between',
1280
+ gap: 8,
1281
+ padding: '7px 8px',
1282
+ borderBottom: `1px solid ${THEME.border}`,
1283
+ }}
1284
+ >
1285
+ <div style={{ minWidth: 0 }}>
1286
+ <div style={{ color: THEME.text, fontSize: 12, fontWeight: 600 }}>{node.label}</div>
1287
+ <div style={{ color: THEME.textMuted, fontSize: 10 }}>
1288
+ {args ? `${Object.keys(args).length} args · session controls` : 'Portable CSF story'}
1289
+ </div>
1290
+ </div>
1291
+ <Button
1292
+ type="button"
1293
+ variant="secondary"
1294
+ size="compact"
1295
+ data-testid="react-story-reset-args"
1296
+ onClick={() => owner.resetPortableArgs(node.id)}
1297
+ >
1298
+ Reset
1299
+ </Button>
940
1300
  </div>
941
1301
  );
942
1302
  }
@@ -949,38 +1309,111 @@ function GroupBlock({
949
1309
  * `data-oid` component-source writes); a `ReactDomAuthoringAdapter`
950
1310
  * (ingested, overlay-only — never touches source) gets its own honest note
951
1311
  * instead, so neither lane's write model is ever misrepresented. */
1312
+ /**
1313
+ * C4 (spec §9): "the editor offers open/copy-path/go-to-line operations" for a DOM
1314
+ * element's mapped source location. Read-only — this ONLY surfaces where the source is
1315
+ * and copies a `file:line` locator; it has no write path (that stays SourceWriteBackend's
1316
+ * bounded style/className/text/struct ops, wired elsewhere). `open` degrades honestly to
1317
+ * copy-path when no editor-launch protocol is configured (documented fallback, not a
1318
+ * silent no-op): most terminals/OS shells don't register a handler for arbitrary custom
1319
+ * URI schemes, so we copy a `vscode://file/<abs>:<line>` link users can paste, rather than
1320
+ * assume a scheme is registered and fail silently if it isn't.
1321
+ */
1322
+ function SourceLocationRow({ entry }: { entry: OidEntry }): React.ReactElement {
1323
+ const [copied, setCopied] = useState<'path' | 'link' | null>(null);
1324
+ const filePath = `${entry.file}:${entry.line}`;
1325
+ const copy = async (text: string, kind: 'path' | 'link'): Promise<void> => {
1326
+ try {
1327
+ await navigator.clipboard.writeText(text);
1328
+ setCopied(kind);
1329
+ setTimeout(() => setCopied(null), 1500);
1330
+ } catch {
1331
+ // Clipboard API unavailable (permissions/non-secure context) — the row still shows
1332
+ // the path as selectable text, so the user can copy it manually.
1333
+ }
1334
+ };
1335
+ return (
1336
+ <div
1337
+ data-testid="react-source-location"
1338
+ style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 4, flexWrap: 'wrap' }}
1339
+ >
1340
+ <code style={{ fontFamily: FONT_MONO, color: THEME.textMuted }}>{filePath}</code>
1341
+ <Button
1342
+ type="button"
1343
+ data-testid="source-copy-path"
1344
+ onClick={() => void copy(filePath, 'path')}
1345
+ style={{ width: 'auto' }}
1346
+ >
1347
+ {copied === 'path' ? 'Copied' : 'Copy path'}
1348
+ </Button>
1349
+ <Button
1350
+ type="button"
1351
+ data-testid="source-open"
1352
+ title="Copies a vscode://file link (go-to-line) — no in-editor open protocol is wired yet"
1353
+ onClick={() => void copy(`vscode://file/${entry.file}:${entry.line}`, 'link')}
1354
+ style={{ width: 'auto' }}
1355
+ >
1356
+ {copied === 'link' ? 'Copied' : 'Open (copy link)'}
1357
+ </Button>
1358
+ </div>
1359
+ );
1360
+ }
1361
+
952
1362
  function LaneDisclosure({
953
1363
  owner,
954
1364
  node,
1365
+ nodeId,
955
1366
  }: {
956
1367
  owner: ReactWorldAuthoringAdapter | ReactDomAuthoringAdapter;
957
1368
  node: EditorNode | null;
1369
+ nodeId: string;
958
1370
  }): React.ReactElement {
959
1371
  const isLaneB = owner instanceof ReactWorldAuthoringAdapter;
1372
+ // C4: only Lane B (the OID/JSX source-writer path) has a source-location mapping —
1373
+ // Lane A (`ReactDomAuthoringAdapter`, an ingested/overlay session) never stamps
1374
+ // `data-oid` or has an OID index (see its file doc comment), so it has nothing to
1375
+ // resolve here; that's the intended DOM-only degradation, not a bug.
1376
+ const sourceEntry = isLaneB ? owner.sourceLocation(nodeId) : undefined;
1377
+ const semanticCopy =
1378
+ node?.role === 'story' ? (
1379
+ <>
1380
+ <strong style={{ color: THEME.accent }}>Story preview.</strong>{' '}
1381
+ <code style={{ fontFamily: FONT_MONO }}>{node.label}</code> is the active portable CSF
1382
+ design state.
1383
+ </>
1384
+ ) : node?.role === 'document' ? (
1385
+ <>
1386
+ <strong style={{ color: THEME.accent }}>CSF document.</strong>{' '}
1387
+ <code style={{ fontFamily: FONT_MONO }}>{node.secondaryLabel ?? node.label}</code> owns this
1388
+ root's design states.
1389
+ </>
1390
+ ) : null;
960
1391
  return (
961
1392
  <div
962
1393
  data-testid={isLaneB ? 'react-lane-b-disclosure' : 'react-overlay-disclosure'}
963
1394
  style={{
964
1395
  padding: 8,
965
1396
  borderBottom: `1px solid ${THEME.border}`,
966
- background: isLaneB ? 'rgba(59,130,246,0.08)' : 'rgba(255,255,255,0.04)',
1397
+ background: isLaneB ? themeVars.accent.muted : THEME.neutralHover,
967
1398
  color: THEME.text,
968
1399
  fontSize: 11,
969
1400
  lineHeight: 1.5,
970
1401
  }}
971
1402
  >
972
- {isLaneB ? (
973
- <>
974
- <strong style={{ color: THEME.accent }}>Component source.</strong> Edits change{' '}
975
- <code style={{ fontFamily: FONT_MONO }}>{node?.label ?? 'this component'}</code>'s source
976
- — every rendered instance updates.
977
- </>
978
- ) : (
979
- <>
980
- <strong style={{ color: THEME.textMuted }}>Session overlay (ingested game).</strong> Edits
981
- are NOT written to source — they persist as a per-project overlay only.
982
- </>
983
- )}
1403
+ {semanticCopy ??
1404
+ (isLaneB ? (
1405
+ <>
1406
+ <strong style={{ color: THEME.accent }}>Component source.</strong> Edits change{' '}
1407
+ <code style={{ fontFamily: FONT_MONO }}>{node?.label ?? 'this component'}</code>'s
1408
+ source — every rendered instance updates.
1409
+ </>
1410
+ ) : (
1411
+ <>
1412
+ <strong style={{ color: THEME.textMuted }}>Session overlay (ingested game).</strong>{' '}
1413
+ Edits are NOT written to source — they persist as a per-project overlay only.
1414
+ </>
1415
+ ))}
1416
+ {sourceEntry && <SourceLocationRow entry={sourceEntry} />}
984
1417
  </div>
985
1418
  );
986
1419
  }
@@ -1008,7 +1441,11 @@ function ReactPropertiesSection({
1008
1441
 
1009
1442
  return (
1010
1443
  <div data-testid="react-inspector-section">
1011
- <LaneDisclosure owner={owner} node={node} />
1444
+ <BreadcrumbBar adapter={adapter} node={node!} />
1445
+ {node?.role === 'story' && owner instanceof ReactWorldAuthoringAdapter && (
1446
+ <StoryActions owner={owner} node={node} />
1447
+ )}
1448
+ <LaneDisclosure owner={owner} node={node} nodeId={nodeId} />
1012
1449
  {ungrouped.length > 0 && (
1013
1450
  <GroupBlock
1014
1451
  name="Style"
@@ -1037,5 +1474,6 @@ function ReactPropertiesSection({
1037
1474
  registerInspectorSections({
1038
1475
  match: isReactDomSelection,
1039
1476
  Section: ReactPropertiesSection,
1040
- suppresses: ['properties'],
1477
+ panelName: 'React Inspector',
1478
+ suppresses: ['header', 'transform', 'properties', 'components'],
1041
1479
  });