@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
@@ -18,13 +18,14 @@ import { CompositeAuthoringAdapter } from '../composite-authoring-adapter';
18
18
  import { VgaiSceneAuthoringAdapter } from '../vgai-scene-authoring-adapter';
19
19
 
20
20
  /**
21
- * True when `node` (or, if `null`, the "no selection" case) is owned by a
22
- * first-party `VgaiSceneAuthoringAdapter` — INCLUDING the null-selection
23
- * fallback: a composite with no selected node falls back to "does a
24
- * first-party child exist at all" (in every production shape there is at
25
- * most one — the edit-mode composite's focused threejs world — so this is
26
- * equivalent to "the focused/first-party child exists" without needing a
27
- * separate "focused" concept).
21
+ * True when `node` is a real Object3D-backed entity owned by a first-party
22
+ * `VgaiSceneAuthoringAdapter`, or when `node` is null and the active adapter
23
+ * has a first-party scene child (the no-selection Environment state).
24
+ *
25
+ * A scene document row is intentionally false: it is owned by the scene
26
+ * adapter for hierarchy routing, but it is not a SceneEntity and therefore
27
+ * cannot be passed to entity-only sections such as HeaderSection. Document
28
+ * rows have their own Environment registration in `register.tsx`.
28
29
  */
29
30
  export function isFirstPartySelection(node: EditorNode | null, adapter: AuthoringAdapter): boolean {
30
31
  const id = node?.id ?? null;
@@ -35,10 +36,28 @@ export function isFirstPartySelection(node: EditorNode | null, adapter: Authorin
35
36
  const owner = adapter.ownerOf(id);
36
37
  if (owner === null) return false; // world:<id> group node or unrecognized id -> generic
37
38
  const child = adapter.childAdapters().find((c) => c.worldId === owner);
38
- return child?.adapter instanceof VgaiSceneAuthoringAdapter;
39
+ return (
40
+ child?.adapter instanceof VgaiSceneAuthoringAdapter &&
41
+ child.adapter.hierarchy.object3D(id) !== null
42
+ );
43
+ }
44
+ if (!(adapter instanceof VgaiSceneAuthoringAdapter)) return false;
45
+ return id === null || adapter.hierarchy.object3D(id) !== null;
46
+ }
47
+
48
+ /** True only for a first-party scene's synthetic document/root row. */
49
+ export function isFirstPartySceneDocument(
50
+ node: EditorNode | null,
51
+ adapter: AuthoringAdapter,
52
+ ): boolean {
53
+ if (!node || node.role !== 'document') return false;
54
+ if (adapter instanceof CompositeAuthoringAdapter) {
55
+ const owner = adapter.ownerOf(node.id);
56
+ if (owner === null) return false;
57
+ return (
58
+ adapter.childAdapters().find((child) => child.worldId === owner)?.adapter instanceof
59
+ VgaiSceneAuthoringAdapter
60
+ );
39
61
  }
40
- // Bare (non-composite) adapter: first-party-ness doesn't depend on
41
- // selection at all — a bare `VgaiSceneAuthoringAdapter` is first-party
42
- // whether or not anything is selected; a bare ingest/module override never is.
43
62
  return adapter instanceof VgaiSceneAuthoringAdapter;
44
63
  }
