@vgai/editor 0.2.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 (519) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +35 -0
  3. package/dist/a/b/c/test-nested.json +1 -0
  4. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
  5. package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
  6. package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
  7. package/dist/assets/browserAll-CEkWYlFk.js +1 -0
  8. package/dist/assets/collision-world-DniMB0qq.glb +0 -0
  9. package/dist/assets/create-runtime-DYIWr3va.js +1 -0
  10. package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
  11. package/dist/assets/esbuild-BHljloGq.wasm +0 -0
  12. package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
  13. package/dist/assets/game-8SDZPPgH.js +1 -0
  14. package/dist/assets/game-BI111o5f.js +10 -0
  15. package/dist/assets/game-CevorxyV.js +1 -0
  16. package/dist/assets/game-Di1H0gFj.js +1 -0
  17. package/dist/assets/game-Di7XlywD.js +1 -0
  18. package/dist/assets/game-DjnCaw8R.js +1 -0
  19. package/dist/assets/game-Q9A0aXSV.js +1 -0
  20. package/dist/assets/game-pt_1lQLv.js +1 -0
  21. package/dist/assets/game-source-CvidN7AB.js +1 -0
  22. package/dist/assets/game-xsOho_wg.js +83 -0
  23. package/dist/assets/index-Bp5cecD6.js +12 -0
  24. package/dist/assets/index-CPVV6pjw.js +1 -0
  25. package/dist/assets/index-CpOnYyeD.js +213 -0
  26. package/dist/assets/index-Cs8xwv3e.js +1 -0
  27. package/dist/assets/index-DL7RcQ0n.js +27 -0
  28. package/dist/assets/index-Dr8H_6Rl.js +1 -0
  29. package/dist/assets/index-_bCaipUk.js +13650 -0
  30. package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
  31. package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
  32. package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
  33. package/dist/assets/scene-query-BivAHhJu.js +1 -0
  34. package/dist/assets/stats.module--VATS4Kh.js +1 -0
  35. package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
  36. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
  37. package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
  38. package/dist/index.html +37 -0
  39. package/dist/ingest/racing-game/UPSTREAM.md +68 -0
  40. package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
  41. package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
  42. package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
  43. package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
  44. package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
  45. package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
  46. package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
  47. package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
  48. package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
  49. package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
  50. package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
  51. package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
  52. package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
  53. package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
  54. package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
  55. package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
  56. package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
  57. package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
  58. package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
  59. package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
  60. package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
  61. package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
  62. package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
  63. package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
  64. package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
  65. package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
  66. package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
  67. package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
  68. package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
  69. package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
  70. package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
  71. package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
  72. package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
  73. package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
  74. package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
  75. package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
  76. package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
  77. package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
  78. package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
  79. package/dist/ingest/racing-game/a/index.css +1 -0
  80. package/dist/ingest/racing-game/images/bronze.png +0 -0
  81. package/dist/ingest/racing-game/images/gold.png +0 -0
  82. package/dist/ingest/racing-game/images/silver.png +0 -0
  83. package/dist/ingest/racing-game/index.html +15 -0
  84. package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
  85. package/dist/ingest/racing-game/models/track-draco.glb +0 -0
  86. package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
  87. package/dist/ingest/racing-game/rg-entry.js +1 -0
  88. package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
  89. package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
  90. package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
  91. package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
  92. package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
  93. package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
  94. package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
  95. package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
  96. package/dist/ingest/racing-game/textures/cursor.svg +3 -0
  97. package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
  98. package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
  99. package/dist/ingest/racing-game/textures/mask.svg +11 -0
  100. package/dist/ingest/racing-game/textures/no_sound.png +0 -0
  101. package/dist/ingest/racing-game/three-r139.module.js +50617 -0
  102. package/dist/ingest/racing-game/vendor.js +179 -0
  103. package/dist/ingest/racing-game/vgai.game.json +20 -0
  104. package/dist/ingest/simcity/UPSTREAM.md +65 -0
  105. package/dist/ingest/simcity/favicon.ico +0 -0
  106. package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
  107. package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
  108. package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
  109. package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
  110. package/dist/ingest/simcity/icons/bulldozer.png +0 -0
  111. package/dist/ingest/simcity/icons/calendar.png +0 -0
  112. package/dist/ingest/simcity/icons/factory-color.png +0 -0
  113. package/dist/ingest/simcity/icons/factory.png +0 -0
  114. package/dist/ingest/simcity/icons/house-color.png +0 -0
  115. package/dist/ingest/simcity/icons/house.png +0 -0
  116. package/dist/ingest/simcity/icons/job.png +0 -0
  117. package/dist/ingest/simcity/icons/office.png +0 -0
  118. package/dist/ingest/simcity/icons/pause-color.png +0 -0
  119. package/dist/ingest/simcity/icons/pause.png +0 -0
  120. package/dist/ingest/simcity/icons/person.png +0 -0
  121. package/dist/ingest/simcity/icons/play-color.png +0 -0
  122. package/dist/ingest/simcity/icons/play.png +0 -0
  123. package/dist/ingest/simcity/icons/power-color.png +0 -0
  124. package/dist/ingest/simcity/icons/power-line-color.png +0 -0
  125. package/dist/ingest/simcity/icons/power.png +0 -0
  126. package/dist/ingest/simcity/icons/road-color.png +0 -0
  127. package/dist/ingest/simcity/icons/road.png +0 -0
  128. package/dist/ingest/simcity/icons/select-color.png +0 -0
  129. package/dist/ingest/simcity/icons/select.png +0 -0
  130. package/dist/ingest/simcity/icons/store-color.png +0 -0
  131. package/dist/ingest/simcity/index.html +94 -0
  132. package/dist/ingest/simcity/index.js +58 -0
  133. package/dist/ingest/simcity/main.css +233 -0
  134. package/dist/ingest/simcity/models/armored-truck.glb +0 -0
  135. package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
  136. package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
  137. package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
  138. package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
  139. package/dist/ingest/simcity/models/building-cafe.glb +0 -0
  140. package/dist/ingest/simcity/models/building-casino.glb +0 -0
  141. package/dist/ingest/simcity/models/building-cinema.glb +0 -0
  142. package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
  143. package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
  144. package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
  145. package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
  146. package/dist/ingest/simcity/models/building-office-big.glb +0 -0
  147. package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
  148. package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
  149. package/dist/ingest/simcity/models/building-office.glb +0 -0
  150. package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
  151. package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
  152. package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
  153. package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
  154. package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
  155. package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
  156. package/dist/ingest/simcity/models/car-passenger.glb +0 -0
  157. package/dist/ingest/simcity/models/car-police.glb +0 -0
  158. package/dist/ingest/simcity/models/car-taxi.glb +0 -0
  159. package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
  160. package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
  161. package/dist/ingest/simcity/models/car-veteran.glb +0 -0
  162. package/dist/ingest/simcity/models/construction-small.glb +0 -0
  163. package/dist/ingest/simcity/models/data-center.glb +0 -0
  164. package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
  165. package/dist/ingest/simcity/models/industry-factory.glb +0 -0
  166. package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
  167. package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
  168. package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
  169. package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
  170. package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
  171. package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
  172. package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
  173. package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
  174. package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
  175. package/dist/ingest/simcity/models/truck.glb +0 -0
  176. package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
  177. package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
  178. package/dist/ingest/simcity/textures/base.png +0 -0
  179. package/dist/ingest/simcity/textures/grid.png +0 -0
  180. package/dist/ingest/simcity/textures/specular.png +0 -0
  181. package/dist/ingest/simcity/three-r155.module.js +51466 -0
  182. package/dist/ingest/simcity/vgai.game.json +20 -0
  183. package/dist/ingest/tanks/UPSTREAM.md +69 -0
  184. package/dist/ingest/tanks/index.html +214 -0
  185. package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
  186. package/dist/ingest/tanks/tanks-entry.js +92 -0
  187. package/dist/ingest/tanks/three-r170.module.js +54571 -0
  188. package/dist/ingest/tanks/vgai.game.json +20 -0
  189. package/dist/inputmaps/default.inputmap.json +41 -0
  190. package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  191. package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  192. package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  193. package/dist/scenes/untitled.vscn.json +29 -0
  194. package/dist/textures/waternormals.jpg +0 -0
  195. package/dist-server/packaged.mjs +18155 -0
  196. package/package.json +36 -0
  197. package/server/asset-library-routes.ts +833 -0
  198. package/server/canonical-path.ts +50 -0
  199. package/server/cloud-asset-catalog.ts +101 -0
  200. package/server/dev.ts +350 -0
  201. package/server/editor-server.ts +1537 -0
  202. package/server/editor-sse.ts +73 -0
  203. package/server/game-globals-shadow.ts +58 -0
  204. package/server/local-asset-catalog.ts +163 -0
  205. package/server/packaged.ts +571 -0
  206. package/server/prod.ts +75 -0
  207. package/server/project-optimize-deps-entries.ts +149 -0
  208. package/server/project-view.ts +65 -0
  209. package/server/server-utils.ts +430 -0
  210. package/server/session-registry.ts +93 -0
  211. package/src/EditorContext.tsx +87 -0
  212. package/src/achieved-tier.ts +126 -0
  213. package/src/action-registry.ts +268 -0
  214. package/src/adapter-resolver.ts +1858 -0
  215. package/src/asset-events.ts +100 -0
  216. package/src/authoring/active-adapter.ts +98 -0
  217. package/src/authoring/active-systems.ts +50 -0
  218. package/src/authoring/boundary-authoring-adapter.ts +178 -0
  219. package/src/authoring/composite-authoring-adapter.ts +891 -0
  220. package/src/authoring/design-time-layers.ts +776 -0
  221. package/src/authoring/edit-mode-authoring.ts +643 -0
  222. package/src/authoring/embed-only-adapter.ts +57 -0
  223. package/src/authoring/ephemeral-persistence.ts +26 -0
  224. package/src/authoring/eyedropper-session.ts +69 -0
  225. package/src/authoring/first-party-hierarchy-menu.ts +74 -0
  226. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
  227. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
  228. package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
  229. package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
  230. package/src/authoring/first-party-inspector/register.tsx +108 -0
  231. package/src/authoring/ingest-authoring-adapter.ts +475 -0
  232. package/src/authoring/ingest-capture.ts +94 -0
  233. package/src/authoring/ingest-game-adapter.ts +291 -0
  234. package/src/authoring/ingest-iframe-adapter.ts +38 -0
  235. package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
  236. package/src/authoring/ingest-overlay.ts +193 -0
  237. package/src/authoring/layered-pick.ts +87 -0
  238. package/src/authoring/mount-failure-report.ts +112 -0
  239. package/src/authoring/overlay-report.ts +52 -0
  240. package/src/authoring/project-scenes.ts +111 -0
  241. package/src/authoring/react-dom-authoring-adapter.ts +795 -0
  242. package/src/authoring/react-inspector-section.tsx +1041 -0
  243. package/src/authoring/react-world-authoring-adapter.ts +2400 -0
  244. package/src/authoring/root-activation.ts +61 -0
  245. package/src/authoring/scene-ui-layer.ts +485 -0
  246. package/src/authoring/story-overlay.ts +48 -0
  247. package/src/authoring/ui-authoring-adapter.ts +325 -0
  248. package/src/authoring/ui-hierarchy-menu.ts +40 -0
  249. package/src/authoring/ui-inspector-sections.tsx +156 -0
  250. package/src/authoring/undo-timeline.ts +145 -0
  251. package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
  252. package/src/authoring/viewport-pick-context.ts +31 -0
  253. package/src/authoring/viewport-raycast.ts +80 -0
  254. package/src/authoring/world-overlay.ts +76 -0
  255. package/src/authoring/world-pan-state.ts +115 -0
  256. package/src/authoring/world-session-state.ts +102 -0
  257. package/src/authoring/world2d-authoring-adapter.ts +313 -0
  258. package/src/browser-external-watch.ts +98 -0
  259. package/src/browser-play-entry.ts +112 -0
  260. package/src/browser-project-scripts.ts +112 -0
  261. package/src/browser-transpile.ts +246 -0
  262. package/src/browser-watch-reload.ts +100 -0
  263. package/src/command-listener.ts +326 -0
  264. package/src/components/AlignToolbar.tsx +189 -0
  265. package/src/components/AppRoot.tsx +73 -0
  266. package/src/components/AssetBrowser.tsx +502 -0
  267. package/src/components/BottomPanel.tsx +326 -0
  268. package/src/components/BuildPanel.tsx +455 -0
  269. package/src/components/CameraInfo.tsx +56 -0
  270. package/src/components/CommandPalette.tsx +265 -0
  271. package/src/components/ConsolePanel.tsx +272 -0
  272. package/src/components/DataPanel.tsx +1268 -0
  273. package/src/components/DefaultEditorLayout.tsx +509 -0
  274. package/src/components/FolderTree.tsx +179 -0
  275. package/src/components/GameHierarchy.tsx +1349 -0
  276. package/src/components/Inspector.tsx +671 -0
  277. package/src/components/InspectorToolSection.tsx +74 -0
  278. package/src/components/MountFailureBanner.tsx +110 -0
  279. package/src/components/NetworkSection.tsx +109 -0
  280. package/src/components/OnlineAssetBrowser.tsx +448 -0
  281. package/src/components/OverlayReportBanner.tsx +122 -0
  282. package/src/components/PlayBar.tsx +356 -0
  283. package/src/components/PrefabSaveDialog.tsx +201 -0
  284. package/src/components/ProjectHeader.tsx +174 -0
  285. package/src/components/ProjectScreen.tsx +1004 -0
  286. package/src/components/RightPanel.tsx +166 -0
  287. package/src/components/RootActivateDialog.tsx +105 -0
  288. package/src/components/SaveDialog.tsx +197 -0
  289. package/src/components/SaveStatus.tsx +69 -0
  290. package/src/components/StatsOverlay.tsx +35 -0
  291. package/src/components/ToolHost.tsx +80 -0
  292. package/src/components/Toolbar.tsx +307 -0
  293. package/src/components/ViewportOverlay.tsx +234 -0
  294. package/src/components/ViewportPanel.tsx +369 -0
  295. package/src/components/WorldContextMenu.tsx +257 -0
  296. package/src/components/WorldSelectionOverlay.tsx +2003 -0
  297. package/src/components/WorldTextEditor.tsx +86 -0
  298. package/src/components/asset-viewers/AssetViewer.tsx +38 -0
  299. package/src/components/asset-viewers/AudioViewer.tsx +149 -0
  300. package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
  301. package/src/components/asset-viewers/ImageViewer.tsx +65 -0
  302. package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
  303. package/src/components/asset-viewers/ModelViewer.tsx +180 -0
  304. package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
  305. package/src/components/data-csv.ts +301 -0
  306. package/src/components/entity-name-commit.ts +18 -0
  307. package/src/components/inspector-property-grouping.ts +65 -0
  308. package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
  309. package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
  310. package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
  311. package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
  312. package/src/components/inspector-widgets/FontPicker.tsx +196 -0
  313. package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
  314. package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
  315. package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
  316. package/src/components/inspector-widgets/color-utils.ts +202 -0
  317. package/src/components/inspector-widgets/gallery-main.tsx +167 -0
  318. package/src/components/inspector-widgets/index.ts +22 -0
  319. package/src/components/inspector-widgets/shared.tsx +135 -0
  320. package/src/components/inspectors/AnimationSection.tsx +143 -0
  321. package/src/components/inspectors/AudioSection.tsx +215 -0
  322. package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
  323. package/src/components/inspectors/CameraSection.tsx +105 -0
  324. package/src/components/inspectors/ComponentsSection.tsx +358 -0
  325. package/src/components/inspectors/JointsSection.tsx +255 -0
  326. package/src/components/inspectors/LightSection.tsx +116 -0
  327. package/src/components/inspectors/MaterialSection.tsx +1189 -0
  328. package/src/components/inspectors/MeshSection.tsx +99 -0
  329. package/src/components/inspectors/NavigationSection.tsx +58 -0
  330. package/src/components/inspectors/ParticleSection.tsx +681 -0
  331. package/src/components/inspectors/PhysicsSection.tsx +326 -0
  332. package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
  333. package/src/components/inspectors/RenderingSection.tsx +109 -0
  334. package/src/components/inspectors/ShadowSection.tsx +84 -0
  335. package/src/components/inspectors/SplineSection.tsx +191 -0
  336. package/src/components/inspectors/TransformSection.tsx +191 -0
  337. package/src/components/inspectors/json-schema-fields.ts +281 -0
  338. package/src/components/inspectors/multi-edit-utils.ts +49 -0
  339. package/src/components/inspectors/zod-fields.ts +184 -0
  340. package/src/components/primitives/ColorInput.tsx +109 -0
  341. package/src/components/primitives/JsonInput.tsx +98 -0
  342. package/src/components/primitives/NumberInput.tsx +164 -0
  343. package/src/components/primitives/Panel.tsx +68 -0
  344. package/src/components/primitives/SectionHeader.tsx +45 -0
  345. package/src/components/primitives/Tooltip.tsx +82 -0
  346. package/src/components/primitives/Vec3Input.tsx +62 -0
  347. package/src/components/world-overlay-gestures.ts +1438 -0
  348. package/src/doctor/data-checks.ts +285 -0
  349. package/src/doctor/report.ts +386 -0
  350. package/src/editor-api.ts +700 -0
  351. package/src/editor-console.ts +194 -0
  352. package/src/editor-hotkeys.ts +441 -0
  353. package/src/editor-layers.ts +2 -0
  354. package/src/editor-mode.ts +28 -0
  355. package/src/editor-store.ts +2225 -0
  356. package/src/editor-viewport.ts +1924 -0
  357. package/src/entity-factory.ts +221 -0
  358. package/src/game-globals-prelude.ts +27 -0
  359. package/src/gated-globals.ts +131 -0
  360. package/src/generated/setup-by-example.generated.ts +53 -0
  361. package/src/gizmo-registry.ts +608 -0
  362. package/src/handle-system.ts +221 -0
  363. package/src/hierarchy-menu-registry.ts +70 -0
  364. package/src/hierarchy-rows.ts +73 -0
  365. package/src/hosted-example.ts +160 -0
  366. package/src/hotkeys.ts +142 -0
  367. package/src/ingest/discovery-react.ts +132 -0
  368. package/src/ingest/discovery.ts +205 -0
  369. package/src/ingest/discovery2d.ts +204 -0
  370. package/src/ingest/esm-entry-resolution.ts +102 -0
  371. package/src/ingest/games/bloom-composer/game.js +99 -0
  372. package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
  373. package/src/ingest/games/embed-bundle/embed.html +10 -0
  374. package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
  375. package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
  376. package/src/ingest/games/games-fps/game.js +479 -0
  377. package/src/ingest/games/games-fps/vgai.game.json +24 -0
  378. package/src/ingest/games/iframe-reachable/game-source.js +58 -0
  379. package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
  380. package/src/ingest/games/interactive-cubes/game.js +139 -0
  381. package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
  382. package/src/ingest/games/mismatched-three/fake-three.js +38 -0
  383. package/src/ingest/games/mismatched-three/game.js +10 -0
  384. package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
  385. package/src/ingest/games/own-three-bundled/game.js +43 -0
  386. package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
  387. package/src/ingest/games/pointerlock-maze/game.js +288 -0
  388. package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
  389. package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
  390. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
  391. package/src/ingest/games-2d/bunnymark/game.ts +88 -0
  392. package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
  393. package/src/ingest/games-2d/flappy/game.ts +148 -0
  394. package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
  395. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
  396. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
  397. package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
  398. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
  399. package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
  400. package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
  401. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
  402. package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
  403. package/src/ingest/host-namespace-registry.ts +196 -0
  404. package/src/ingest/types.ts +127 -0
  405. package/src/ingest-hierarchy-rows.ts +207 -0
  406. package/src/ingest-mode.ts +2092 -0
  407. package/src/ingest-siblings.ts +466 -0
  408. package/src/initial-scene.ts +194 -0
  409. package/src/inspector-section-registry.ts +117 -0
  410. package/src/interactive-edit-scope.ts +34 -0
  411. package/src/main.tsx +23 -0
  412. package/src/manifest-project.ts +43 -0
  413. package/src/model-thumbnail.ts +156 -0
  414. package/src/module-mode.ts +513 -0
  415. package/src/navmesh-handler.ts +94 -0
  416. package/src/packaged-runtime.ts +49 -0
  417. package/src/play-mode.ts +1148 -0
  418. package/src/playground/main.ts +206 -0
  419. package/src/project-manager.ts +173 -0
  420. package/src/scene-diff.ts +112 -0
  421. package/src/scene-serializer.ts +144 -0
  422. package/src/scene-sync.ts +587 -0
  423. package/src/scene-ui-mode/active-scene-ui.ts +58 -0
  424. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
  425. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
  426. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
  427. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
  428. package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
  429. package/src/script-loader.ts +143 -0
  430. package/src/storage/fsa-storage.ts +55 -0
  431. package/src/storage/handle-storage.ts +150 -0
  432. package/src/storage/handle-store.ts +116 -0
  433. package/src/storage/http-storage.ts +137 -0
  434. package/src/storage/index.ts +147 -0
  435. package/src/storage/mem-storage.ts +135 -0
  436. package/src/storage/opfs-storage.ts +25 -0
  437. package/src/storage/paths.ts +26 -0
  438. package/src/storage/seed.ts +106 -0
  439. package/src/storage/types.ts +110 -0
  440. package/src/tool-loader.ts +291 -0
  441. package/src/ui-editor/inspector.tsx +563 -0
  442. package/src/ui-editor/node-ops.ts +266 -0
  443. package/src/ui-editor/overlay.tsx +380 -0
  444. package/src/ui-editor/palette.tsx +114 -0
  445. package/src/ui-editor/react-store.ts +78 -0
  446. package/src/ui-editor/timeline-panel.tsx +443 -0
  447. package/src/ui-editor/ui-edit-store.ts +552 -0
  448. package/src/ui-editor/ui-prefab.ts +95 -0
  449. package/src/ui-source/inspect.ts +544 -0
  450. package/src/ui-source/oid-transform.ts +133 -0
  451. package/src/ui-source/source-write-backend.ts +203 -0
  452. package/src/ui-source/tailwind-map.ts +174 -0
  453. package/src/ui-source/writer.ts +1175 -0
  454. package/src/widgets/index.ts +73 -0
  455. package/template/.claude/skills/editor/SKILL.md +252 -0
  456. package/template/CLAUDE.md +512 -0
  457. package/template/LICENSE +30 -0
  458. package/template/emit-schemas.ts +42 -0
  459. package/template/index.html +17 -0
  460. package/template/package.json +53 -0
  461. package/template/project.json +9 -0
  462. package/template/public/audio/ambient-arena.wav +0 -0
  463. package/template/public/audio/ambient-music.wav +0 -0
  464. package/template/public/audio/ambient-nature.wav +0 -0
  465. package/template/public/audio/ambient-wind.wav +0 -0
  466. package/template/public/inputmaps/default.inputmap.json +41 -0
  467. package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  468. package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  469. package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
  470. package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  471. package/template/public/scenes/default.vscn.json +43 -0
  472. package/template/public/scenes/untitled.vscn.json +29 -0
  473. package/template/public/textures/waternormals.jpg +0 -0
  474. package/template/server/colyseus-setup.ts +85 -0
  475. package/template/server/main.ts +34 -0
  476. package/template/server/rooms/arena-room.ts +420 -0
  477. package/template/server/rooms/game-room.ts +162 -0
  478. package/template/server/rooms.ts +15 -0
  479. package/template/server/tsconfig.json +19 -0
  480. package/template/src/data/README.md +40 -0
  481. package/template/src/data/assets.ts +31 -0
  482. package/template/src/data/tuning.data.json +5 -0
  483. package/template/src/data/tuning.schema.json +21 -0
  484. package/template/src/data/tuning.schema.ts +34 -0
  485. package/template/src/data/tuning.ts +24 -0
  486. package/template/src/main.ts +157 -0
  487. package/template/src/scripts/components/data-spinner.ts +45 -0
  488. package/template/src/scripts/components/scene-camera.ts +40 -0
  489. package/template/src/scripts/main.ts +94 -0
  490. package/template/src/scripts/registry.ts +46 -0
  491. package/template/src/tools/README.md +14 -0
  492. package/template/src/tools/example.tool.tsx +91 -0
  493. package/template/src/tools/spin.tool.tsx +101 -0
  494. package/template/src/ui/Button.tsx +27 -0
  495. package/template/src/ui/DefaultHud.tsx +40 -0
  496. package/template/src/ui/KeyHint.tsx +25 -0
  497. package/template/src/ui/Panel.tsx +52 -0
  498. package/template/src/ui/RebindPanel.tsx +129 -0
  499. package/template/src/ui/Slider.tsx +28 -0
  500. package/template/src/ui/StatusDot.tsx +22 -0
  501. package/template/src/ui/Toggle.tsx +19 -0
  502. package/template/src/ui/game-state.tsx +37 -0
  503. package/template/src/ui/index.ts +7 -0
  504. package/template/src/ui/mount-default-hud.tsx +19 -0
  505. package/template/src/ui/mount-ui.ts +15 -0
  506. package/template/tsconfig.json +36 -0
  507. package/template/validate-asset-content.ts +78 -0
  508. package/template/validate-manifest.ts +55 -0
  509. package/template/validate-scenes.ts +211 -0
  510. package/template/vgai.game.json +21 -0
  511. package/template/vite.config.ts +128 -0
  512. package/vite-plugin-example-assets.ts +60 -0
  513. package/vite-plugin-game-globals.ts +46 -0
  514. package/vite-plugin-game-static.ts +71 -0
  515. package/vite-plugin-project-game-static.ts +84 -0
  516. package/vite-plugin-react-game-static.ts +98 -0
  517. package/vite-plugin-react-world-provider.ts +81 -0
  518. package/vite-plugin-react-world-runtime.ts +76 -0
  519. package/vite-plugin-ui-oid.ts +561 -0
