@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,301 @@
1
+ /**
2
+ * CSV round-trip for TABLE data assets (W6c, docs/DATA-TOOLS-DESIGN.md §7).
3
+ *
4
+ * React-free, sibling discipline to `inspectors/json-schema-fields.ts` — pure
5
+ * functions, unit-tested in Node, no DOM.
6
+ *
7
+ * ARCHITECTURE RULE: the editor never executes the project's Zod schema (the
8
+ * W2 pin — see `json-schema-fields.ts`'s header). Cell coercion here is by
9
+ * the EMITTED schema's `DataFieldDescriptor` KIND only (number/boolean/enum/
10
+ * string/color/group/json) — never a real `.parse()`. Real validation stays
11
+ * at the runtime parse-on-load (`defineData`) and the W5 build
12
+ * plugin/`vgai doctor` gate; this module's job is just "don't corrupt the
13
+ * file", not "be a validator".
14
+ *
15
+ * Import is WHOLE-TABLE REPLACE, deliberately: a CSV file IS the table (every
16
+ * row this import doesn't mention is gone). And it's ATOMIC — any coercion
17
+ * error anywhere in the sheet means NOTHING is written; the caller must not
18
+ * call `writeFile` when `errors.length > 0`.
19
+ */
20
+
21
+ import type { DataFieldDescriptor } from './inspectors/json-schema-fields';
22
+ import { defaultValueForDescriptor } from './inspectors/json-schema-fields';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Export: table -> CSV text
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /** RFC-4180 field escaping: quote when the cell contains a comma, quote, or
29
+ * newline; double any embedded quote. */
30
+ function escapeCsvField(value: string): string {
31
+ if (/[",\r\n]/.test(value)) {
32
+ return `"${value.replace(/"/g, '""')}"`;
33
+ }
34
+ return value;
35
+ }
36
+
37
+ /** One CSV cell for row-schema field `f`, from row value `v` (the row's
38
+ * current value under key `f.name`, or `undefined` if absent). */
39
+ function cellToCsv(f: DataFieldDescriptor, v: unknown): string {
40
+ if (v === undefined) return '';
41
+ // Nested/unrenderable kinds — or any non-primitive runtime value, in case
42
+ // data and schema disagree — round-trip as a JSON-encoded cell.
43
+ if (f.kind === 'group' || f.kind === 'json' || (v !== null && typeof v === 'object')) {
44
+ return escapeCsvField(JSON.stringify(v));
45
+ }
46
+ return escapeCsvField(String(v));
47
+ }
48
+
49
+ /**
50
+ * Serialize a table asset's data (minus `"$schema"`) to CSV text: header
51
+ * `key,<rowFields in schema order>`, one line per data key in FILE order,
52
+ * `\r\n` line endings (spreadsheet-friendly), trailing newline.
53
+ */
54
+ export function tableToCsv(
55
+ data: Record<string, unknown>,
56
+ rowFields: DataFieldDescriptor[],
57
+ ): string {
58
+ const header = ['key', ...rowFields.map((f) => f.name)].map(escapeCsvField).join(',');
59
+ const lines = [header];
60
+ for (const [key, row] of Object.entries(data)) {
61
+ if (key === '$schema') continue;
62
+ const rowObj = row && typeof row === 'object' ? (row as Record<string, unknown>) : {};
63
+ const cells = [escapeCsvField(key), ...rowFields.map((f) => cellToCsv(f, rowObj[f.name]))];
64
+ lines.push(cells.join(','));
65
+ }
66
+ return `${lines.join('\r\n')}\r\n`;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Import: CSV text -> string[][], then -> table
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Advance one step while scanning INSIDE a quoted field. Returns the next
75
+ * scan position, the character (if any) to append to the current field, and
76
+ * whether this step closed the quote — pulled out of `parseCsv`'s main loop
77
+ * purely to keep that loop's branching flat (this is the one place with
78
+ * nested conditionals: a `""` escape vs. a closing `"`).
79
+ */
80
+ function stepQuoted(
81
+ text: string,
82
+ i: number,
83
+ ): { next: number; char: string | null; closed: boolean } {
84
+ const ch = text[i]!;
85
+ if (ch !== '"') return { next: i + 1, char: ch, closed: false };
86
+ if (text[i + 1] === '"') return { next: i + 2, char: '"', closed: false }; // `""` escape
87
+ return { next: i + 1, char: null, closed: true }; // closing quote
88
+ }
89
+
90
+ /**
91
+ * Minimal RFC-4180 parser: quoted fields, `""` escapes, `\r\n`/`\n` line
92
+ * endings, no external deps. Returns rows of raw string cells (no
93
+ * type coercion — that's `csvToTable`'s job).
94
+ */
95
+ export function parseCsv(text: string): string[][] {
96
+ const rows: string[][] = [];
97
+ let row: string[] = [];
98
+ let field = '';
99
+ let inQuotes = false;
100
+ let i = 0;
101
+ const n = text.length;
102
+
103
+ const endField = () => {
104
+ row.push(field);
105
+ field = '';
106
+ };
107
+ const endRow = () => {
108
+ endField();
109
+ rows.push(row);
110
+ row = [];
111
+ };
112
+
113
+ while (i < n) {
114
+ if (inQuotes) {
115
+ const step = stepQuoted(text, i);
116
+ if (step.char !== null) field += step.char;
117
+ if (step.closed) inQuotes = false;
118
+ i = step.next;
119
+ continue;
120
+ }
121
+ const ch = text[i]!;
122
+ if (ch === '"') {
123
+ inQuotes = true;
124
+ i += 1;
125
+ continue;
126
+ }
127
+ if (ch === ',') {
128
+ endField();
129
+ i += 1;
130
+ continue;
131
+ }
132
+ if (ch === '\r' || ch === '\n') {
133
+ // Treat \r\n and bare \r as one line ending.
134
+ if (ch === '\r' && text[i + 1] === '\n') i += 1;
135
+ endRow();
136
+ i += 1;
137
+ continue;
138
+ }
139
+ field += ch;
140
+ i += 1;
141
+ }
142
+ // Trailing field/row (no terminating newline, or the file has one and this
143
+ // is an empty tail — skip a wholly-empty trailing row from a final \r\n).
144
+ if (field !== '' || row.length > 0) {
145
+ endRow();
146
+ }
147
+ return rows;
148
+ }
149
+
150
+ /** Coerce one raw CSV cell string to field `f`'s runtime value. Returns
151
+ * `{ error }` instead of throwing — the caller aggregates errors across the
152
+ * whole sheet and refuses the import atomically if any exist. */
153
+ function coerceCell(
154
+ raw: string,
155
+ f: DataFieldDescriptor,
156
+ rowNum: number,
157
+ ): { value: unknown } | { error: string } {
158
+ if (raw === '') {
159
+ // This is a PRESENT cell that's blank — `coerceRow` handles an entirely
160
+ // MISSING column itself and never reaches here with one (#5). A
161
+ // `kind: 'string'` field's blank cell IS the value `""`, never "absent",
162
+ // so it must not be swapped for the schema's default: an export→reimport
163
+ // of a legitimately-empty string field has to be a no-op diff, not a
164
+ // mutation. Every other kind has no meaningful "empty" runtime value, so
165
+ // those keep falling back to the field default.
166
+ return { value: f.kind === 'string' ? '' : defaultValueForDescriptor(f) };
167
+ }
168
+ switch (f.kind) {
169
+ case 'number': {
170
+ const n = Number(raw);
171
+ if (!Number.isFinite(n)) {
172
+ return { error: `row ${rowNum}, col ${f.name}: "${raw}" is not a number` };
173
+ }
174
+ return { value: n };
175
+ }
176
+ case 'boolean': {
177
+ const lower = raw.trim().toLowerCase();
178
+ if (lower === 'true' || lower === '1') return { value: true };
179
+ if (lower === 'false' || lower === '0') return { value: false };
180
+ return { error: `row ${rowNum}, col ${f.name}: "${raw}" is not a boolean (true/false/1/0)` };
181
+ }
182
+ case 'enum': {
183
+ const options = f.options ?? [];
184
+ if (!options.includes(raw)) {
185
+ return {
186
+ error: `row ${rowNum}, col ${f.name}: "${raw}" is not one of [${options.join(', ')}]`,
187
+ };
188
+ }
189
+ return { value: raw };
190
+ }
191
+ case 'string':
192
+ case 'color':
193
+ return { value: raw };
194
+ case 'group':
195
+ case 'json':
196
+ try {
197
+ return { value: JSON.parse(raw) };
198
+ } catch {
199
+ return { error: `row ${rowNum}, col ${f.name}: "${raw}" is not valid JSON` };
200
+ }
201
+ default:
202
+ return { value: raw };
203
+ }
204
+ }
205
+
206
+ export interface CsvImportResult {
207
+ /** Present (and non-empty `rows`) only when `errors` is empty. */
208
+ rows: Record<string, Record<string, unknown>>;
209
+ /** Any coercion/structural problem, one entry per issue. Non-empty means
210
+ * the caller must NOT write — the import is atomic, all-or-nothing. */
211
+ errors: string[];
212
+ }
213
+
214
+ /**
215
+ * Coerce ONE data row (`line`, the raw CSV cells including the leading key)
216
+ * into its key + typed row object, pushing any problem onto `errors` (never
217
+ * throwing). Returns `null` when the row has no usable key (empty or the
218
+ * reserved `"$schema"`) — pulled out of `csvToTable`'s main loop so that loop
219
+ * only has to handle the duplicate-key check across rows.
220
+ */
221
+ function coerceRow(
222
+ line: string[],
223
+ rowNum: number,
224
+ rowFields: DataFieldDescriptor[],
225
+ columns: string[],
226
+ errors: string[],
227
+ ): { key: string; value: Record<string, unknown> } | null {
228
+ const key = (line[0] ?? '').trim();
229
+ if (!key) {
230
+ errors.push(`row ${rowNum}: key cannot be empty.`);
231
+ return null;
232
+ }
233
+ if (key === '$schema') {
234
+ errors.push(`row ${rowNum}: "$schema" is reserved and cannot be a row key.`);
235
+ return null;
236
+ }
237
+
238
+ const rowValue: Record<string, unknown> = {};
239
+ for (const f of rowFields) {
240
+ // +1: `columns` excludes the leading "key" column that `line[0]` holds.
241
+ const colIdx = columns.indexOf(f.name);
242
+ if (colIdx < 0) {
243
+ // Column missing from the sheet ENTIRELY (distinct from a present but
244
+ // blank cell, #5) — there is no cell content to preserve, so every
245
+ // kind (string included) falls to the schema default here.
246
+ rowValue[f.name] = defaultValueForDescriptor(f);
247
+ continue;
248
+ }
249
+ const raw = line[colIdx + 1] ?? '';
250
+ const result = coerceCell(raw, f, rowNum);
251
+ if ('error' in result) {
252
+ errors.push(result.error);
253
+ continue;
254
+ }
255
+ rowValue[f.name] = result.value;
256
+ }
257
+ return { key, value: rowValue };
258
+ }
259
+
260
+ /**
261
+ * Parse CSV text into table rows for row-schema `rowFields`. Never throws;
262
+ * collects every problem into `errors` and returns `rows: {}` alongside them
263
+ * (callers must check `errors.length` before using `rows` — a non-empty
264
+ * `errors` means the sheet is not a valid whole-table replacement).
265
+ */
266
+ export function csvToTable(text: string, rowFields: DataFieldDescriptor[]): CsvImportResult {
267
+ const errors: string[] = [];
268
+ const grid = parseCsv(text).filter((r) => !(r.length === 1 && r[0] === ''));
269
+ if (grid.length === 0) {
270
+ return { rows: {}, errors: ['CSV is empty — expected a header row starting with "key".'] };
271
+ }
272
+
273
+ const header = grid[0]!;
274
+ if (header[0] !== 'key') {
275
+ return { rows: {}, errors: [`First column must be "key" (found "${header[0] ?? ''}").`] };
276
+ }
277
+ const columns = header.slice(1);
278
+ const validNames = new Set(rowFields.map((f) => f.name));
279
+ for (const col of columns) {
280
+ if (!validNames.has(col)) {
281
+ errors.push(
282
+ `Unknown column "${col}" — valid columns are [${rowFields.map((f) => f.name).join(', ')}].`,
283
+ );
284
+ }
285
+ }
286
+
287
+ const rows: Record<string, Record<string, unknown>> = {};
288
+ for (let r = 1; r < grid.length; r++) {
289
+ const rowNum = r + 1; // 1-based, header is row 1 (spreadsheet-familiar numbering)
290
+ const coerced = coerceRow(grid[r]!, rowNum, rowFields, columns, errors);
291
+ if (!coerced) continue;
292
+ if (coerced.key in rows) {
293
+ errors.push(`row ${rowNum}: duplicate key "${coerced.key}".`);
294
+ continue;
295
+ }
296
+ rows[coerced.key] = coerced.value;
297
+ }
298
+
299
+ if (errors.length > 0) return { rows: {}, errors };
300
+ return { rows, errors: [] };
301
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Pure decision for the inspector's entity-name field commit (T1.12b): given
3
+ * the in-progress draft and the last-committed name, returns the trimmed name
4
+ * to commit, or `null` if nothing should be committed (empty/whitespace-only
5
+ * draft, or unchanged). Kept import-free (no React/Three/editor-store deps)
6
+ * so it can be unit-tested without pulling in the HeaderSection module graph.
7
+ *
8
+ * Used by `EntityNameField` in
9
+ * `authoring/first-party-inspector/HeaderSection.tsx`, whose `commit()` handler
10
+ * (wired to `onBlur`/Enter only — never `onChange`) calls this before pushing
11
+ * to the store, so the store only sees one call per edit gesture instead of
12
+ * one per keystroke.
13
+ */
14
+ export function resolveNameCommit(draft: string, name: string): string | null {
15
+ const trimmed = draft.trim();
16
+ if (trimmed && trimmed !== name) return trimmed;
17
+ return null;
18
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure grouping logic behind the generic adapter-driven inspector's
3
+ * `PropertyDescriptor.group?` rendering (T3.4 slice 2, docs/ADAPTER-
4
+ * AUTHORING-DESIGN.md §1.E). Extracted out of `Inspector.tsx`'s (formerly
5
+ * `IngestPanels.tsx`'s `GenericInspector`) inspector shell component (a react
6
+ * component with no jsdom/testing-library in this repo — `vitest.config.ts`
7
+ * runs `environment: 'node'`) so
8
+ * this can be unit-tested directly as data-in/data-out, matching the
9
+ * repo's existing "test the pure logic headlessly" convention.
10
+ *
11
+ * Ungrouped fields keep their original relative order (byte-stable with the
12
+ * pre-`group` flat-list rendering); named groups are returned in FIRST-SEEN
13
+ * order, each carrying a slugified, testid-safe `groupId` (lowercase,
14
+ * non-alphanumeric runs collapsed to a single `-`, no leading/trailing `-`).
15
+ */
16
+
17
+ import type { PropertyDescriptor } from '@engine/adapter';
18
+
19
+ export interface PropertyGroup {
20
+ name: string;
21
+ /** Slugified group name — the suffix of the rendered section's
22
+ * `data-testid="ingest-group-<groupId>"`. */
23
+ groupId: string;
24
+ properties: PropertyDescriptor[];
25
+ }
26
+
27
+ export interface GroupedProperties {
28
+ ungrouped: PropertyDescriptor[];
29
+ groups: PropertyGroup[];
30
+ }
31
+
32
+ /** Lowercase, non-alphanumeric runs -> single `-`, no leading/trailing `-`. */
33
+ export function slugifyGroupName(name: string): string {
34
+ return name
35
+ .toLowerCase()
36
+ .replace(/[^a-z0-9]+/g, '-')
37
+ .replace(/^-+|-+$/g, '');
38
+ }
39
+
40
+ export function groupProperties(props: readonly PropertyDescriptor[]): GroupedProperties {
41
+ const ungrouped: PropertyDescriptor[] = [];
42
+ const order: string[] = [];
43
+ const byName = new Map<string, PropertyDescriptor[]>();
44
+
45
+ for (const p of props) {
46
+ if (p.group) {
47
+ if (!byName.has(p.group)) {
48
+ byName.set(p.group, []);
49
+ order.push(p.group);
50
+ }
51
+ byName.get(p.group)!.push(p);
52
+ } else {
53
+ ungrouped.push(p);
54
+ }
55
+ }
56
+
57
+ return {
58
+ ungrouped,
59
+ groups: order.map((name) => ({
60
+ name,
61
+ groupId: slugifyGroupName(name),
62
+ properties: byName.get(name)!,
63
+ })),
64
+ };
65
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * AlignmentGrid — a 3×3 flex `justify-content`×`align-items` picker (spec 27
3
+ * §5 C1). Ported from `visual-edit/inspector.tsx`'s `AlignmentGrid`
4
+ * (:304-351). `AnchorPresets` below REUSES the existing `ui-editor/
5
+ * inspector.tsx` `ANCHOR_PRESETS` table's VALUES (now exported there for
6
+ * this reuse) as a data-only copy — not a live import — because
7
+ * `ui-editor/inspector.tsx` pulls in `UIEditStore`/`react-store`/
8
+ * `node-ops`/the inspector-section-registry (a whole editing-mode runtime
9
+ * graph this pure, adapter-free widget kit must not drag in, Rule zero's
10
+ * "talk ONLY to the AuthoringAdapter contract" spirit extended to "don't
11
+ * import a store-coupled module just for one constant"). Keep this table in
12
+ * sync with `ui-editor/inspector.tsx`'s `ANCHOR_PRESETS` if either changes —
13
+ * C2's wiring step is the natural point to collapse them into one shared,
14
+ * dependency-free `anchor-presets.ts` module both sides import.
15
+ */
16
+
17
+ import { type ChangeHandlers, THEME } from './shared';
18
+
19
+ const ANCHOR_PRESETS: Record<
20
+ string,
21
+ { anchor: AnchorRect['anchor']; offset: AnchorRect['offset'] }
22
+ > = {
23
+ full: {
24
+ anchor: { left: 0, top: 0, right: 1, bottom: 1 },
25
+ offset: { left: 0, top: 0, right: 0, bottom: 0 },
26
+ },
27
+ top: {
28
+ anchor: { left: 0, top: 0, right: 1, bottom: 0 },
29
+ offset: { left: 0, top: 0, right: 0, bottom: 0 },
30
+ },
31
+ bottom: {
32
+ anchor: { left: 0, top: 1, right: 1, bottom: 1 },
33
+ offset: { left: 0, top: 0, right: 0, bottom: 0 },
34
+ },
35
+ left: {
36
+ anchor: { left: 0, top: 0, right: 0, bottom: 1 },
37
+ offset: { left: 0, top: 0, right: 0, bottom: 0 },
38
+ },
39
+ right: {
40
+ anchor: { left: 1, top: 0, right: 1, bottom: 1 },
41
+ offset: { left: 0, top: 0, right: 0, bottom: 0 },
42
+ },
43
+ center: {
44
+ anchor: { left: 0.5, top: 0.5, right: 0.5, bottom: 0.5 },
45
+ offset: { left: -60, top: -20, right: 60, bottom: 20 },
46
+ },
47
+ 'top-left': {
48
+ anchor: { left: 0, top: 0, right: 0, bottom: 0 },
49
+ offset: { left: 0, top: 0, right: 120, bottom: 40 },
50
+ },
51
+ 'top-right': {
52
+ anchor: { left: 1, top: 0, right: 1, bottom: 0 },
53
+ offset: { left: -120, top: 0, right: 0, bottom: 40 },
54
+ },
55
+ };
56
+
57
+ const JUSTIFY = ['flex-start', 'center', 'flex-end'] as const;
58
+ const ALIGN = ['flex-start', 'center', 'flex-end'] as const;
59
+
60
+ export interface AlignmentValue {
61
+ justify: (typeof JUSTIFY)[number];
62
+ align: (typeof ALIGN)[number];
63
+ }
64
+
65
+ /** One 3×3 dot — split out of `AlignmentGrid`'s double `.map` purely to keep
66
+ * each function under biome's cognitive-complexity ceiling; same markup. */
67
+ function AlignmentCell({
68
+ row,
69
+ col,
70
+ justify,
71
+ align,
72
+ isActive,
73
+ disabled,
74
+ onSelect,
75
+ }: {
76
+ row: number;
77
+ col: number;
78
+ justify: AlignmentValue['justify'];
79
+ align: AlignmentValue['align'];
80
+ isActive: boolean;
81
+ disabled?: boolean | undefined;
82
+ onSelect: (justify: AlignmentValue['justify'], align: AlignmentValue['align']) => void;
83
+ }): React.ReactElement {
84
+ return (
85
+ <div
86
+ data-testid={`alignment-grid-${row}-${col}`}
87
+ onClick={() => {
88
+ if (!disabled) onSelect(justify, align);
89
+ }}
90
+ style={{
91
+ width: 16,
92
+ height: 16,
93
+ display: 'flex',
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ cursor: disabled ? 'not-allowed' : 'pointer',
97
+ borderRadius: 2,
98
+ background: isActive ? THEME.accent : 'transparent',
99
+ }}
100
+ >
101
+ <div
102
+ style={{
103
+ width: isActive ? 6 : 4,
104
+ height: isActive ? 6 : 4,
105
+ borderRadius: '50%',
106
+ background: isActive ? '#fff' : THEME.textMuted,
107
+ }}
108
+ />
109
+ </div>
110
+ );
111
+ }
112
+
113
+ export function AlignmentGrid({
114
+ value,
115
+ onChange,
116
+ disabled,
117
+ }: ChangeHandlers<AlignmentValue>): React.ReactElement {
118
+ const jIdx = JUSTIFY.indexOf(value.justify);
119
+ const aIdx = ALIGN.indexOf(value.align);
120
+
121
+ return (
122
+ <div
123
+ data-testid="alignment-grid"
124
+ style={{
125
+ display: 'inline-grid',
126
+ gridTemplateColumns: 'repeat(3, 16px)',
127
+ gridTemplateRows: 'repeat(3, 16px)',
128
+ gap: 1,
129
+ background: THEME.inputBg,
130
+ borderRadius: 6,
131
+ padding: 4,
132
+ opacity: disabled ? 0.35 : 1,
133
+ }}
134
+ >
135
+ {ALIGN.map((a, row) =>
136
+ JUSTIFY.map((j, col) => (
137
+ <AlignmentCell
138
+ key={`${row}-${col}`}
139
+ row={row}
140
+ col={col}
141
+ justify={j}
142
+ align={a}
143
+ isActive={row === aIdx && col === jIdx}
144
+ disabled={disabled}
145
+ onSelect={(justify, align) => onChange({ justify, align })}
146
+ />
147
+ )),
148
+ )}
149
+ </div>
150
+ );
151
+ }
152
+
153
+ export interface AnchorRect {
154
+ anchor: { left: number; top: number; right: number; bottom: number };
155
+ offset: { left: number; top: number; right: number; bottom: number };
156
+ }
157
+
158
+ export interface AnchorPresetsProps {
159
+ onSelect: (name: string, preset: AnchorRect) => void;
160
+ disabled?: boolean | undefined;
161
+ }
162
+
163
+ export function AnchorPresets({ onSelect, disabled }: AnchorPresetsProps): React.ReactElement {
164
+ return (
165
+ <div data-testid="anchor-presets" style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
166
+ {Object.entries(ANCHOR_PRESETS).map(([name, preset]) => (
167
+ <button
168
+ key={name}
169
+ type="button"
170
+ data-testid={`anchor-preset-${name}`}
171
+ disabled={disabled}
172
+ onClick={() => onSelect(name, preset)}
173
+ style={{
174
+ background: 'rgba(255,255,255,0.06)',
175
+ border: `1px solid ${THEME.border}`,
176
+ borderRadius: 3,
177
+ color: THEME.text,
178
+ fontSize: 10,
179
+ padding: '3px 6px',
180
+ cursor: disabled ? 'not-allowed' : 'pointer',
181
+ }}
182
+ >
183
+ {name}
184
+ </button>
185
+ ))}
186
+ </div>
187
+ );
188
+ }