@@ -44,9 +44,9 @@ import {
44
44
  import { AddSectionMenu } from './AddSectionMenu';
45
45
  import { EnvironmentSection } from './EnvironmentSection';
46
46
  import { HeaderSection } from './HeaderSection';
47
- import { isFirstPartySelection } from './is-first-party-selection';
47
+ import { isFirstPartySceneDocument, isFirstPartySelection } from './is-first-party-selection';
48
48
 
49
- export { isFirstPartySelection };
49
+ export { isFirstPartySceneDocument, isFirstPartySelection };
50
50
 
51
51
  /** A first-party selection (an actual node, not the deselect state). */
52
52
  const isSelected = (node: EditorNode | null, adapter: AuthoringAdapter): boolean =>
@@ -56,6 +56,10 @@ const isSelected = (node: EditorNode | null, adapter: AuthoringAdapter): boolean
56
56
  const isDeselected = (node: EditorNode | null, adapter: AuthoringAdapter): boolean =>
57
57
  node === null && isFirstPartySelection(node, adapter);
58
58
 
59
+ /** A first-party scene document row shows scene settings, not entity fields. */
60
+ const isSceneDocument = (node: EditorNode | null, adapter: AuthoringAdapter): boolean =>
61
+ isFirstPartySceneDocument(node, adapter);
62
+
59
63
  /**
60
64
  * Adapt a `SectionProps`-based typed section (the existing
61
65
  * `components/inspectors/*.tsx` components, which already read the live
@@ -95,6 +99,13 @@ registerInspectorSections({ match: isSelected, Section: wrap(NavigationSection)
95
99
  registerInspectorSections({ match: isSelected, Section: wrap(ComponentsSection) });
96
100
  registerInspectorSections({ match: isSelected, Section: wrap(AddSectionMenu) });
97
101
 
102
+ registerInspectorSections({
103
+ match: isSceneDocument,
104
+ Section: EnvironmentSection,
105
+ suppresses: ['header', 'transform', 'properties', 'components', 'stories'],
106
+ panelName: 'Inspector',
107
+ });
108
+
98
109
  registerInspectorSections({
99
110
  match: isDeselected,
100
111
  Section: EnvironmentSection,
@@ -30,6 +30,7 @@
30
30
  import type {
31
31
  AuthoringAdapter,
32
32
  AuthoringCapabilities,
33
+ AuthoringProvenance,
33
34
  EditorNode,
34
35
  HierarchyProvider,
35
36
  InspectorProvider,
@@ -47,12 +48,31 @@ import {
47
48
  colorMaterialOf,
48
49
  } from '@engine/adapter/ingest/overlay-apply';
49
50
  import type * as THREE from 'three';
51
+ import { editorConsole } from '../editor-console';
50
52
  import type { EditorStore } from '../editor-store';
53
+ import { JsonHistoryResource } from '../history/json-history-resource';
51
54
  import { captureSubtreeToVscn } from './ingest-capture';
52
55
  import type { IngestOverlay } from './ingest-overlay';
53
- import { notifyUndoableOp, registerUndoAdapter } from './undo-timeline';
54
56
  import { raycastPick } from './viewport-raycast';
55
57
 
58
+ interface IngestHistoryState {
59
+ overlay: IngestOverlay;
60
+ objects: Record<
61
+ string,
62
+ {
63
+ name: string;
64
+ visible: boolean;
65
+ transform: Transform;
66
+ color?: string;
67
+ lightIntensity?: number;
68
+ lightColor?: string;
69
+ camera?: { fov: number; near: number; far: number };
70
+ castShadow: boolean;
71
+ receiveShadow: boolean;
72
+ }
73
+ >;
74
+ }
75
+
56
76
  /** Loop-gate hooks so a stable edit can pause the game that owns the rAF. */
57
77
  export interface IngestLoopControl {
58
78
  pause(): void;
@@ -115,9 +135,17 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
115
135
  /** Accumulated edits (this session + reapplied overlay), keyed by stable id. */
116
136
  private overlay: IngestOverlay = {};
117
137
  private dirty = false;
138
+ private readonly historyResource: JsonHistoryResource<IngestHistoryState> | null;
118
139
 
119
140
  readonly capabilities: AuthoringCapabilities;
120
141
 
142
+ readonly provenance: AuthoringProvenance = {
143
+ source: 'foreign',
144
+ label: 'overlay',
145
+ detail:
146
+ 'Unmodified ingested game — edits persist to an editor overlay, never to the game source.',
147
+ };
148
+
121
149
  constructor(
122
150
  private readonly store: EditorStore,
123
151
  private readonly scene: THREE.Scene,
@@ -128,12 +156,6 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
128
156
  this.destination =
129
157
  opts.destination ?? (opts.persist ? '(ingest overlay)' : '(runtime-only — not persisted)');
130
158
  this.camera = opts.camera;
131
- // T3.2 slice 2: this adapter IS the undo/redo target the editor-global
132
- // timeline dispatches to for entries it records below (`pushUndo`).
133
- // `ingest-mode.ts`'s `exitIngestMode()` unregisters it on teardown, so a
134
- // Ctrl+Z reaching an entry from a since-uninstalled ingest session drops it
135
- // (with a warn) instead of undoing against a stale live scene.
136
- registerUndoAdapter(this);
137
159
  this.capabilities = {
138
160
  transform: true,
139
161
  material: true,
@@ -151,6 +173,33 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
151
173
  };
152
174
  this.refresh();
153
175
  if (opts.overlay) this.applyOverlay(opts.overlay);
176
+ const history = store.projectHistory;
177
+ this.historyResource = history
178
+ ? new JsonHistoryResource({
179
+ history,
180
+ kind: opts.persist ? 'overlay' : 'session-state',
181
+ scope: opts.persist ? 'project' : 'session',
182
+ ...(opts.persist
183
+ ? { location: (opts.destination ?? '.vgai/overlays/ingest.json').replace(/^\//, '') }
184
+ : { sessionId: `ingest-${crypto.randomUUID()}` }),
185
+ displayName: opts.destination ?? 'Ingest edits',
186
+ capture: () => this.captureHistoryState(),
187
+ // The snapshot carries a mirror of every live Object3D (the restore
188
+ // payload), but an ingested game is a RUNNING game that animates its
189
+ // own objects every frame — so the full snapshot stops matching a
190
+ // frame after it is taken. Only the OVERLAY is this resource's real
191
+ // content (it is what `serialize()`/`save()` writes to disk, and only
192
+ // editor edits touch it), so it alone decides "did something else
193
+ // change this resource?". Without this, every ingest transaction
194
+ // failed preflight with `content-conflict` and undo silently did
195
+ // nothing (issue #81).
196
+ conflictIdentity: (state) => state.overlay,
197
+ restore: async (state) => {
198
+ this.restoreHistoryState(state);
199
+ if (this.persistFn) await this.persistFn(this.overlay);
200
+ },
201
+ })
202
+ : null;
154
203
  }
155
204
 
156
205
  /**
@@ -231,6 +280,7 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
231
280
  return {
232
281
  id,
233
282
  label: o.name || o.type || 'Object3D',
283
+ role: 'entity',
234
284
  kind: kindOf(o),
235
285
  parentId,
236
286
  childIds: o.children.map((c) => this.idOf(c)!).filter(Boolean),
@@ -292,26 +342,22 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
292
342
  // The game owns this object's transform; the editor freezes the game loop so
293
343
  // its gizmo edit isn't overwritten next frame, then resumes on release.
294
344
  owner: (): TransformOwner => 'external',
295
- beginEdit: (id) => {
345
+ beginEdit: () => {
346
+ this.historyResource?.assertCanMutate();
296
347
  this.loop?.pause();
297
- this.editStartTransform = this.transforms.get(id); // snapshot for undo
348
+ this.editStartState = this.captureHistoryState();
298
349
  },
299
350
  apply: (id, t) => this.writeTransform(id, t),
300
351
  endEdit: (id) => {
301
352
  this.loop?.resume();
302
- const prev = this.editStartTransform;
303
- this.editStartTransform = undefined;
304
- if (!prev) return;
305
- const next = this.transforms.get(id);
306
- this.pushUndo({
307
- undo: () => this.writeTransform(id, prev),
308
- redo: () => this.writeTransform(id, next),
309
- });
353
+ this.recordHistory(`Transform ${this.byId.get(id)?.name || 'Object'}`, this.editStartState);
354
+ this.editStartState = undefined;
310
355
  },
311
356
  };
312
357
 
313
358
  /** Write a transform to the live object + record it in the overlay (no undo push). */
314
359
  private writeTransform(id: string, t: Transform): void {
360
+ this.historyResource?.assertCanMutate();
315
361
  const o = this.byId.get(id);
316
362
  if (!o) return;
317
363
  o.position.set(t.position[0], t.position[1], t.position[2]);
@@ -376,12 +422,10 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
376
422
  return undefined;
377
423
  },
378
424
  set: (id, path, value) => {
379
- const prev = this.inspector.get(id, path); // snapshot for undo
425
+ this.historyResource?.assertCanMutate();
426
+ const before = this.captureHistoryState();
380
427
  this.writeProp(id, path, value);
381
- this.pushUndo({
382
- undo: () => this.writeProp(id, path, prev),
383
- redo: () => this.writeProp(id, path, value),
384
- });
428
+ this.recordHistory(`Set ${this.byId.get(id)?.name || 'Object'} ${path}`, before);
385
429
  },
386
430
  };
387
431
 
@@ -417,34 +461,87 @@ export class IngestAuthoringAdapter implements AuthoringAdapter {
417
461
  }
418
462
 
419
463
  // --- Overlay-backed undo/redo (feat-edit-undo for ingested games) ---
420
- private readonly undoStack: Array<{ undo: () => void; redo: () => void }> = [];
421
- private readonly redoStack: Array<{ undo: () => void; redo: () => void }> = [];
422
- private editStartTransform: Transform | undefined;
423
-
424
- private pushUndo(entry: { undo: () => void; redo: () => void }): void {
425
- this.undoStack.push(entry);
426
- this.redoStack.length = 0;
427
- notifyUndoableOp(this); // T3.2 slice 2 — tag this op on the global timeline
464
+ private editStartState: IngestHistoryState | undefined;
465
+
466
+ /**
467
+ * Journal a before→after snapshot pair into project history, LOUDLY.
468
+ *
469
+ * A rejected `record(...)` means the edit IS applied to the live scene but has
470
+ * no history entry: it cannot be undone and will not survive a reload.
471
+ * Swallowing that (the previous `.catch(() => {})`) turned a persistence
472
+ * failure into silent data loss — the user sees their edit, then loses it with
473
+ * no indication anything went wrong.
474
+ */
475
+ private recordHistory(label: string, before: IngestHistoryState | undefined): void {
476
+ if (!this.historyResource || !before) return;
477
+ void this.historyResource
478
+ .record(label, before, this.captureHistoryState())
479
+ .catch((err: unknown) => {
480
+ editorConsole.error(
481
+ `[ingest] failed to journal "${label}" into project history — the edit is applied ` +
482
+ `but has no history entry (it cannot be undone and will not survive reload): ` +
483
+ `${err instanceof Error ? err.message : String(err)}`,
484
+ );
485
+ });
428
486
  }
429
487
 
430
- undo(): boolean {
431
- // `false` tells the undo timeline "couldn't act — put the entry back"
432
- // (see UndoAdapter in ./undo-timeline.ts).
433
- const e = this.undoStack.pop();
434
- if (!e) return false;
435
- e.undo();
436
- this.redoStack.push(e);
437
- this.store.notifyIngestEdit();
438
- return true;
488
+ dispose(): void {
489
+ this.historyResource?.dispose();
490
+ }
491
+
492
+ private captureHistoryState(): IngestHistoryState {
493
+ const objects: IngestHistoryState['objects'] = {};
494
+ for (const [id, object] of this.byId) {
495
+ const material = colorMaterialOf(object);
496
+ const light = lightOf(object);
497
+ const camera = perspectiveCameraOf(object);
498
+ objects[id] = {
499
+ name: object.name,
500
+ visible: object.visible,
501
+ transform: this.transforms.get(id),
502
+ ...(material ? { color: `#${material.color.getHexString()}` } : {}),
503
+ ...(light
504
+ ? {
505
+ lightIntensity: light.intensity,
506
+ lightColor: `#${light.color.getHexString()}`,
507
+ }
508
+ : {}),
509
+ ...(camera ? { camera: { fov: camera.fov, near: camera.near, far: camera.far } } : {}),
510
+ castShadow: object.castShadow,
511
+ receiveShadow: object.receiveShadow,
512
+ };
513
+ }
514
+ return { overlay: structuredClone(this.overlay), objects };
439
515
  }
440
516
 
441
- redo(): boolean {
442
- const e = this.redoStack.pop();
443
- if (!e) return false;
444
- e.redo();
445
- this.undoStack.push(e);
517
+ private restoreHistoryState(state: IngestHistoryState): void {
518
+ for (const [id, value] of Object.entries(state.objects)) {
519
+ const object = this.byId.get(id);
520
+ if (!object) continue;
521
+ object.name = value.name;
522
+ object.visible = value.visible;
523
+ object.position.fromArray(value.transform.position);
524
+ object.quaternion.fromArray(value.transform.rotation);
525
+ object.scale.fromArray(value.transform.scale);
526
+ if (value.color) colorMaterialOf(object)?.color.set(value.color);
527
+ const light = lightOf(object);
528
+ if (light) {
529
+ if (value.lightIntensity !== undefined) light.intensity = value.lightIntensity;
530
+ if (value.lightColor) light.color.set(value.lightColor);
531
+ }
532
+ const camera = perspectiveCameraOf(object);
533
+ if (camera && value.camera) {
534
+ camera.fov = value.camera.fov;
535
+ camera.near = value.camera.near;
536
+ camera.far = value.camera.far;
537
+ camera.updateProjectionMatrix();
538
+ }
539
+ object.castShadow = value.castShadow;
540
+ object.receiveShadow = value.receiveShadow;
541
+ }
542
+ this.overlay = structuredClone(state.overlay);
543
+ this.dirty = true;
446
544
  this.store.notifyIngestEdit();
447
- return true;
448
545
  }
449
546
 
450
547
  // A class GETTER, not a field initializer: a field initializer runs before the
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * D-V5 (docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md, F24 composite): a composite
13
13
  * manifest (one ingest world + siblings, `ingest-siblings.ts`) can fail
14
- * SEVERAL worlds independently in one mount attempt (a `default-three`
14
+ * SEVERAL roots independently in one mount attempt (a `default-three`
15
15
  * sibling, a throwing `default-react` sibling, …) — a single-slot "last
16
16
  * failure wins" model would silently drop every failure but the latest, the
17
17
  * exact F9 anti-pattern this module exists to close. This generalizes the
@@ -38,12 +38,12 @@
38
38
 
39
39
  /** One manifest world's mount failure — the exact fields the banner shows. */
40
40
  export interface MountFailureReport {
41
- /** The failing world's manifest id (`ResolvedWorldEntry.id`), or the
41
+ /** The failing world's manifest id (`ResolvedAdapterRoot.id`), or the
42
42
  * `?ingest=<id>` URL-fallback route's discovered game/folder id. */
43
43
  readonly worldId: string;
44
- /** The world's render substrate kind (`ResolvedWorldEntry.kind`, e.g.
44
+ /** The world's render substrate kind (`ResolvedAdapterRoot.kind`, e.g.
45
45
  * `'threejs'`/`'pixijs'`/`'react'`) — or, for the URL-fallback route
46
- * (which has no `ResolvedWorldEntry` to read a kind from), the same
46
+ * (which has no `ResolvedAdapterRoot` to read a kind from), the same
47
47
  * vocabulary inferred from which fixture glob resolved the id. */
48
48
  readonly kind: string;
49
49
  /** The resolved adapter identity (`ResolvedAdapter['identity']` — e.g.
@@ -55,7 +55,7 @@ export interface MountFailureReport {
55
55
  }
56
56
 
57
57
  /** D-V5: an ORDERED list now backs the slot (insertion order — the order
58
- * worlds actually failed in, which for a composite's sibling loop is
58
+ * roots actually failed in, which for a composite's sibling loop is
59
59
  * manifest order). Every existing (wave-4) accessor below is a thin view
60
60
  * over this same array — never a second, parallel source of truth. */
61
61
  let _reports: MountFailureReport[] = [];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Seam-level provenance resolution (spec 29 §5, docs/unified-world-editor/
3
+ * 29-hierarchy-philosophy.md): which {@link AuthoringProvenance} governs a
4
+ * given hierarchy node. Provenance is declared once per adapter and rendered
5
+ * at the seam (world group row / panel header); affordance code also reads it
6
+ * for the REASON string on a disabled operation ("the subject is present but
7
+ * the operation isn't" — the complement of spec 28's hide-when-absent rule).
8
+ *
9
+ * Rule zero: roots are resolved by ASKING the composite (`groupNodeId` /
10
+ * `ownerOf`), never by decoding adapter-minted id strings here.
11
+ */
12
+ import type { AuthoringAdapter, AuthoringProvenance } from '@engine/adapter';
13
+ import { CompositeAuthoringAdapter } from './composite-authoring-adapter';
14
+
15
+ /**
16
+ * The provenance governing `nodeId` under the ACTIVE adapter: the owning
17
+ * child's declaration inside a composite (a `world:<id>` group row resolves
18
+ * to that world's own child), the adapter's own declaration otherwise.
19
+ * `null` when nothing was declared (the shell shows no badge and falls back
20
+ * to a generic reason — never fabricates a claim).
21
+ */
22
+ export function provenanceForNode(
23
+ adapter: AuthoringAdapter,
24
+ nodeId: string,
25
+ ): AuthoringProvenance | null {
26
+ if (!(adapter instanceof CompositeAuthoringAdapter)) return adapter.provenance ?? null;
27
+ for (const child of adapter.childAdapters()) {
28
+ if (adapter.groupNodeId(child.worldId) === nodeId) return child.adapter.provenance ?? null;
29
+ }
30
+ const worldId = adapter.ownerOf(nodeId);
31
+ if (worldId === null) return null;
32
+ const owner = adapter.childAdapters().find((c) => c.worldId === worldId);
33
+ return owner?.adapter.provenance ?? null;
34
+ }
35
+
36
+ /** The reason string for an affordance that is present but unavailable —
37
+ * the adapter's own explanation when it declared one, a plain honest
38
+ * fallback when it didn't. */
39
+ export function unavailableReason(provenance: AuthoringProvenance | null): string {
40
+ return provenance?.detail ?? "Not supported by this world's adapter.";
41
+ }