@vgai/editor 0.2.0 → 0.4.0-canary.20260715.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/README.md +24 -0
  2. package/dist/assets/axe-C7YBtOuM.js +30 -0
  3. package/dist/assets/browserAll-DDtGGE48.js +1 -0
  4. package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
  5. package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
  6. package/dist/assets/game-BdTHR2_X.js +1 -0
  7. package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
  8. package/dist/assets/game-CcHfYxjP.js +1 -0
  9. package/dist/assets/game-Cqzj_lpD.js +1 -0
  10. package/dist/assets/game-DSZdeCHu.js +1 -0
  11. package/dist/assets/game-Dch9sAPc.js +1 -0
  12. package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
  13. package/dist/assets/game-loop-DMIgSMV-.js +1 -0
  14. package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
  15. package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
  16. package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
  17. package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
  18. package/dist/assets/index-CQEfi217.js +15412 -0
  19. package/dist/assets/index-Dvi-V0NE.css +1 -0
  20. package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
  21. package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
  22. package/dist/assets/react-18-C9eHn8x1.js +1 -0
  23. package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
  24. package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
  25. package/dist/index.html +15 -13
  26. package/dist/ingest/racing-game/vgai.game.json +5 -4
  27. package/dist/ingest/simcity/vgai.game.json +5 -4
  28. package/dist/ingest/tanks/vgai.game.json +5 -4
  29. package/dist/project-scripts.bundle.js +3917 -0
  30. package/dist/scenes/untitled.vscn.json +2 -0
  31. package/dist-server/packaged.mjs +7697 -3435
  32. package/package.json +31 -4
  33. package/scripts/probe-reporter.ts +349 -0
  34. package/scripts/run-probe.ts +2260 -0
  35. package/server/asset-catalog-audit.ts +268 -0
  36. package/server/asset-catalog-v2.ts +146 -0
  37. package/server/asset-library-routes.ts +276 -58
  38. package/server/canonical-path.ts +1 -1
  39. package/server/dev.ts +148 -44
  40. package/server/editor-server.ts +888 -61
  41. package/server/editor-sse.ts +115 -4
  42. package/server/harness-chat-service.ts +796 -0
  43. package/server/local-asset-catalog.ts +157 -22
  44. package/server/packaged.ts +51 -21
  45. package/server/process-shutdown.ts +136 -0
  46. package/server/prod.ts +9 -6
  47. package/server/project-hmr-files.ts +41 -0
  48. package/server/project-operations.ts +343 -0
  49. package/server/project-optimize-deps-entries.ts +4 -4
  50. package/server/project-output-writer.ts +156 -0
  51. package/server/project-validation.ts +140 -0
  52. package/server/project-view.ts +8 -50
  53. package/server/server-utils.ts +75 -10
  54. package/server/session-registry.ts +12 -0
  55. package/src/EditorContext.tsx +107 -3
  56. package/src/action-registry.ts +115 -17
  57. package/src/adapter-resolver.ts +213 -478
  58. package/src/asset-events.ts +64 -1
  59. package/src/asset-preview.ts +906 -0
  60. package/src/asset-selection.ts +121 -0
  61. package/src/asset-workflow/asset-capabilities.ts +259 -0
  62. package/src/asset-workflow/asset-editor-errors.ts +46 -0
  63. package/src/asset-workflow/asset-import-pipeline.ts +176 -0
  64. package/src/asset-workflow/asset-library-query.ts +107 -0
  65. package/src/asset-workflow/asset-types.ts +192 -0
  66. package/src/asset-workflow/asset-workflow-quality.ts +65 -0
  67. package/src/asset-workflow/import-contract.ts +113 -0
  68. package/src/asset-workflow/model-conversion.ts +126 -0
  69. package/src/asset-workflow/model-inspection.ts +185 -0
  70. package/src/asset-workflow/project-asset-commands.ts +23 -0
  71. package/src/asset-workflow/project-asset-health.ts +153 -0
  72. package/src/asset-workflow/project-asset-operations.ts +119 -0
  73. package/src/asset-workflow/thumbnail-system.ts +172 -0
  74. package/src/authoring/active-systems.ts +15 -0
  75. package/src/authoring/boundary-authoring-adapter.ts +16 -5
  76. package/src/authoring/composite-authoring-adapter.ts +305 -124
  77. package/src/authoring/design-time-layers.ts +300 -117
  78. package/src/authoring/edit-mode-authoring.ts +278 -293
  79. package/src/authoring/embed-only-adapter.ts +4 -2
  80. package/src/authoring/first-party-hierarchy-menu.ts +7 -2
  81. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
  82. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
  83. package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
  84. package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
  85. package/src/authoring/first-party-inspector/register.tsx +13 -2
  86. package/src/authoring/ingest-authoring-adapter.ts +142 -45
  87. package/src/authoring/mount-failure-report.ts +5 -5
  88. package/src/authoring/provenance.ts +41 -0
  89. package/src/authoring/react-dom-authoring-adapter.ts +92 -92
  90. package/src/authoring/react-inspector-section.tsx +489 -51
  91. package/src/authoring/react-world-authoring-adapter.ts +541 -306
  92. package/src/authoring/story-overlay.ts +1 -1
  93. package/src/authoring/story-types.ts +11 -0
  94. package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
  95. package/src/authoring/viewport-tool-context.ts +78 -0
  96. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  97. package/src/authoring/world-hidden-viewport.ts +186 -0
  98. package/src/authoring/world-pan-state.ts +33 -17
  99. package/src/authoring/world-session-state.ts +1 -1
  100. package/src/authoring/world2d-authoring-adapter.ts +109 -52
  101. package/src/availability-tick.ts +56 -0
  102. package/src/browser-play-entry.ts +5 -5
  103. package/src/browser-project-scripts.ts +1 -0
  104. package/src/build-identity.ts +15 -0
  105. package/src/command-listener.ts +893 -19
  106. package/src/components/AlignToolbar.tsx +74 -63
  107. package/src/components/AppRoot.tsx +193 -26
  108. package/src/components/ApplicationMenus.tsx +272 -0
  109. package/src/components/AssetBrowser.tsx +1450 -293
  110. package/src/components/AssetEditorShell.tsx +141 -0
  111. package/src/components/AssetImportDetails.tsx +218 -0
  112. package/src/components/AssetLibraryPanel.tsx +14 -0
  113. package/src/components/CameraInfo.tsx +11 -20
  114. package/src/components/CenterDocuments.tsx +585 -0
  115. package/src/components/CommandPalette.tsx +75 -30
  116. package/src/components/ConsolePanel.css +55 -0
  117. package/src/components/ConsolePanel.tsx +57 -112
  118. package/src/components/DebugConsolePanel.tsx +461 -0
  119. package/src/components/DebuggerPanel.tsx +165 -0
  120. package/src/components/DefaultEditorLayout.tsx +167 -231
  121. package/src/components/GameHierarchy.tsx +873 -290
  122. package/src/components/GamePerformanceOverlay.tsx +46 -0
  123. package/src/components/HarnessChatPanel.tsx +2352 -0
  124. package/src/components/HistoryPanel.tsx +226 -0
  125. package/src/components/Inspector.tsx +153 -99
  126. package/src/components/InspectorToolSection.tsx +6 -4
  127. package/src/components/MountFailureBanner.tsx +31 -48
  128. package/src/components/NetworkSection.tsx +25 -14
  129. package/src/components/OnlineAssetBrowser.tsx +794 -343
  130. package/src/components/OverlayReportBanner.tsx +38 -64
  131. package/src/components/PerformancePanel.tsx +206 -0
  132. package/src/components/PlayBar.tsx +93 -263
  133. package/src/components/PrefabSaveDialog.tsx +55 -127
  134. package/src/components/ProjectHeader.css +60 -0
  135. package/src/components/ProjectHeader.tsx +170 -105
  136. package/src/components/ProjectScreen.tsx +246 -184
  137. package/src/components/ReactCanvasControls.tsx +185 -0
  138. package/src/components/ResolutionPicker.tsx +85 -0
  139. package/src/components/RootActivateDialog.tsx +39 -68
  140. package/src/components/SaveDialog.tsx +55 -127
  141. package/src/components/SaveStatus.tsx +11 -29
  142. package/src/components/StartupLoadingScreen.tsx +96 -0
  143. package/src/components/StateWatchPanel.tsx +302 -0
  144. package/src/components/StatsOverlay.tsx +12 -5
  145. package/src/components/StatusBar.tsx +64 -0
  146. package/src/components/ThemeManagerDialog.css +89 -0
  147. package/src/components/ThemeManagerDialog.tsx +371 -0
  148. package/src/components/ToolHost.tsx +26 -15
  149. package/src/components/Toolbar.tsx +117 -214
  150. package/src/components/VgaiVoxelLogo.tsx +89 -0
  151. package/src/components/ViewportOverlay.tsx +97 -172
  152. package/src/components/ViewportPanel.tsx +130 -31
  153. package/src/components/WorkspaceDock.tsx +891 -0
  154. package/src/components/WorldSelectionOverlay.tsx +51 -63
  155. package/src/components/WorldTextEditor.tsx +9 -5
  156. package/src/components/asset-documents.tsx +390 -0
  157. package/src/components/asset-editor-persistence.ts +44 -0
  158. package/src/components/asset-selection-section.tsx +338 -0
  159. package/src/components/asset-viewers/AudioViewer.tsx +73 -21
  160. package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
  161. package/src/components/asset-viewers/ImageViewer.tsx +43 -11
  162. package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
  163. package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
  164. package/src/components/asset-viewers/ModelViewer.tsx +805 -121
  165. package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
  166. package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
  167. package/src/components/asset-workflow.css +506 -0
  168. package/src/components/build-documents.tsx +696 -0
  169. package/src/components/core-utilities.tsx +75 -0
  170. package/src/components/data-documents.tsx +364 -0
  171. package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
  172. package/src/components/debugger-panel-state.ts +57 -0
  173. package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
  174. package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
  175. package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
  176. package/src/components/inspector-widgets/FontPicker.tsx +13 -21
  177. package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
  178. package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
  179. package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
  180. package/src/components/inspector-widgets/shared.tsx +90 -49
  181. package/src/components/inspectors/AnimationSection.tsx +39 -40
  182. package/src/components/inspectors/AudioSection.tsx +28 -39
  183. package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
  184. package/src/components/inspectors/CameraSection.tsx +14 -23
  185. package/src/components/inspectors/ComponentsSection.tsx +39 -43
  186. package/src/components/inspectors/JointsSection.tsx +34 -43
  187. package/src/components/inspectors/LightSection.tsx +90 -35
  188. package/src/components/inspectors/MaterialSection.tsx +137 -174
  189. package/src/components/inspectors/MeshSection.tsx +9 -17
  190. package/src/components/inspectors/NavigationSection.tsx +8 -15
  191. package/src/components/inspectors/ParticleSection.tsx +91 -76
  192. package/src/components/inspectors/PhysicsSection.tsx +42 -46
  193. package/src/components/inspectors/PostProcessingSection.tsx +28 -36
  194. package/src/components/inspectors/RenderingSection.tsx +12 -20
  195. package/src/components/inspectors/ShadowSection.tsx +7 -8
  196. package/src/components/inspectors/SplineSection.tsx +31 -42
  197. package/src/components/inspectors/TransformSection.tsx +14 -22
  198. package/src/components/patterns/Dialog.tsx +95 -0
  199. package/src/components/patterns/Fields.tsx +44 -0
  200. package/src/components/patterns/List.tsx +25 -0
  201. package/src/components/patterns/StateSurface.tsx +40 -0
  202. package/src/components/patterns/Surfaces.tsx +115 -0
  203. package/src/components/patterns/Tabs.tsx +80 -0
  204. package/src/components/patterns/Toolbar.tsx +51 -0
  205. package/src/components/patterns/Tree.tsx +72 -0
  206. package/src/components/primitives/Button.tsx +46 -0
  207. package/src/components/primitives/ColorInput.tsx +11 -47
  208. package/src/components/primitives/EditorIcon.tsx +99 -0
  209. package/src/components/primitives/FormControls.tsx +71 -0
  210. package/src/components/primitives/JsonInput.tsx +15 -2
  211. package/src/components/primitives/Layout.tsx +100 -0
  212. package/src/components/primitives/Menu.tsx +105 -0
  213. package/src/components/primitives/NumberInput.tsx +8 -35
  214. package/src/components/primitives/Panel.tsx +49 -44
  215. package/src/components/primitives/SectionHeader.tsx +10 -28
  216. package/src/components/primitives/Text.tsx +46 -0
  217. package/src/components/primitives/Tooltip.tsx +20 -12
  218. package/src/components/primitives/Vec3Input.tsx +3 -0
  219. package/src/components/primitives/banner-tones.ts +33 -0
  220. package/src/components/primitives/editor-icons.ts +102 -0
  221. package/src/components/primitives/panel-header-styles.ts +30 -0
  222. package/src/components/project-operation-documents.tsx +226 -0
  223. package/src/components/status-contributions.tsx +175 -0
  224. package/src/components/story-documents-register.ts +15 -0
  225. package/src/components/story-documents.tsx +1185 -0
  226. package/src/components/tool-documents.tsx +110 -0
  227. package/src/components/workspace-dock.css +293 -0
  228. package/src/components/workspace-history.ts +33 -0
  229. package/src/components/world-documents.tsx +145 -0
  230. package/src/components/xstate-documents.tsx +209 -0
  231. package/src/composite-screenshot.ts +248 -0
  232. package/src/design-system/index.ts +70 -0
  233. package/src/design-system-stories/Foundation.stories.tsx +183 -0
  234. package/src/design-system-stories/Patterns.stories.tsx +251 -0
  235. package/src/design-system-stories/Workspace.stories.tsx +538 -0
  236. package/src/doctor/report.ts +57 -13
  237. package/src/editor-api.ts +295 -31
  238. package/src/editor-compatibility.ts +200 -0
  239. package/src/editor-console.ts +35 -17
  240. package/src/editor-hotkeys.ts +85 -33
  241. package/src/editor-store.ts +538 -233
  242. package/src/editor-viewport.ts +160 -34
  243. package/src/entity-factory.ts +14 -3
  244. package/src/gizmo-registry.ts +21 -5
  245. package/src/harness-chat-editor-context.ts +37 -0
  246. package/src/harness-chat-indicator.ts +68 -0
  247. package/src/harness-chat-layout.ts +53 -0
  248. package/src/harness-chat-lifecycle.ts +170 -0
  249. package/src/harness-chat-reconnect.ts +85 -0
  250. package/src/harness-chat-types.ts +409 -0
  251. package/src/hierarchy-drop.ts +50 -0
  252. package/src/hierarchy-expansion-state.ts +100 -0
  253. package/src/hierarchy-menu-registry.ts +1 -1
  254. package/src/hierarchy-projection.ts +76 -0
  255. package/src/history/editor-session.ts +21 -0
  256. package/src/history/history-commands.ts +147 -0
  257. package/src/history/history-service.ts +904 -0
  258. package/src/history/json-history-resource.ts +165 -0
  259. package/src/history/persistence-coordinator.ts +35 -0
  260. package/src/history/project-file-history.ts +266 -0
  261. package/src/history/project-root-history-backends.ts +53 -0
  262. package/src/history/resource-registry.ts +209 -0
  263. package/src/history/snapshot-store.ts +108 -0
  264. package/src/history/source-history-backend.ts +374 -0
  265. package/src/history/types.ts +100 -0
  266. package/src/hmr-registration-group.ts +67 -0
  267. package/src/hmr-stable-react-context.ts +23 -0
  268. package/src/hosted-example.ts +12 -2
  269. package/src/hotkeys.ts +89 -18
  270. package/src/ingest/discovery-react.ts +9 -10
  271. package/src/ingest/discovery.ts +4 -4
  272. package/src/ingest/discovery2d.ts +5 -5
  273. package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
  274. package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
  275. package/src/ingest/games/games-fps/vgai.game.json +3 -3
  276. package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
  277. package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
  278. package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
  279. package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
  280. package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
  281. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
  282. package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
  283. package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
  284. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
  285. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
  286. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
  287. package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
  288. package/src/ingest/types.ts +1 -1
  289. package/src/ingest-mode.ts +47 -52
  290. package/src/ingest-siblings.ts +23 -33
  291. package/src/initial-scene.ts +1 -2
  292. package/src/main.tsx +16 -8
  293. package/src/manifest-project.ts +10 -12
  294. package/src/model-thumbnail.ts +261 -26
  295. package/src/module-mode.ts +44 -50
  296. package/src/play-log-events.ts +26 -0
  297. package/src/play-mode.ts +495 -401
  298. package/src/play-scene-adoption.ts +23 -0
  299. package/src/playground/main.ts +68 -24
  300. package/src/playwright-shim.ts +473 -0
  301. package/src/project-manager.ts +90 -10
  302. package/src/project-operations.ts +112 -0
  303. package/src/project-story-discovery.ts +25 -0
  304. package/src/project-tool-discovery.ts +43 -0
  305. package/src/scene-serializer.ts +0 -6
  306. package/src/scene-sync.ts +7 -2
  307. package/src/script-loader.ts +3 -3
  308. package/src/skeleton-helper.ts +57 -0
  309. package/src/storage/seed.ts +68 -8
  310. package/src/stories/StoryPreviewMount.tsx +265 -0
  311. package/src/stories/action-stories.fixture.tsx +44 -0
  312. package/src/stories/button-stories.fixture.tsx +50 -0
  313. package/src/stories/compose-project-stories.ts +182 -0
  314. package/src/stories/loader-stories.fixture.tsx +53 -0
  315. package/src/stories/story-discovery.ts +130 -0
  316. package/src/stories/story-registry.ts +146 -0
  317. package/src/theme-css.ts +1049 -0
  318. package/src/theme-library.ts +238 -0
  319. package/src/theme-preference.ts +130 -0
  320. package/src/theme.ts +527 -0
  321. package/src/tool-loader.ts +80 -24
  322. package/src/ui-source/inspect.ts +27 -4
  323. package/src/ui-source/source-write-backend.ts +83 -22
  324. package/src/widgets/index.ts +9 -18
  325. package/src/workspace-aux-commands.ts +11 -0
  326. package/src/workspace-dock-controller.ts +64 -0
  327. package/src/workspace-document-ids.ts +5 -0
  328. package/src/workspace-document-registry.ts +367 -0
  329. package/src/workspace-state-persistence.ts +455 -0
  330. package/src/workspace-status-registry.ts +110 -0
  331. package/src/workspace-utility-commands.ts +49 -0
  332. package/src/workspace-utility-registry.ts +161 -0
  333. package/src/world-document-routing.ts +16 -0
  334. package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
  335. package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
  336. package/src/xstate-inspect/use-live-actor-state.ts +39 -0
  337. package/src/xstate-inspect/xstate-graph.ts +265 -0
  338. package/src/xstate-inspect/xstate-layout.ts +76 -0
  339. package/src/z-index.ts +26 -0
  340. package/template/.claude/skills/editor/SKILL.md +129 -90
  341. package/template/AGENTS.md +1110 -0
  342. package/template/CLAUDE.md +1 -512
  343. package/template/IDIOMS.md +50 -0
  344. package/template/check-idioms.ts +706 -0
  345. package/template/manifest-entry-modules-plugin.ts +53 -0
  346. package/template/package.json +13 -4
  347. package/template/public/scenes/default.scn2d.json +6 -0
  348. package/template/public/scenes/default.vscn.json +7 -1
  349. package/template/public/scenes/untitled.vscn.json +10 -0
  350. package/template/scripts/playtest.ts +40 -0
  351. package/template/server/loopback.ts +75 -0
  352. package/template/server/rooms/arena-room.ts +69 -1
  353. package/template/server/rooms/game-room.ts +68 -1
  354. package/template/src/data/README.md +9 -4
  355. package/template/src/data/data-ref.ts +35 -0
  356. package/template/src/data/tuning.schema.ts +1 -1
  357. package/template/src/main.ts +41 -62
  358. package/template/src/manifest-entry-modules.d.ts +3 -0
  359. package/template/src/operations/README.md +88 -0
  360. package/template/src/runtime/pixi-adapter.ts +24 -0
  361. package/template/src/scripts/components/data-spinner.ts +43 -2
  362. package/template/src/scripts/components/spin-lap-math.ts +27 -0
  363. package/template/src/tools/README.md +5 -3
  364. package/template/src/tools/example.tool.tsx +13 -7
  365. package/template/src/ui/Button.stories.tsx +66 -0
  366. package/template/src/ui/RebindPanel.tsx +2 -6
  367. package/template/src/ui/game.tsx +17 -0
  368. package/template/tests/acceptance/example.spec.ts +64 -0
  369. package/template/tests/logic/example.test.ts +31 -0
  370. package/template/tests/logic/room.test.ts +100 -0
  371. package/template/tests/tsconfig.json +30 -0
  372. package/template/validate-manifest.ts +5 -5
  373. package/template/validate-scenes.ts +50 -18
  374. package/template/vgai.game.json +20 -13
  375. package/template/vite.config.ts +77 -16
  376. package/vite-plugin-ui-oid.ts +261 -17
  377. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
  378. package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
  379. package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
  380. package/dist/assets/browserAll-CEkWYlFk.js +0 -1
  381. package/dist/assets/create-runtime-DYIWr3va.js +0 -1
  382. package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
  383. package/dist/assets/game-8SDZPPgH.js +0 -1
  384. package/dist/assets/game-CevorxyV.js +0 -1
  385. package/dist/assets/game-Di7XlywD.js +0 -1
  386. package/dist/assets/game-DjnCaw8R.js +0 -1
  387. package/dist/assets/game-Q9A0aXSV.js +0 -1
  388. package/dist/assets/index-CPVV6pjw.js +0 -1
  389. package/dist/assets/index-CpOnYyeD.js +0 -213
  390. package/dist/assets/index-Dr8H_6Rl.js +0 -1
  391. package/dist/assets/index-_bCaipUk.js +0 -13650
  392. package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
  393. package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
  394. package/dist/assets/scene-query-BivAHhJu.js +0 -1
  395. package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
  396. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
  397. package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
  398. package/src/authoring/scene-ui-layer.ts +0 -485
  399. package/src/authoring/ui-authoring-adapter.ts +0 -325
  400. package/src/authoring/ui-hierarchy-menu.ts +0 -40
  401. package/src/authoring/ui-inspector-sections.tsx +0 -156
  402. package/src/authoring/undo-timeline.ts +0 -145
  403. package/src/components/BottomPanel.tsx +0 -326
  404. package/src/components/BuildPanel.tsx +0 -455
  405. package/src/components/FolderTree.tsx +0 -179
  406. package/src/components/RightPanel.tsx +0 -166
  407. package/src/components/WorldContextMenu.tsx +0 -257
  408. package/src/components/asset-viewers/AssetViewer.tsx +0 -38
  409. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
  410. package/src/scene-ui-mode/active-scene-ui.ts +0 -58
  411. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
  412. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
  413. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
  414. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
  415. package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
  416. package/src/ui-editor/inspector.tsx +0 -563
  417. package/src/ui-editor/node-ops.ts +0 -266
  418. package/src/ui-editor/overlay.tsx +0 -380
  419. package/src/ui-editor/palette.tsx +0 -114
  420. package/src/ui-editor/react-store.ts +0 -78
  421. package/src/ui-editor/timeline-panel.tsx +0 -443
  422. package/src/ui-editor/ui-edit-store.ts +0 -552
  423. package/src/ui-editor/ui-prefab.ts +0 -95
  424. package/template/project.json +0 -9
  425. package/template/src/scripts/main.ts +0 -94
  426. package/template/src/ui/DefaultHud.tsx +0 -40
  427. package/template/src/ui/game-state.tsx +0 -37
  428. package/template/src/ui/mount-default-hud.tsx +0 -19
  429. package/template/src/ui/mount-ui.ts +0 -15
  430. package/vite-plugin-react-world-provider.ts +0 -81
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Asset SELECTION state (W2, docs/EDITOR-WORKSPACE-SHELL-TODO.md §5.1) — the
3
+ * "single click selects" half of the selection-vs-open contract:
4
+ *
5
+ * - single click in the Project/Assets browser SELECTS the asset and shows
6
+ * compact preview/metadata in the Inspector (the registered
7
+ * `components/asset-selection-section.tsx` section reads this store);
8
+ * - double click / Enter OPENS the full viewer as a center document
9
+ * (`components/asset-documents.tsx`);
10
+ * - drag remains a placement gesture and never touches this store.
11
+ *
12
+ * A tiny module-scope store in the house `useSyncExternalStore` shape
13
+ * (mirrors `inspector-section-registry.ts`): version counter + subscribe +
14
+ * `__resetForTest`. It deliberately does NOT touch the EditorStore's entity
15
+ * selection — clicking an asset must never deselect/reselect scene entities
16
+ * (§6.1 "selection never unexpectedly..."); instead the Inspector section
17
+ * simply appears for the selected asset, and the selection is auto-cleared
18
+ * when the ENTITY selection changes (the user moved on — see
19
+ * `installAssetSelectionAutoClear`).
20
+ */
21
+
22
+ import type { AssetCapabilities } from './asset-workflow/asset-capabilities';
23
+ import type { AssetKind } from './editor-store';
24
+
25
+ /** The §5.1 compact-selection payload — enough for the Inspector section to
26
+ * render preview + metadata without re-deriving browser state. */
27
+ export interface SelectedAsset {
28
+ /** Serving path (`/textures/crate.png`) — doubles as the open-document key. */
29
+ readonly path: string;
30
+ /** Source-owned file name as authored (§8 — `crate.png`). */
31
+ readonly name: string;
32
+ readonly kind: AssetKind | 'folder' | 'unknown';
33
+ /** File size in bytes when the browser knows it. */
34
+ readonly sizeBytes?: number;
35
+ readonly capabilities?: AssetCapabilities;
36
+ readonly health?: 'healthy' | 'source-only' | 'unsupported' | 'stale-import';
37
+ readonly sourcePath?: string;
38
+ readonly selectionCount?: number;
39
+ readonly dependencies?: readonly string[];
40
+ readonly references?: readonly { ownerPath: string; jsonPath: string }[];
41
+ readonly healthCodes?: readonly string[];
42
+ }
43
+
44
+ let _selected: SelectedAsset | null = null;
45
+ let _version = 0;
46
+ const _listeners = new Set<() => void>();
47
+
48
+ function notifyChanged(): void {
49
+ _version++;
50
+ for (const fn of _listeners) fn();
51
+ }
52
+
53
+ /** Subscribe to selection changes. Returns an unsubscribe function. */
54
+ export function subscribeAssetSelection(fn: () => void): () => void {
55
+ _listeners.add(fn);
56
+ return () => {
57
+ _listeners.delete(fn);
58
+ };
59
+ }
60
+
61
+ /** Monotonic change counter — the `getSnapshot` for `useSyncExternalStore`. */
62
+ export function assetSelectionVersion(): number {
63
+ return _version;
64
+ }
65
+
66
+ /** The currently selected asset, or `null`. */
67
+ export function getSelectedAsset(): SelectedAsset | null {
68
+ return _selected;
69
+ }
70
+
71
+ /** Select an asset (single-click). Idempotent on the same path. */
72
+ export function setSelectedAsset(asset: SelectedAsset): void {
73
+ if (_selected && JSON.stringify(_selected) === JSON.stringify(asset)) return;
74
+ _selected = asset;
75
+ notifyChanged();
76
+ }
77
+
78
+ /** Clear the asset selection (no-op when already clear). */
79
+ export function clearSelectedAsset(): void {
80
+ if (_selected === null) return;
81
+ _selected = null;
82
+ notifyChanged();
83
+ }
84
+
85
+ /** The narrow store surface the auto-clear needs (testable with a fake). */
86
+ export interface AssetSelectionAutoClearStore {
87
+ subscribe(listener: () => void): () => void;
88
+ readonly selectedEntityIds: ReadonlySet<string>;
89
+ }
90
+
91
+ function entitySelectionKey(store: AssetSelectionAutoClearStore): string {
92
+ return [...store.selectedEntityIds].sort().join('\n');
93
+ }
94
+
95
+ /**
96
+ * Auto-clear wiring (installed once per editor session by the layout): when
97
+ * the ENTITY selection changes while an asset is selected, the user has moved
98
+ * on — drop the asset selection so the Inspector goes back to being purely
99
+ * entity-contextual. The entity-selection snapshot is captured on every asset
100
+ * selection SET (not at install time), so only a change that happens AFTER
101
+ * the asset click clears it.
102
+ */
103
+ export function installAssetSelectionAutoClear(store: AssetSelectionAutoClearStore): () => void {
104
+ let snapshot = entitySelectionKey(store);
105
+ const unsubscribeSelf = subscribeAssetSelection(() => {
106
+ if (_selected) snapshot = entitySelectionKey(store);
107
+ });
108
+ const unsubscribeStore = store.subscribe(() => {
109
+ if (_selected && entitySelectionKey(store) !== snapshot) clearSelectedAsset();
110
+ });
111
+ return () => {
112
+ unsubscribeSelf();
113
+ unsubscribeStore();
114
+ };
115
+ }
116
+
117
+ /** Test-only reset (mirrors `__resetInspectorSectionRegistryForTest`). */
118
+ export function __resetAssetSelectionForTest(): void {
119
+ _selected = null;
120
+ notifyChanged();
121
+ }
@@ -0,0 +1,259 @@
1
+ export type AssetFormat =
2
+ | 'glb'
3
+ | 'gltf'
4
+ | 'fbx'
5
+ | 'obj'
6
+ | 'dae'
7
+ | 'stl'
8
+ | 'ply'
9
+ | '3ds'
10
+ | 'bvh'
11
+ | 'blend'
12
+ | 'usd'
13
+ | 'usda'
14
+ | 'usdc'
15
+ | 'png'
16
+ | 'jpg'
17
+ | 'jpeg'
18
+ | 'webp'
19
+ | 'gif'
20
+ | 'svg'
21
+ | 'mp3'
22
+ | 'ogg'
23
+ | 'wav'
24
+ | 'flac'
25
+ | 'vscn.json'
26
+ | 'prefab.json'
27
+ | 'mat.json'
28
+ | 'inputmap.json'
29
+ | 'data.json'
30
+ | 'json'
31
+ | 'unknown';
32
+
33
+ export type AssetImportStrategy =
34
+ | 'copy-runtime'
35
+ | 'convert-model'
36
+ | 'animation-source'
37
+ | 'copy-project'
38
+ | 'unsupported';
39
+
40
+ export type AssetCapabilityKind =
41
+ | 'model'
42
+ | 'image'
43
+ | 'audio'
44
+ | 'json'
45
+ | 'scene'
46
+ | 'prefab'
47
+ | 'material'
48
+ | 'source'
49
+ | 'unknown';
50
+
51
+ export interface AssetCapabilities {
52
+ readonly format: AssetFormat;
53
+ readonly kind: AssetCapabilityKind;
54
+ readonly previewable: boolean;
55
+ readonly importable: boolean;
56
+ readonly runtimeReady: boolean;
57
+ readonly placeable: boolean;
58
+ readonly animated: boolean;
59
+ readonly importStrategy: AssetImportStrategy;
60
+ readonly editor: 'model' | 'image' | 'audio' | 'material' | 'data' | 'scene' | 'source' | 'none';
61
+ }
62
+
63
+ const MODEL_PREVIEW_FORMATS = ['glb', 'gltf', 'fbx', 'obj', 'dae', 'stl', 'ply', '3ds'] as const;
64
+ const SOURCE_MODEL_FORMATS = ['blend', 'usd', 'usda', 'usdc'] as const;
65
+ const IMAGE_FORMATS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'svg'] as const;
66
+ const AUDIO_FORMATS = ['mp3', 'ogg', 'wav', 'flac'] as const;
67
+
68
+ function row(
69
+ format: AssetFormat,
70
+ values: Omit<AssetCapabilities, 'format'>,
71
+ ): readonly [AssetFormat, AssetCapabilities] {
72
+ return [format, { format, ...values }];
73
+ }
74
+
75
+ const MODEL_SOURCE_BASE = {
76
+ kind: 'source',
77
+ previewable: true,
78
+ importable: true,
79
+ runtimeReady: false,
80
+ placeable: false,
81
+ animated: false,
82
+ importStrategy: 'convert-model',
83
+ editor: 'model',
84
+ } as const;
85
+
86
+ export const ASSET_CAPABILITY_REGISTRY: ReadonlyMap<AssetFormat, AssetCapabilities> = new Map([
87
+ ...MODEL_PREVIEW_FORMATS.map((format) =>
88
+ row(format, {
89
+ ...MODEL_SOURCE_BASE,
90
+ kind: format === 'glb' || format === 'gltf' ? 'model' : 'source',
91
+ runtimeReady: format === 'glb' || format === 'gltf',
92
+ placeable: format === 'glb' || format === 'gltf',
93
+ importStrategy: format === 'glb' || format === 'gltf' ? 'copy-runtime' : 'convert-model',
94
+ }),
95
+ ),
96
+ row('bvh', {
97
+ kind: 'source',
98
+ previewable: true,
99
+ importable: true,
100
+ runtimeReady: false,
101
+ placeable: false,
102
+ animated: true,
103
+ importStrategy: 'animation-source',
104
+ editor: 'model',
105
+ }),
106
+ ...SOURCE_MODEL_FORMATS.map((format) =>
107
+ row(format, { ...MODEL_SOURCE_BASE, previewable: false, editor: 'source' }),
108
+ ),
109
+ ...IMAGE_FORMATS.map((format) =>
110
+ row(format, {
111
+ kind: 'image',
112
+ previewable: true,
113
+ importable: true,
114
+ runtimeReady: true,
115
+ placeable: false,
116
+ animated: format === 'gif',
117
+ importStrategy: 'copy-project',
118
+ editor: 'image',
119
+ }),
120
+ ),
121
+ ...AUDIO_FORMATS.map((format) =>
122
+ row(format, {
123
+ kind: 'audio',
124
+ previewable: true,
125
+ importable: true,
126
+ runtimeReady: true,
127
+ placeable: false,
128
+ animated: false,
129
+ importStrategy: 'copy-project',
130
+ editor: 'audio',
131
+ }),
132
+ ),
133
+ row('vscn.json', {
134
+ kind: 'scene',
135
+ previewable: false,
136
+ importable: true,
137
+ runtimeReady: true,
138
+ placeable: false,
139
+ animated: false,
140
+ importStrategy: 'copy-project',
141
+ editor: 'scene',
142
+ }),
143
+ row('prefab.json', {
144
+ kind: 'prefab',
145
+ previewable: true,
146
+ importable: true,
147
+ runtimeReady: true,
148
+ placeable: true,
149
+ animated: false,
150
+ importStrategy: 'copy-project',
151
+ editor: 'scene',
152
+ }),
153
+ row('mat.json', {
154
+ kind: 'material',
155
+ previewable: true,
156
+ importable: true,
157
+ runtimeReady: true,
158
+ placeable: false,
159
+ animated: false,
160
+ importStrategy: 'copy-project',
161
+ editor: 'material',
162
+ }),
163
+ row('inputmap.json', {
164
+ kind: 'json',
165
+ previewable: true,
166
+ importable: true,
167
+ runtimeReady: true,
168
+ placeable: false,
169
+ animated: false,
170
+ importStrategy: 'copy-project',
171
+ editor: 'data',
172
+ }),
173
+ row('data.json', {
174
+ kind: 'json',
175
+ previewable: true,
176
+ importable: true,
177
+ runtimeReady: true,
178
+ placeable: false,
179
+ animated: false,
180
+ importStrategy: 'copy-project',
181
+ editor: 'data',
182
+ }),
183
+ row('json', {
184
+ kind: 'json',
185
+ previewable: true,
186
+ importable: true,
187
+ runtimeReady: false,
188
+ placeable: false,
189
+ animated: false,
190
+ importStrategy: 'copy-project',
191
+ editor: 'data',
192
+ }),
193
+ ]);
194
+
195
+ const UNKNOWN_CAPABILITIES: AssetCapabilities = {
196
+ format: 'unknown',
197
+ kind: 'unknown',
198
+ previewable: false,
199
+ importable: false,
200
+ runtimeReady: false,
201
+ placeable: false,
202
+ animated: false,
203
+ importStrategy: 'unsupported',
204
+ editor: 'none',
205
+ };
206
+
207
+ export function assetFormatFromPath(path: string): AssetFormat {
208
+ const clean = path.split(/[?#]/, 1)[0]!.toLowerCase();
209
+ for (const compound of [
210
+ 'vscn.json',
211
+ 'prefab.json',
212
+ 'mat.json',
213
+ 'inputmap.json',
214
+ 'data.json',
215
+ ] as const) {
216
+ if (clean.endsWith(`.${compound}`)) return compound;
217
+ }
218
+ const dot = clean.lastIndexOf('.');
219
+ const extension = dot >= 0 ? clean.slice(dot + 1) : '';
220
+ return ASSET_CAPABILITY_REGISTRY.has(extension as AssetFormat)
221
+ ? (extension as AssetFormat)
222
+ : extension === 'json'
223
+ ? 'json'
224
+ : 'unknown';
225
+ }
226
+
227
+ export function assetCapabilities(pathOrFormat: string): AssetCapabilities {
228
+ const direct = ASSET_CAPABILITY_REGISTRY.get(pathOrFormat.toLowerCase() as AssetFormat);
229
+ if (direct) return direct;
230
+ return ASSET_CAPABILITY_REGISTRY.get(assetFormatFromPath(pathOrFormat)) ?? UNKNOWN_CAPABILITIES;
231
+ }
232
+
233
+ export function supportedAssetFormats(): readonly AssetFormat[] {
234
+ return [...ASSET_CAPABILITY_REGISTRY.keys()];
235
+ }
236
+
237
+ /** Editor document kind for a capability row; null means no honest editor exists. */
238
+ export function assetDocumentKind(
239
+ capability: AssetCapabilities,
240
+ ): 'model' | 'image' | 'audio' | 'json' | 'scene' | 'prefab' | 'material' | 'source' | null {
241
+ switch (capability.editor) {
242
+ case 'model':
243
+ return 'model';
244
+ case 'image':
245
+ return 'image';
246
+ case 'audio':
247
+ return 'audio';
248
+ case 'material':
249
+ return 'material';
250
+ case 'data':
251
+ return 'json';
252
+ case 'source':
253
+ return 'source';
254
+ case 'scene':
255
+ return capability.kind === 'prefab' ? 'prefab' : 'scene';
256
+ default:
257
+ return null;
258
+ }
259
+ }
@@ -0,0 +1,46 @@
1
+ export type AssetLoadStage =
2
+ | 'fetching'
3
+ | 'parsing'
4
+ | 'dependency-resolution'
5
+ | 'conversion'
6
+ | 'gpu-upload'
7
+ | 'rendering'
8
+ | 'ready';
9
+
10
+ export interface AssetEditorIssue {
11
+ code: string;
12
+ stage: AssetLoadStage;
13
+ message: string;
14
+ recovery: Array<'retry' | 'reveal-source' | 'choose-variant' | 'reimport'>;
15
+ }
16
+
17
+ /** Convert loader-specific failures into actionable, stable editor diagnostics. */
18
+ export function classifyAssetEditorError(error: unknown, stage: AssetLoadStage): AssetEditorIssue {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ const lower = message.toLowerCase();
21
+ if (lower.includes('404') || lower.includes('failed to fetch')) {
22
+ return {
23
+ code: 'asset.fetch.failed',
24
+ stage: 'fetching',
25
+ message,
26
+ recovery: ['retry', 'reveal-source'],
27
+ };
28
+ }
29
+ if (lower.includes('dependency') || lower.includes('buffer') || lower.includes('texture')) {
30
+ return {
31
+ code: 'asset.dependency.missing',
32
+ stage: 'dependency-resolution',
33
+ message,
34
+ recovery: ['reveal-source', 'reimport'],
35
+ };
36
+ }
37
+ if (lower.includes('bounds') || lower.includes('geometry')) {
38
+ return {
39
+ code: 'asset.render.no-geometry',
40
+ stage: 'rendering',
41
+ message: `${message} The source may be animation-only or contain hidden/zero-sized geometry.`,
42
+ recovery: ['choose-variant', 'reimport'],
43
+ };
44
+ }
45
+ return { code: `asset.${stage}.failed`, stage, message, recovery: ['retry', 'reimport'] };
46
+ }
@@ -0,0 +1,176 @@
1
+ import type { HistoryService } from '../history/history-service';
2
+ import { getProjectFileHistory } from '../history/project-file-history';
3
+ import { sha256Hex } from '../history/snapshot-store';
4
+ import type { StorageBackend } from '../storage/types';
5
+ import { recordAssetWorkflowMetric } from './asset-workflow-quality';
6
+ import {
7
+ type AssetImportPlan,
8
+ type AssetImportProgress,
9
+ type AssetImportResult,
10
+ assetImportPlanSchema,
11
+ importRecipePath,
12
+ } from './import-contract';
13
+
14
+ export interface StagedImportFile {
15
+ path: string;
16
+ bytes: Uint8Array;
17
+ }
18
+ export interface AssetImportPipelineDependencies {
19
+ fetch(
20
+ file: AssetImportPlan['sourceFiles'][number],
21
+ signal: AbortSignal,
22
+ progress: (loaded: number, total: number) => void,
23
+ ): Promise<Uint8Array>;
24
+ convert(
25
+ plan: AssetImportPlan,
26
+ sources: readonly StagedImportFile[],
27
+ signal: AbortSignal,
28
+ ): Promise<readonly StagedImportFile[]>;
29
+ validate(
30
+ plan: AssetImportPlan,
31
+ files: readonly StagedImportFile[],
32
+ signal: AbortSignal,
33
+ ): Promise<void>;
34
+ commit(plan: AssetImportPlan, files: readonly StagedImportFile[]): Promise<boolean>;
35
+ }
36
+
37
+ function abortIfNeeded(signal: AbortSignal): void {
38
+ if (signal.aborted) throw signal.reason ?? new DOMException('Import cancelled', 'AbortError');
39
+ }
40
+
41
+ /** Four-stage import: no project mutation occurs until all staged bytes validate. */
42
+ export async function runAssetImportPipeline(
43
+ rawPlan: AssetImportPlan,
44
+ dependencies: AssetImportPipelineDependencies,
45
+ options: { signal: AbortSignal; onProgress?: (progress: AssetImportProgress) => void },
46
+ ): Promise<AssetImportResult> {
47
+ const plan = assetImportPlanSchema.parse(rawPlan);
48
+ const stagedSources: StagedImportFile[] = [];
49
+ let stageStarted = performance.now();
50
+ const metric = (
51
+ stage: 'delivery' | 'conversion' | 'validation' | 'commit',
52
+ outcome: 'ok' | 'failed' | 'cancelled' = 'ok',
53
+ ) => {
54
+ recordAssetWorkflowMetric({ stage, durationMs: performance.now() - stageStarted, outcome });
55
+ stageStarted = performance.now();
56
+ };
57
+ try {
58
+ for (const file of plan.sourceFiles) {
59
+ abortIfNeeded(options.signal);
60
+ const bytes = await dependencies.fetch(file, options.signal, (loaded, total) =>
61
+ options.onProgress?.({ stage: 'fetch', item: file.path, loaded, total }),
62
+ );
63
+ stagedSources.push({ path: file.path, bytes });
64
+ }
65
+ metric('delivery');
66
+ abortIfNeeded(options.signal);
67
+ options.onProgress?.({ stage: 'convert', item: plan.destination, loaded: 0, total: 1 });
68
+ const outputs = await dependencies.convert(plan, stagedSources, options.signal);
69
+ metric('conversion');
70
+ abortIfNeeded(options.signal);
71
+ options.onProgress?.({
72
+ stage: 'validate',
73
+ item: plan.destination,
74
+ loaded: 0,
75
+ total: outputs.length,
76
+ });
77
+ await dependencies.validate(plan, outputs, options.signal);
78
+ metric('validation');
79
+ abortIfNeeded(options.signal);
80
+ options.onProgress?.({
81
+ stage: 'commit',
82
+ item: plan.destination,
83
+ loaded: 0,
84
+ total: outputs.length,
85
+ });
86
+ if (!(await dependencies.commit(plan, outputs)))
87
+ throw new Error('Atomic project commit was rejected.');
88
+ metric('commit');
89
+ return {
90
+ version: 1,
91
+ planId: plan.id,
92
+ status: 'imported',
93
+ writtenFiles: await Promise.all(
94
+ outputs.map(async (file) => ({
95
+ path: file.path,
96
+ hash: await sha256Hex(file.bytes),
97
+ size: file.bytes.byteLength,
98
+ })),
99
+ ),
100
+ skippedFiles: [],
101
+ issues: [],
102
+ recipePath: importRecipePath(plan.destination),
103
+ };
104
+ } catch (error) {
105
+ const cancelled =
106
+ options.signal.aborted || (error instanceof DOMException && error.name === 'AbortError');
107
+ metric('commit', cancelled ? 'cancelled' : 'failed');
108
+ return {
109
+ version: 1,
110
+ planId: plan.id,
111
+ status: cancelled ? 'cancelled' : 'failed',
112
+ writtenFiles: [],
113
+ skippedFiles: [],
114
+ issues: [
115
+ {
116
+ code: cancelled ? 'import.cancelled' : 'import.failed',
117
+ stage: 'staging',
118
+ message: error instanceof Error ? error.message : String(error),
119
+ },
120
+ ],
121
+ };
122
+ }
123
+ }
124
+
125
+ export async function commitAssetImport(
126
+ plan: AssetImportPlan,
127
+ outputs: readonly StagedImportFile[],
128
+ dependencies: { backend: StorageBackend; history: HistoryService },
129
+ ): Promise<boolean> {
130
+ const recipe = JSON.stringify(plan, null, 2);
131
+ const changes = [
132
+ ...outputs.map((file) => ({ path: file.path, data: file.bytes })),
133
+ { path: importRecipePath(plan.destination), data: recipe, contentType: 'application/json' },
134
+ ];
135
+ return getProjectFileHistory(dependencies.history, dependencies.backend).mutate(changes, {
136
+ label: `Import ${plan.identity.familyId}`,
137
+ });
138
+ }
139
+
140
+ export async function fetchImportFile(
141
+ file: AssetImportPlan['sourceFiles'][number],
142
+ signal: AbortSignal,
143
+ progress: (loaded: number, total: number) => void,
144
+ ): Promise<Uint8Array> {
145
+ const response = await fetch(file.url, { signal, cache: 'no-store' });
146
+ if (!response.ok) throw new Error(`Fetch failed with HTTP ${response.status}: ${file.url}`);
147
+ const bytes = new Uint8Array(await response.arrayBuffer());
148
+ progress(bytes.byteLength, Number(response.headers.get('content-length')) || bytes.byteLength);
149
+ if ((await sha256Hex(bytes)) !== file.hash)
150
+ throw new Error(`Source hash mismatch for ${file.path}.`);
151
+ return bytes;
152
+ }
153
+
154
+ export interface AttributionEntry {
155
+ familyId: string;
156
+ name: string;
157
+ author: string;
158
+ license: string;
159
+ sourceUrl: string;
160
+ text: string;
161
+ }
162
+
163
+ export function generateAttributionReports(entries: readonly AttributionEntry[]): {
164
+ json: string;
165
+ markdown: string;
166
+ } {
167
+ const unique = [
168
+ ...new Map(
169
+ entries.map((entry) => [`${entry.familyId}:${entry.license}:${entry.sourceUrl}`, entry]),
170
+ ).values(),
171
+ ].sort((a, b) => a.name.localeCompare(b.name) || a.familyId.localeCompare(b.familyId));
172
+ return {
173
+ json: `${JSON.stringify({ version: 1, assets: unique }, null, 2)}\n`,
174
+ markdown: `# Asset attribution\n\n${unique.map((entry) => `## ${entry.name}\n\n${entry.text}\n\nAuthor: ${entry.author} \nLicense: ${entry.license} \nSource: ${entry.sourceUrl}`).join('\n\n')}\n`,
175
+ };
176
+ }