@@ -0,0 +1,561 @@
1
+ /**
2
+ * Vite plugin: live OID instrumentation + source-write endpoints (C1/C2/C7/C8).
3
+ *
4
+ * The visual-edit "trick" made live for hand-authored React UI components: at dev
5
+ * time it stamps `data-oid` on every JSX element in files matched by `include` (a
6
+ * predicate, so the rest of the editor build is untouched), builds an OID→source
7
+ * index, and serves:
8
+ * GET /__ui-source/index → the whole OID index
9
+ * POST /__ui-source/write {oid,prop,value} → surgical inline-style edit to source
10
+ * POST /__ui-source/struct {oid,op} → structural source edit (delete)
11
+ * Edits run the SAME `writer.ts` used in unit tests, server-side, then Vite HMR
12
+ * reloads. `data-oid` exists only in transform output, never on disk.
13
+ *
14
+ * `include` widening (T6.2 slice 2, `docs/REACT-WORLD-DESIGN.md` §1.D): the original
15
+ * scope was ONLY `ui-editor/editable-components/*.tsx` (the visual-edit fixture dir,
16
+ * for `UIAuthoringAdapter`/`SourceEditPanel`). A react WORLD's entry graph is a real
17
+ * project's `.tsx` source — the mounted template project (`packages/editor/template/
18
+ * src/**`) or an externally-scaffolded project directory reached via the SAME
19
+ * dev-server `fs.allow` pipeline `server/dev.ts` already builds for project scripts
20
+ * (T3.3) — so `ReactWorldAuthoringAdapter` needs those files instrumented too.
21
+ * `defaultProjectScopeInclude` widens to PROJECT scope, not repo-global: any `.tsx`
22
+ * file outside `node_modules`, EXCEPT the vgai tooling/engine source trees this repo
23
+ * itself is built from (`packages/engine/src`, `packages/create-vgai-project`,
24
+ * `packages/vgai-cli`, `packages/editor-sdk`, and `packages/editor/src` generally —
25
+ * carving OUT `ui-editor/editable-components` so the existing UI-edit-mode surface
26
+ * keeps working unchanged). This does NOT depend on which project happens to be
27
+ * open — `fs.allow` already bounds what Vite can even reach, and this predicate
28
+ * additionally keeps the vgai app's OWN react source un-instrumented.
29
+ *
30
+ * Vendored-tree exclusion (Track N, D-N4 item 2 — docs/REACT-INGEST-LANDING-
31
+ * DESIGN.md): any path under a `/vendor/` segment (`vendor/pixi-games/…`,
32
+ * and — once N2 lands — `vendor/react-games/…`) is REJECTED regardless of
33
+ * extension. Vendored trees hold unmodified, foreign game source (Track P's
34
+ * pixi games today; Track N's react games starting N2) — the whole point of
35
+ * vendoring is a byte-for-byte, never-modified tree (`verify-unaltered.mjs`'s
36
+ * zero-diff CI gate). `data-oid` stamping is what makes a `.tsx` file
37
+ * WRITABLE through `/__ui-source/write` (`ReactWorldAuthoringAdapter`'s
38
+ * source-write path, D-N4 item 1's companion) — stamping a vendored file
39
+ * would silently reopen the never-modify-game-source rule for it. Today this
40
+ * is a no-op in practice (the existing `.tsx`-only filter already misses
41
+ * `vendor/pixi-games/`'s `.jsx` sources, and react-rpg — the first vendored
42
+ * react game, D-N5 — is ALSO `.jsx`), but it is closing a real future hole,
43
+ * not changing current behavior: the day a vendored `.tsx` game lands (or an
44
+ * existing one is ever renamed), this is the only thing standing between it
45
+ * and becoming editable.
46
+ *
47
+ * D-Y4 (wave 2, docs/EXTERNAL-REACT-AND-MODULE-DESIGN.md §1/§4 slice S3) —
48
+ * the write-back ban becomes IDENTITY-scoped, not just location-scoped: the
49
+ * `/vendor/` regex above only protects the repo's OWN vendored trees, but
50
+ * wave 2 lets an `ingest-react` world live in ANY external, user-owned
51
+ * folder (`vgai edit <folder>`) — an ingested-as-is foreign game there would
52
+ * otherwise be silently stamped/writable, reopening the never-modify-game-
53
+ * source rule by location rather than by identity. `nearestManifestExcludesIngestReact`
54
+ * walks up from a candidate `.tsx`'s own directory to the NEAREST
55
+ * `vgai.game.json`; if that manifest declares an `{ ingest }` react world,
56
+ * the whole manifest folder's subtree is excluded — exact, not coarse:
57
+ * `ingest-react` is single-world by construction (`adapter-resolver.ts`'s
58
+ * `MULTI_WORLD_ALLOWED_IDENTITIES` exclusion), so "the manifest folder" IS
59
+ * the foreign game's whole tree, and D-N8 ships no authoring for it anyway —
60
+ * excluding it from stamping loses nothing. Cached per directory (hit AND
61
+ * miss, R-Y3) so a project with no manifest anywhere up the tree pays the
62
+ * walk cost once, ever. Malformed JSON at the manifest path is treated as
63
+ * "no exclusion" (never crash the transform hook over a broken file); a
64
+ * genuine fs error OTHER than "not found" (e.g. a permission error) is not
65
+ * swallowed — it propagates, same honesty bar the rest of this file holds.
66
+ */
67
+
68
+ import { createHash } from 'node:crypto';
69
+ import { readFileSync, writeFileSync } from 'node:fs';
70
+ import { dirname, join } from 'node:path';
71
+ import type { Plugin } from 'vite';
72
+ import { lineColToOffset, OidStore, transformSource } from './src/ui-source/oid-transform';
73
+ import {
74
+ deleteElement,
75
+ deleteElements,
76
+ duplicateElement,
77
+ editTextContent,
78
+ getEditableText,
79
+ insertChildElement,
80
+ removeInlineStyle,
81
+ reorderChild,
82
+ reparentElement,
83
+ type StructEditResult,
84
+ type StyleEditResult,
85
+ surgicalCssEdit,
86
+ unwrapElement,
87
+ wrapElement,
88
+ writePropChange,
89
+ writeStyleAuto,
90
+ } from './src/ui-source/writer';
91
+
92
+ const NODE_MODULES_RE = /\/node_modules\//;
93
+ const TOOLING_SRC_RE = /\/packages\/(engine\/src|create-vgai-project|vgai-cli|editor-sdk)\//;
94
+ const EDITOR_SRC_RE = /\/packages\/editor\/src\//;
95
+ const EDITABLE_COMPONENTS_RE = /\/ui-editor\/editable-components\/.*\.tsx$/;
96
+ /** Track N, D-N4 item 2 — see the module doc comment's "Vendored-tree exclusion". */
97
+ const VENDOR_RE = /\/vendor\//;
98
+
99
+ /** True if a parsed `vgai.game.json` body declares at least one
100
+ * `kind: 'react'` world with an `{ ingest }` adapter (kind-agnostic on
101
+ * `strategy` — every ingest-react strategy is D-N8 no-authoring today, and
102
+ * a future strategy should not silently reopen the hole). Defensive against
103
+ * any shape (never trusts the manifest is even an object) — this is a
104
+ * best-effort identity probe for a WRITE-BACK BAN, not manifest validation
105
+ * (the real Zod schema/loader is `@engine/manifest/load`, not reachable
106
+ * from this vite-config-time, dependency-light file by design). */
107
+ function manifestDeclaresIngestReactWorld(parsed: unknown): boolean {
108
+ if (!parsed || typeof parsed !== 'object') return false;
109
+ const worlds = (parsed as { worlds?: unknown }).worlds;
110
+ if (!Array.isArray(worlds)) return false;
111
+ return worlds.some((entry) => {
112
+ if (!entry || typeof entry !== 'object') return false;
113
+ const world = entry as { kind?: unknown; adapter?: unknown };
114
+ if (world.kind !== 'react') return false;
115
+ const { adapter } = world;
116
+ return !!adapter && typeof adapter === 'object' && 'ingest' in adapter;
117
+ });
118
+ }
119
+
120
+ /** Cache: absolute directory -> whether ITS OWN ancestor walk (starting AT
121
+ * this directory) hit an ingest-react-declaring manifest. Cached both ways
122
+ * (hit and miss, R-Y3) — see the module doc comment's D-Y4 section. */
123
+ const manifestAncestorExclusionCache = new Map<string, boolean>();
124
+
125
+ function nearestManifestExcludesIngestReact(dir: string): boolean {
126
+ const cached = manifestAncestorExclusionCache.get(dir);
127
+ if (cached !== undefined) return cached;
128
+
129
+ const manifestPath = join(dir, 'vgai.game.json');
130
+ let raw: string | undefined;
131
+ try {
132
+ raw = readFileSync(manifestPath, 'utf8');
133
+ } catch (err) {
134
+ // ENOENT (no manifest at this level) -> keep walking up. Any OTHER fs
135
+ // error (e.g. EACCES) is a real environment problem, not "no manifest
136
+ // here" — never silently swallowed.
137
+ if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;
138
+ }
139
+
140
+ let result: boolean;
141
+ if (raw === undefined) {
142
+ const parent = dirname(dir);
143
+ result = parent === dir ? false : nearestManifestExcludesIngestReact(parent);
144
+ } else {
145
+ try {
146
+ result = manifestDeclaresIngestReactWorld(JSON.parse(raw));
147
+ } catch {
148
+ // Malformed JSON: honest "no exclusion" (stamping stays on) — never
149
+ // crash the transform hook over a broken manifest file.
150
+ result = false;
151
+ }
152
+ }
153
+ manifestAncestorExclusionCache.set(dir, result);
154
+ return result;
155
+ }
156
+
157
+ /**
158
+ * Cap 2 (React visual-edit parity): the scope guard for the `/__ui-source/css` endpoint.
159
+ * A CSS file path (from a stylesheet's `data-vite-dev-id`) is editable iff it is a
160
+ * first-party PROJECT `.css` file — same carve-outs as `defaultProjectScopeInclude`
161
+ * (never node_modules, vendored trees, the vgai tooling/engine source, or the editor's
162
+ * own source). Paths are normalized to forward slashes so a Windows dev-id matches. This
163
+ * is the CSS analogue of the `.tsx` stamping scope: an out-of-scope path must not become
164
+ * writable through a client-supplied `file`.
165
+ */
166
+ export function isEditableCssFile(id: string): boolean {
167
+ const clean = (id.split('?')[0] ?? id).replace(/\\/g, '/');
168
+ if (!clean.endsWith('.css')) return false;
169
+ if (NODE_MODULES_RE.test(clean)) return false;
170
+ if (VENDOR_RE.test(clean)) return false;
171
+ if (TOOLING_SRC_RE.test(clean)) return false;
172
+ if (EDITOR_SRC_RE.test(clean)) return false;
173
+ return true;
174
+ }
175
+
176
+ /** D-1 (Phase D, spec27 §2): sha256 hex digest — the checksum guard the
177
+ * `/__ui-source/restore` endpoint (below) uses to refuse a stale write. Only
178
+ * ever computed SERVER-SIDE: the client (`ReactWorldAuthoringAdapter`) never
179
+ * hashes anything itself, it only echoes back a digest this server already
180
+ * handed it in a prior `writeStruct`/`restoreSource` response. */
181
+ function sha256(text: string): string {
182
+ return createHash('sha256').update(text, 'utf8').digest('hex');
183
+ }
184
+
185
+ /** Default `include` predicate (see the module doc comment above for the reasoning). */
186
+ export function defaultProjectScopeInclude(id: string): boolean {
187
+ if (!id.endsWith('.tsx')) return false;
188
+ if (NODE_MODULES_RE.test(id)) return false;
189
+ if (VENDOR_RE.test(id)) return false;
190
+ if (TOOLING_SRC_RE.test(id)) return false;
191
+ if (EDITOR_SRC_RE.test(id) && !EDITABLE_COMPONENTS_RE.test(id)) return false;
192
+ if (nearestManifestExcludesIngestReact(dirname(id))) return false;
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * D-A3 (wave 13, `docs/WAVE13-JSX-SPREAD-UNDO-DESIGN.md`): route a
198
+ * `/__ui-source/write` body to the write-or-remove writer call — a `value: null`
199
+ * body is the REMOVAL sentinel (paired with `SourceWriteBackend.removeStyle`),
200
+ * routing to `removeInlineStyle`; any other value routes to the normal
201
+ * write-or-append path (`writeStyleAuto`, unchanged). Exported as a pure function
202
+ * so this routing decision is unit-testable without spinning up a dev server —
203
+ * the OID→offset resolution and the actual fs read/write around it stay inline in
204
+ * the middleware below (real I/O, not worth mocking).
205
+ */
206
+ export function applyStyleWriteRequest(
207
+ code: string,
208
+ elementStart: number,
209
+ prop: string,
210
+ value: string | number | null,
211
+ ): StyleEditResult & { route?: 'class' | 'inline' } {
212
+ if (value === null) return removeInlineStyle(code, elementStart, prop);
213
+ return writeStyleAuto(code, elementStart, prop, value);
214
+ }
215
+
216
+ /** The JSON response a source-endpoint handler produces (status defaults to 200). */
217
+ export interface HandlerResult {
218
+ status?: number;
219
+ body: unknown;
220
+ }
221
+
222
+ /** POST `/__ui-source/write` — surgical inline-style/className edit (F5 routing).
223
+ * D-A3 (wave 13): a `value: null` body is the REMOVAL sentinel — routed via
224
+ * `applyStyleWriteRequest` to `removeInlineStyle` (append-aware undo); `appended`
225
+ * is forwarded so the client's undo can remove vs. restore. */
226
+ function handleWrite(store: OidStore, body: Record<string, unknown>): HandlerResult {
227
+ const { oid, prop, value } = body as { oid: string; prop: string; value: string | null };
228
+ const entry = store.index.get(oid);
229
+ if (!entry) return { body: { changed: false, error: 'unknown oid' } };
230
+ const src = readFileSync(entry.file, 'utf8');
231
+ const off = lineColToOffset(src, entry.line, entry.col);
232
+ const result = applyStyleWriteRequest(src, off, prop, value);
233
+ if (result.changed) writeFileSync(entry.file, result.code);
234
+ return {
235
+ body: {
236
+ changed: result.changed,
237
+ dynamic: result.dynamic,
238
+ route: result.route,
239
+ appended: result.appended,
240
+ file: entry.file,
241
+ },
242
+ };
243
+ }
244
+
245
+ /**
246
+ * POST `/__ui-source/css` — Cap 2 surgical CSS-FILE edit. The client (adapter) resolves
247
+ * the target rule from live matched-rule data (`inspect.ts` getMatchedCssRules) and sends
248
+ * the rule's source file + selector; this edits that file in place. The file path is
249
+ * client-supplied, so it MUST pass the first-party CSS scope guard before any read/write
250
+ * — an out-of-scope path is rejected, not touched. A selector not present in source is
251
+ * generated CSS (`generated: true`); the client re-routes to class/inline.
252
+ *
253
+ * Exported (unlike its sibling handlers above, which close over the live
254
+ * `OidStore`) so it's directly unit-testable without a dev server — it's pure
255
+ * body-in/HandlerResult-out plus real fs I/O on a client-supplied, scope-
256
+ * guarded path, no store/session state involved.
257
+ */
258
+ export function handleCss(body: Record<string, unknown>): HandlerResult {
259
+ const { file, selector, prop, value } = body as {
260
+ file: string;
261
+ selector: string;
262
+ prop: string;
263
+ value: string;
264
+ };
265
+ if (!file || !isEditableCssFile(file)) {
266
+ return { body: { changed: false, error: 'css file out of editable scope' } };
267
+ }
268
+ const cleanFile = file.split('?')[0] ?? file;
269
+ const src = readFileSync(cleanFile, 'utf8');
270
+ const edited = surgicalCssEdit(src, selector, prop, value);
271
+ if (edited === null) return { body: { changed: false, generated: true, file: cleanFile } };
272
+ if (edited !== src) writeFileSync(cleanFile, edited);
273
+ return { body: { changed: edited !== src, file: cleanFile } };
274
+ }
275
+
276
+ /**
277
+ * POST `/__ui-source/text` — Cap 3 text-content edit. Replaces a leaf element's pure-text
278
+ * body in place (guarded: refuses a body with `{expression}` or child elements). Returns
279
+ * `prevText` (the editable text before the edit) for the client's undo inverse.
280
+ */
281
+ function handleText(store: OidStore, body: Record<string, unknown>): HandlerResult {
282
+ const { oid, text } = body as { oid: string; text: string };
283
+ const entry = store.index.get(oid);
284
+ if (!entry) return { body: { changed: false, error: 'unknown oid' } };
285
+ const src = readFileSync(entry.file, 'utf8');
286
+ const off = lineColToOffset(src, entry.line, entry.col);
287
+ const prevText = getEditableText(src, off);
288
+ const result = editTextContent(src, off, text);
289
+ if (result.changed) writeFileSync(entry.file, result.code);
290
+ return {
291
+ body: { changed: result.changed, dynamic: result.dynamic, prevText, file: entry.file },
292
+ };
293
+ }
294
+
295
+ /**
296
+ * POST `/__ui-source/prop` — Cap 4 component prop / attribute edit at the call site. The
297
+ * client sends the CALL-SITE oid (resolved from the fiber, `inspect.ts` getCallSiteOid)
298
+ * whose index entry points at the `<Component …>` tag. Guards a dynamic (non-literal) prop
299
+ * and the event-handler / structural props.
300
+ */
301
+ function handleProp(store: OidStore, body: Record<string, unknown>): HandlerResult {
302
+ const { oid, prop, value } = body as { oid: string; prop: string; value: string };
303
+ const entry = store.index.get(oid);
304
+ if (!entry) return { body: { changed: false, error: 'unknown oid' } };
305
+ const src = readFileSync(entry.file, 'utf8');
306
+ const off = lineColToOffset(src, entry.line, entry.col);
307
+ const result = writePropChange(src, off, prop, value);
308
+ if (result.changed) writeFileSync(entry.file, result.code);
309
+ return { body: { changed: result.changed, dynamic: result.dynamic, file: entry.file } };
310
+ }
311
+
312
+ /**
313
+ * POST `/__ui-source/struct` — Cap 5 structural source edits. Ops: `delete`, `duplicate`,
314
+ * `wrap` (+wrapperTag), `unwrap`, `create` (insert a `<tag/>` child of `oid`), `reorder`
315
+ * (move `oid` before `targetOid`, or to the end of `parentOid`), and `reparent` (move `oid`
316
+ * into `parentOid`). Multi-oid ops resolve every oid to an offset in the SAME file (a
317
+ * cross-file move is refused) — OID-resolved, immune to index races.
318
+ *
319
+ * D-1 (Phase D, spec27 §2 "known limitation to schedule, not hide"): when the op actually
320
+ * changes the file, the response also carries the WHOLE file's `prevSource`/`newSource`
321
+ * (before/after this write) and their `prevSha`/`newSha` (sha256 digests, computed here —
322
+ * the client never hashes anything itself). `ReactWorldAuthoringAdapter.structOp` uses
323
+ * these to push a checksum-guarded whole-file-snapshot undo/redo entry — the sound inverse
324
+ * for a structural op (a per-OID inverse is unsound: OIDs are content-signature keyed,
325
+ * reorder/delete reassign occurrence indices, and delete has no inverse payload). Exported
326
+ * (like `handleCss`) so it's directly unit-testable without a running dev server.
327
+ */
328
+ export function handleStruct(store: OidStore, body: Record<string, unknown>): HandlerResult {
329
+ const { oid, op, targetOid, parentOid, wrapperTag } = body as {
330
+ oid: string;
331
+ op: string;
332
+ targetOid?: string;
333
+ parentOid?: string;
334
+ wrapperTag?: string;
335
+ };
336
+ const entry = store.index.get(oid);
337
+ if (!entry) return { body: { changed: false, error: 'unknown oid' } };
338
+ const src = readFileSync(entry.file, 'utf8');
339
+ const off = lineColToOffset(src, entry.line, entry.col);
340
+ // Resolve a companion oid to an offset in the SAME file, or null (cross-file ⇒ error).
341
+ const resolve = (id: string | undefined): number | null | 'cross-file' => {
342
+ if (!id) return null;
343
+ const e = store.index.get(id);
344
+ if (!e) return null;
345
+ if (e.file !== entry.file) return 'cross-file';
346
+ return lineColToOffset(src, e.line, e.col);
347
+ };
348
+ const target = resolve(targetOid);
349
+ const parent = resolve(parentOid);
350
+ if (target === 'cross-file' || parent === 'cross-file') {
351
+ return { body: { changed: false, error: 'cannot move across files' } };
352
+ }
353
+
354
+ let result: StructEditResult;
355
+ switch (op) {
356
+ case 'delete':
357
+ result = deleteElement(src, off);
358
+ break;
359
+ case 'duplicate':
360
+ result = duplicateElement(src, off);
361
+ break;
362
+ case 'wrap':
363
+ result = wrapElement(src, off, wrapperTag ?? 'div');
364
+ break;
365
+ case 'unwrap':
366
+ result = unwrapElement(src, off);
367
+ break;
368
+ case 'create':
369
+ result = insertChildElement(src, off, wrapperTag ?? 'div');
370
+ break;
371
+ case 'reorder':
372
+ result = reorderChild(src, off, target, parent);
373
+ break;
374
+ case 'reparent':
375
+ result = parent == null ? { code: src, changed: false } : reparentElement(src, off, parent);
376
+ break;
377
+ default:
378
+ return { body: { changed: false, error: `unknown struct op: ${op}` } };
379
+ }
380
+ if (!result.changed) return { body: { changed: false, file: entry.file } };
381
+ const prevSource = src;
382
+ const newSource = result.code;
383
+ writeFileSync(entry.file, newSource);
384
+ return {
385
+ body: {
386
+ changed: true,
387
+ file: entry.file,
388
+ prevSource,
389
+ newSource,
390
+ prevSha: sha256(prevSource),
391
+ newSha: sha256(newSource),
392
+ },
393
+ };
394
+ }
395
+
396
+ /**
397
+ * POST `/__ui-source/struct-many` — delete-order-residual fix (bug-panel follow-up
398
+ * to the multi-delete corruption fix, 50f90a6d). Delete-only (today's one caller,
399
+ * `ReactWorldAuthoringAdapter.structure.removeMany`): resolves EVERY `oid` in `oids`
400
+ * to an offset against ONE `readFileSync` snapshot of their (shared) file, then
401
+ * applies all of them via `deleteElements` (`writer.ts`) — highest offset first, so
402
+ * no still-queued target's offset is ever shifted out from under it, REGARDLESS of
403
+ * what order the caller listed `oids` in. This is what makes the batch immune to the
404
+ * delete-order-residual: `deleteSelection` (`editor-hotkeys.ts`)'s per-id fallback
405
+ * loop derives its delete order from the CALLER's own hierarchy walk (DOM order for
406
+ * `ReactWorldAuthoringAdapter` — see `walkOidTree`), which can diverge from true
407
+ * SOURCE order when a component's live DOM child order is a runtime permutation of
408
+ * its JSX (e.g. `{[...els].reverse()}`); this endpoint never consults that walk order
409
+ * at all, only each oid's own registered `{line, col}` and ONE shared file read, so
410
+ * caller order is irrelevant to correctness. Explicitly NOT a re-scan/re-transform of
411
+ * the file per oid (see `deleteElements`'s doc comment for why that would be UNSOUND
412
+ * here — a live re-transform mid-batch reassigns occurrence-index-based oid identity
413
+ * out from under the very oids this batch is still trying to resolve). One write, one
414
+ * `prevSource`/`newSource` pair — one undo entry for the whole batch (a bonus over
415
+ * the N-entry per-id fallback, not the point of this fix, but free from reusing
416
+ * `pushStructUndo`'s existing whole-file-snapshot inverse unchanged).
417
+ */
418
+ export function handleStructMany(store: OidStore, body: Record<string, unknown>): HandlerResult {
419
+ const { oids, op } = body as { oids?: unknown; op?: string };
420
+ if (op !== 'delete') return { body: { changed: false, error: `unsupported batch op: ${op}` } };
421
+ if (!Array.isArray(oids) || oids.length === 0) {
422
+ return { body: { changed: false, error: 'oids must be a non-empty array' } };
423
+ }
424
+ const uniqueOids = [...new Set(oids as string[])];
425
+ const entries = uniqueOids.map((oid) => ({ oid, entry: store.index.get(oid) }));
426
+ const missing = entries.find((e) => !e.entry);
427
+ if (missing) return { body: { changed: false, error: `unknown oid: ${missing.oid}` } };
428
+ const file = entries[0]!.entry!.file;
429
+ const crossFile = entries.find((e) => e.entry!.file !== file);
430
+ if (crossFile) return { body: { changed: false, error: 'cannot batch-delete across files' } };
431
+
432
+ const src = readFileSync(file, 'utf8'); // ONE snapshot every offset below resolves against
433
+ const offsets = entries.map((e) => lineColToOffset(src, e.entry!.line, e.entry!.col));
434
+ const result = deleteElements(src, offsets);
435
+ if (!result.changed) return { body: { changed: false, file } };
436
+ const prevSource = src;
437
+ const newSource = result.code;
438
+ writeFileSync(file, newSource);
439
+ return {
440
+ body: {
441
+ changed: true,
442
+ file,
443
+ prevSource,
444
+ newSource,
445
+ prevSha: sha256(prevSource),
446
+ newSha: sha256(newSource),
447
+ },
448
+ };
449
+ }
450
+
451
+ /**
452
+ * POST `/__ui-source/restore` — D-1 (Phase D, spec27 §2): the checksum-guarded whole-file
453
+ * restore that makes Cap-5 structural edits (delete/duplicate/wrap/unwrap/reorder/reparent/
454
+ * create) undoable. `ReactWorldAuthoringAdapter.structOp` captures `{prevSource, newSource,
455
+ * prevSha, newSha}` from a struct write's response (`handleStruct`, above) and, on undo/
456
+ * redo, posts the INVERSE `{file, source, ifMatchSha}` here: undo sends `source: prevSource,
457
+ * ifMatchSha: newSha` (the hash of what SHOULD be on disk right now); redo sends the mirror
458
+ * image. Refuses — WITHOUT writing — when the file's CURRENT sha256 doesn't match
459
+ * `ifMatchSha` (an out-of-band change since: a hand-edit, a since-diverged reload, …);
460
+ * `restored: false` is the caller's cue to surface a loud refusal instead of silently
461
+ * corrupting the file. Scope-guarded the same way `handleCss`'s client-supplied `file` is
462
+ * (`defaultProjectScopeInclude` — restore targets the SAME `.tsx` project files struct ops
463
+ * do). Exported (like `handleCss`) so it's directly unit-testable without a dev server.
464
+ */
465
+ export function handleRestore(body: Record<string, unknown>): HandlerResult {
466
+ const { file, source, ifMatchSha } = body as {
467
+ file: string;
468
+ source: string;
469
+ ifMatchSha: string;
470
+ };
471
+ if (!file || !defaultProjectScopeInclude(file)) {
472
+ return { status: 403, body: { restored: false, error: 'file out of editable scope' } };
473
+ }
474
+ let current: string;
475
+ try {
476
+ current = readFileSync(file, 'utf8');
477
+ } catch (e) {
478
+ return { status: 404, body: { restored: false, error: String(e) } };
479
+ }
480
+ const currentSha = sha256(current);
481
+ if (currentSha !== ifMatchSha) {
482
+ return {
483
+ status: 409,
484
+ body: {
485
+ restored: false,
486
+ error: 'stale: the file changed since this undo/redo entry was recorded',
487
+ sha: currentSha,
488
+ },
489
+ };
490
+ }
491
+ writeFileSync(file, source);
492
+ return { body: { restored: true, sha: sha256(source) } };
493
+ }
494
+
495
+ export function uiOidPlugin(
496
+ include: RegExp | ((id: string) => boolean) = defaultProjectScopeInclude,
497
+ ): Plugin {
498
+ const test = typeof include === 'function' ? include : (id: string) => include.test(id);
499
+ const store = new OidStore();
500
+ return {
501
+ name: 'vgai-ui-oid',
502
+ enforce: 'pre',
503
+ transform(code, id) {
504
+ const clean = id.split('?')[0] ?? id;
505
+ if (!test(clean)) return null;
506
+ const { code: out } = transformSource(code, clean, store);
507
+ return { code: out, map: null };
508
+ },
509
+ configureServer(server) {
510
+ // The Express host may apply express.json() app-wide, consuming the body and
511
+ // leaving the parsed object on req.body. Prefer that; else read the raw stream.
512
+ const readJson = async (
513
+ req: import('node:http').IncomingMessage,
514
+ ): Promise<Record<string, unknown>> => {
515
+ const pre = (req as unknown as { body?: unknown }).body;
516
+ if (pre && typeof pre === 'object') return pre as Record<string, unknown>;
517
+ const chunks: Buffer[] = [];
518
+ for await (const chunk of req) chunks.push(chunk as Buffer);
519
+ const raw = Buffer.concat(chunks).toString('utf8');
520
+ return raw ? JSON.parse(raw) : {};
521
+ };
522
+ const json = (res: import('node:http').ServerResponse, obj: unknown, code = 200): void => {
523
+ res.statusCode = code;
524
+ res.setHeader('Content-Type', 'application/json');
525
+ res.end(JSON.stringify(obj));
526
+ };
527
+ // Single middleware switching on the URL — avoids connect prefix-matching subtleties.
528
+ // Each POST endpoint delegates to a small module-level handler (below) so this
529
+ // dispatcher stays flat; the try/catch + JSON plumbing lives here once.
530
+ const post = async (
531
+ req: import('node:http').IncomingMessage,
532
+ res: import('node:http').ServerResponse,
533
+ handler: (body: Record<string, unknown>) => HandlerResult,
534
+ ): Promise<void> => {
535
+ try {
536
+ const result = handler(await readJson(req));
537
+ json(res, result.body, result.status ?? 200);
538
+ } catch (e) {
539
+ json(res, { changed: false, error: String(e) }, 400);
540
+ }
541
+ };
542
+ // POST route table — keeps the dispatcher flat (one lookup, no per-endpoint branch).
543
+ const postRoutes: Record<string, (body: Record<string, unknown>) => HandlerResult> = {
544
+ '/__ui-source/write': (b) => handleWrite(store, b),
545
+ '/__ui-source/css': handleCss,
546
+ '/__ui-source/text': (b) => handleText(store, b),
547
+ '/__ui-source/prop': (b) => handleProp(store, b),
548
+ '/__ui-source/struct': (b) => handleStruct(store, b),
549
+ '/__ui-source/struct-many': (b) => handleStructMany(store, b),
550
+ '/__ui-source/restore': handleRestore,
551
+ };
552
+ server.middlewares.use(async (req, res, next) => {
553
+ const url = (req.url ?? '').split('?')[0] ?? '';
554
+ if (url === '/__ui-source/index') return json(res, Object.fromEntries(store.index));
555
+ const handler = req.method === 'POST' ? postRoutes[url] : undefined;
556
+ if (handler) return post(req, res, handler);
557
+ next();
558
+ });
559
+ },
560
+ };
561
+ }