@volter/editor-core 0.5.57

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 (719) hide show
  1. package/BUNDLED_NOTICES +1527 -0
  2. package/LICENSE +686 -0
  3. package/LICENSE-APACHE +202 -0
  4. package/NOTICE +18 -0
  5. package/README.md +24 -0
  6. package/dist/build/chunk-JS4D4XZ3.js +175 -0
  7. package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
  8. package/dist/build/vite-plugin-product-contributions.js +100 -0
  9. package/dist/build/vite-plugin-product-contributions.js.map +7 -0
  10. package/dist/build/vite-plugin-shared-react.js +25 -0
  11. package/dist/build/vite-plugin-shared-react.js.map +7 -0
  12. package/dist/build/vite-plugin-shared-three.js +86 -0
  13. package/dist/build/vite-plugin-shared-three.js.map +7 -0
  14. package/dist/server/chunk-KMJC7AGO.js +98 -0
  15. package/dist/server/chunk-KMJC7AGO.js.map +7 -0
  16. package/dist/server/chunk-PSILUMGS.js +46 -0
  17. package/dist/server/chunk-PSILUMGS.js.map +7 -0
  18. package/dist/server/frame-proxy.js +255 -0
  19. package/dist/server/frame-proxy.js.map +7 -0
  20. package/dist/server/open-browser.js +53 -0
  21. package/dist/server/open-browser.js.map +7 -0
  22. package/dist/server/process-shutdown.js +127 -0
  23. package/dist/server/process-shutdown.js.map +7 -0
  24. package/dist/server/session-registry.js +141 -0
  25. package/dist/server/session-registry.js.map +7 -0
  26. package/dist/server/spawn-opener.js +11 -0
  27. package/dist/server/spawn-opener.js.map +7 -0
  28. package/dist/server/worktree-identity.js +11 -0
  29. package/dist/server/worktree-identity.js.map +7 -0
  30. package/dist-server/packaged.mjs +88816 -0
  31. package/package.json +118 -0
  32. package/scripts/build-plugins.mjs +14 -0
  33. package/scripts/build-server.mjs +26 -0
  34. package/scripts/build-session.mjs +9 -0
  35. package/server/account-credentials.ts +60 -0
  36. package/server/account-service.ts +1434 -0
  37. package/server/adapter-region-includes.ts +47 -0
  38. package/server/asset-catalog-v2.ts +175 -0
  39. package/server/asset-history-snapshots.ts +196 -0
  40. package/server/asset-ledger-store.ts +195 -0
  41. package/server/asset-library-routes.ts +1595 -0
  42. package/server/blender-wasm-artifact.ts +430 -0
  43. package/server/boot-timings.ts +106 -0
  44. package/server/canonical-path.ts +50 -0
  45. package/server/checkout-workspace-preflight.ts +349 -0
  46. package/server/cloud-asset-catalog.ts +158 -0
  47. package/server/coding-inference-launch.ts +237 -0
  48. package/server/collaboration-account-client.ts +202 -0
  49. package/server/collaboration-attribution.ts +122 -0
  50. package/server/collaboration-session.ts +934 -0
  51. package/server/comfyui-bridge.ts +248 -0
  52. package/server/console-ledger.ts +499 -0
  53. package/server/creation-site-transform.ts +304 -0
  54. package/server/creation-site-write.ts +648 -0
  55. package/server/data-file-serialize.ts +158 -0
  56. package/server/editor-brand-html.ts +72 -0
  57. package/server/editor-control-socket.ts +366 -0
  58. package/server/editor-server-options.ts +190 -0
  59. package/server/editor-server.ts +1483 -0
  60. package/server/editor-sse.ts +352 -0
  61. package/server/engine-provenance.ts +169 -0
  62. package/server/engine-source-restart.ts +117 -0
  63. package/server/frame-bridge.ts +115 -0
  64. package/server/frame-proxy.ts +490 -0
  65. package/server/frame-workbench.ts +259 -0
  66. package/server/frontend-handoff.ts +229 -0
  67. package/server/game-globals-shadow.ts +113 -0
  68. package/server/gameplay-session-retention.ts +149 -0
  69. package/server/gameplay-sessions.ts +123 -0
  70. package/server/generation-jobs.ts +201 -0
  71. package/server/generation-reconciler.ts +137 -0
  72. package/server/generative-execution-context.ts +59 -0
  73. package/server/git-workflow.ts +612 -0
  74. package/server/harness-chat-caller.ts +36 -0
  75. package/server/harness-chat-service.ts +1681 -0
  76. package/server/idle-shutdown.ts +170 -0
  77. package/server/js-profiling-policy.ts +52 -0
  78. package/server/launcher-settings.ts +84 -0
  79. package/server/local-asset-catalog.ts +410 -0
  80. package/server/managed-account-defaults.ts +22 -0
  81. package/server/mock-control-plane.ts +540 -0
  82. package/server/model-import-conversion.ts +345 -0
  83. package/server/native-credential-store.ts +162 -0
  84. package/server/open-browser.ts +118 -0
  85. package/server/packaged.ts +1192 -0
  86. package/server/play-stall.ts +126 -0
  87. package/server/process-shutdown.ts +218 -0
  88. package/server/product-presets.ts +55 -0
  89. package/server/project-build-artifact.ts +95 -0
  90. package/server/project-components.ts +100 -0
  91. package/server/project-dependency-invalidation.ts +114 -0
  92. package/server/project-file-scan.ts +235 -0
  93. package/server/project-hmr-files.ts +215 -0
  94. package/server/project-install-roots.ts +210 -0
  95. package/server/project-kinds.ts +71 -0
  96. package/server/project-mcp-servers.ts +82 -0
  97. package/server/project-module-freshness.ts +109 -0
  98. package/server/project-module-instance.ts +375 -0
  99. package/server/project-optimize-deps-entries.ts +827 -0
  100. package/server/project-output-writer.ts +679 -0
  101. package/server/project-package-origin.ts +63 -0
  102. package/server/project-root-surface.ts +831 -0
  103. package/server/project-scratch-path.ts +47 -0
  104. package/server/project-script-hmr.ts +619 -0
  105. package/server/project-serving-plugins.ts +236 -0
  106. package/server/project-tools.ts +653 -0
  107. package/server/project-validation.ts +250 -0
  108. package/server/project-verbs.ts +127 -0
  109. package/server/project-view.ts +52 -0
  110. package/server/project-watch.ts +1134 -0
  111. package/server/project-work-coordinator.ts +288 -0
  112. package/server/provider-credentials.ts +331 -0
  113. package/server/recent-projects-store.ts +98 -0
  114. package/server/redact-secrets.ts +71 -0
  115. package/server/repository-presence.ts +267 -0
  116. package/server/routes/account.ts +357 -0
  117. package/server/routes/agents.ts +60 -0
  118. package/server/routes/assets.ts +154 -0
  119. package/server/routes/build.ts +13 -0
  120. package/server/routes/collaboration.ts +595 -0
  121. package/server/routes/configurations.ts +419 -0
  122. package/server/routes/context.ts +264 -0
  123. package/server/routes/control-plane.ts +1811 -0
  124. package/server/routes/logs.ts +272 -0
  125. package/server/routes/project-identity.ts +302 -0
  126. package/server/routes/project-open.ts +429 -0
  127. package/server/routes/project-source.ts +686 -0
  128. package/server/routes/project-state.ts +383 -0
  129. package/server/routes/relay.ts +1029 -0
  130. package/server/routes/route-helpers.ts +29 -0
  131. package/server/routes/served-modules.ts +138 -0
  132. package/server/routes/session-tabs.ts +196 -0
  133. package/server/routes/settings.ts +121 -0
  134. package/server/routes/share-control.ts +486 -0
  135. package/server/routes/themes.ts +116 -0
  136. package/server/routes/tools.ts +230 -0
  137. package/server/routes/worktrees.ts +595 -0
  138. package/server/scoped-game-css.ts +461 -0
  139. package/server/server-utils.ts +1194 -0
  140. package/server/session-product.ts +112 -0
  141. package/server/session-registry.ts +230 -0
  142. package/server/share-claims.ts +138 -0
  143. package/server/share-host.ts +526 -0
  144. package/server/share-session-gateway.ts +1362 -0
  145. package/server/share-tunnel.ts +123 -0
  146. package/server/spawn-opener.ts +74 -0
  147. package/server/support/play/log-format.ts +76 -0
  148. package/server/support/play/session-record.ts +199 -0
  149. package/server/support/project/build-discipline.ts +718 -0
  150. package/server/support/project/inspection-node.ts +119 -0
  151. package/server/support/project/log-naming.ts +54 -0
  152. package/server/support/project/provenance.ts +290 -0
  153. package/server/support/project/run-name.ts +18 -0
  154. package/server/support/project/session-journal.ts +830 -0
  155. package/server/support/project/shared.ts +327 -0
  156. package/server/tab-bootstrap.ts +62 -0
  157. package/server/tab-heartbeat.ts +477 -0
  158. package/server/tab-lifecycle.ts +550 -0
  159. package/server/tab-presence.ts +1508 -0
  160. package/server/team-agent-mirror.ts +95 -0
  161. package/server/twin-auth.ts +138 -0
  162. package/server/vendored-lock-recorder.ts +968 -0
  163. package/server/worktree-identity.ts +121 -0
  164. package/server/worktree-management.ts +398 -0
  165. package/server/worktree-retention.d.mts +13 -0
  166. package/server/worktree-retention.mjs +205 -0
  167. package/src/EditorContext.tsx +169 -0
  168. package/src/account.ts +333 -0
  169. package/src/action-registry.ts +532 -0
  170. package/src/active-product.ts +74 -0
  171. package/src/active-project.ts +155 -0
  172. package/src/adapter-editor-config.ts +25 -0
  173. package/src/adapter-observation.ts +49 -0
  174. package/src/animation/stage-transport.ts +379 -0
  175. package/src/animation/three-clips-subject.ts +172 -0
  176. package/src/api/asset-library-wire.ts +45 -0
  177. package/src/api/assets.ts +365 -0
  178. package/src/api/base.ts +10 -0
  179. package/src/api/build.ts +99 -0
  180. package/src/api/git-wire.ts +56 -0
  181. package/src/api/logs.ts +92 -0
  182. package/src/api/project-identity.ts +74 -0
  183. package/src/api/project-open.ts +355 -0
  184. package/src/api/project-source.ts +162 -0
  185. package/src/api/project-state.ts +107 -0
  186. package/src/api/relay.ts +268 -0
  187. package/src/api/settings.ts +36 -0
  188. package/src/api/themes.ts +45 -0
  189. package/src/api/worktrees.ts +257 -0
  190. package/src/asset-compare-core.ts +171 -0
  191. package/src/asset-compare.ts +294 -0
  192. package/src/asset-editor-context.tsx +100 -0
  193. package/src/asset-events.ts +97 -0
  194. package/src/asset-inspector-actions.ts +87 -0
  195. package/src/asset-preview-framing.ts +357 -0
  196. package/src/asset-preview.ts +2802 -0
  197. package/src/asset-selection-viewer-registry.ts +113 -0
  198. package/src/asset-selection.ts +133 -0
  199. package/src/asset-workflow/asset-capabilities.ts +344 -0
  200. package/src/asset-workflow/asset-import-jobs.ts +106 -0
  201. package/src/asset-workflow/asset-ledger-backend.ts +126 -0
  202. package/src/asset-workflow/asset-ledger.ts +156 -0
  203. package/src/asset-workflow/asset-materialization-report.ts +140 -0
  204. package/src/asset-workflow/asset-pack-manifest.ts +320 -0
  205. package/src/asset-workflow/asset-types.ts +142 -0
  206. package/src/asset-workflow/asset-workflow-quality.ts +61 -0
  207. package/src/asset-workflow/audio-preview-player.ts +193 -0
  208. package/src/asset-workflow/audio-waveform.ts +22 -0
  209. package/src/asset-workflow/cloud-asset-client.ts +263 -0
  210. package/src/asset-workflow/folder-preview.ts +345 -0
  211. package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
  212. package/src/asset-workflow/image-view-scale.ts +32 -0
  213. package/src/asset-workflow/import-contract.ts +124 -0
  214. package/src/asset-workflow/ledger-write-lock.ts +244 -0
  215. package/src/asset-workflow/model-inspection.ts +818 -0
  216. package/src/asset-workflow/pixi-spritesheet.ts +197 -0
  217. package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
  218. package/src/asset-workflow/project-asset-commands.ts +23 -0
  219. package/src/asset-workflow/project-asset-health.ts +139 -0
  220. package/src/asset-workflow/project-asset-operations.ts +154 -0
  221. package/src/asset-workflow/project-asset-roots.ts +68 -0
  222. package/src/asset-workflow/project-content.ts +288 -0
  223. package/src/asset-workflow/project-source-index.ts +550 -0
  224. package/src/asset-workflow/thumbnail-system.ts +256 -0
  225. package/src/authoring/active-adapter.ts +199 -0
  226. package/src/authoring/active-systems.ts +422 -0
  227. package/src/authoring/adapter-key.ts +18 -0
  228. package/src/authoring/authoring-asset-url.ts +27 -0
  229. package/src/authoring/bootstrap-state.ts +49 -0
  230. package/src/authoring/boundary-authoring-adapter.ts +184 -0
  231. package/src/authoring/breakpoint-state.ts +43 -0
  232. package/src/authoring/canvas-scene-guides.ts +76 -0
  233. package/src/authoring/component-instance-root.ts +171 -0
  234. package/src/authoring/composite-authoring-adapter.ts +2110 -0
  235. package/src/authoring/consumer-actions.ts +520 -0
  236. package/src/authoring/css-numeric-style.ts +97 -0
  237. package/src/authoring/design-time-layers.ts +849 -0
  238. package/src/authoring/design-time-mount-registry.ts +235 -0
  239. package/src/authoring/design-time-settle.ts +343 -0
  240. package/src/authoring/edit-mode-authoring.ts +619 -0
  241. package/src/authoring/eyedropper-session.ts +60 -0
  242. package/src/authoring/instance-source-menu-register.ts +13 -0
  243. package/src/authoring/instance-source-menu.ts +135 -0
  244. package/src/authoring/layered-pick.ts +183 -0
  245. package/src/authoring/live-gesture-lock.ts +50 -0
  246. package/src/authoring/live-object-transform.ts +62 -0
  247. package/src/authoring/mount-failure-report.ts +154 -0
  248. package/src/authoring/mounted-root-subjects.ts +144 -0
  249. package/src/authoring/no-authoring-adapter.ts +55 -0
  250. package/src/authoring/null-inspection-subjects.tsx +75 -0
  251. package/src/authoring/object3d-document-persistence.ts +106 -0
  252. package/src/authoring/object3d-document-session-registry.ts +120 -0
  253. package/src/authoring/object3d-document-session.ts +1326 -0
  254. package/src/authoring/object3d-gesture-controller.ts +113 -0
  255. package/src/authoring/panel-authoring.ts +121 -0
  256. package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
  257. package/src/authoring/prefab-instance-section-model.ts +55 -0
  258. package/src/authoring/project-authoring-session.ts +105 -0
  259. package/src/authoring/provenance.ts +99 -0
  260. package/src/authoring/quarks-particle-systems.ts +19 -0
  261. package/src/authoring/react-canvas-navigation.ts +255 -0
  262. package/src/authoring/react-design-canvas-style.ts +20 -0
  263. package/src/authoring/react-story-board.ts +937 -0
  264. package/src/authoring/selection-scope.ts +195 -0
  265. package/src/authoring/shell-document-ops.ts +169 -0
  266. package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
  267. package/src/authoring/shell-viewport-policy.ts +48 -0
  268. package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
  269. package/src/authoring/stories-scope.ts +35 -0
  270. package/src/authoring/story-board-chrome-fit.ts +107 -0
  271. package/src/authoring/story-board-presentation.ts +111 -0
  272. package/src/authoring/three-projection-core.ts +226 -0
  273. package/src/authoring/viewport-pick-context.ts +40 -0
  274. package/src/authoring/viewport-raycast.ts +240 -0
  275. package/src/authoring/viewport-tool-context.ts +73 -0
  276. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  277. package/src/authoring/world-hidden-viewport.ts +152 -0
  278. package/src/authoring/world-pan-state.ts +198 -0
  279. package/src/authoring/world-session-state.ts +101 -0
  280. package/src/authoring/write-pipe.ts +173 -0
  281. package/src/availability-tick.ts +66 -0
  282. package/src/bitmap-label.ts +120 -0
  283. package/src/blender-tab-metrics.ts +161 -0
  284. package/src/board-open-actions.ts +20 -0
  285. package/src/boot-routing.ts +391 -0
  286. package/src/build-identity.ts +15 -0
  287. package/src/bytes-codec.ts +62 -0
  288. package/src/camera-authoring.ts +175 -0
  289. package/src/cancellation-reason.ts +58 -0
  290. package/src/canvas-preview-frames.ts +482 -0
  291. package/src/capture-camera-pose.ts +77 -0
  292. package/src/chrome-registry.ts +159 -0
  293. package/src/chrome-slot-registry.ts +91 -0
  294. package/src/collaboration-client.ts +264 -0
  295. package/src/collaboration-presence.ts +41 -0
  296. package/src/command-dispatch.ts +19 -0
  297. package/src/command-listener.ts +2743 -0
  298. package/src/command-registry.ts +70 -0
  299. package/src/component-board-registry.ts +205 -0
  300. package/src/component-states-registry.ts +200 -0
  301. package/src/components/AgentPresentationNotice.tsx +75 -0
  302. package/src/components/AlignToolbar.tsx +204 -0
  303. package/src/components/AppRoot.tsx +394 -0
  304. package/src/components/ApplicationMenus.tsx +415 -0
  305. package/src/components/AssetBrowser.tsx +2843 -0
  306. package/src/components/AssetEditorShell.tsx +216 -0
  307. package/src/components/AssetInspectorToolSection.tsx +124 -0
  308. package/src/components/BoardRulers.tsx +354 -0
  309. package/src/components/CameraInfo.tsx +81 -0
  310. package/src/components/CanvasSceneViewport.tsx +556 -0
  311. package/src/components/CapabilityCoverageSection.tsx +185 -0
  312. package/src/components/CenterDocuments.tsx +22 -0
  313. package/src/components/ChromeSlot.tsx +20 -0
  314. package/src/components/CodeView.tsx +470 -0
  315. package/src/components/CollaborationHeaderControl.css +74 -0
  316. package/src/components/CompactInspectorCard.tsx +190 -0
  317. package/src/components/CompactInspectorShell.tsx +39 -0
  318. package/src/components/ConsolePanel.css +55 -0
  319. package/src/components/ConsolePanel.tsx +270 -0
  320. package/src/components/DefaultEditorLayout.tsx +106 -0
  321. package/src/components/DocumentHeaderStrip.tsx +96 -0
  322. package/src/components/DocumentShelfRail.tsx +45 -0
  323. package/src/components/DocumentThumbnail.tsx +345 -0
  324. package/src/components/EditorLeaseGuard.tsx +372 -0
  325. package/src/components/ErrorBanner.tsx +123 -0
  326. package/src/components/FolderPreviewTile.tsx +159 -0
  327. package/src/components/GameHierarchy.tsx +3842 -0
  328. package/src/components/GameplaySessionTimeline.tsx +295 -0
  329. package/src/components/InspectionProjection.tsx +968 -0
  330. package/src/components/Inspector.tsx +233 -0
  331. package/src/components/InspectorCanvasPreview.tsx +35 -0
  332. package/src/components/InspectorFieldsSection.tsx +290 -0
  333. package/src/components/InspectorObjectPreview.tsx +57 -0
  334. package/src/components/InspectorStoriesSection.tsx +92 -0
  335. package/src/components/InspectorToolSection.tsx +96 -0
  336. package/src/components/InspectorTransformSection.tsx +245 -0
  337. package/src/components/LightExplorerPanel.tsx +433 -0
  338. package/src/components/MediaProperties.tsx +145 -0
  339. package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
  340. package/src/components/Object3DDocumentToolbar.css +100 -0
  341. package/src/components/Object3DDocumentToolbar.tsx +454 -0
  342. package/src/components/Object3DDocumentViewport.tsx +58 -0
  343. package/src/components/ProjectHeader.css +617 -0
  344. package/src/components/ProjectHeader.tsx +328 -0
  345. package/src/components/ProjectLayout.tsx +249 -0
  346. package/src/components/ReactCanvasControls.tsx +284 -0
  347. package/src/components/RootSelectionOverlay.tsx +3162 -0
  348. package/src/components/RootTextEditor.tsx +79 -0
  349. package/src/components/SaveStatus.tsx +70 -0
  350. package/src/components/StageHost.tsx +1954 -0
  351. package/src/components/StageOverlays.tsx +21 -0
  352. package/src/components/StartupErrorScreen.tsx +76 -0
  353. package/src/components/StartupLoadingScreen.tsx +65 -0
  354. package/src/components/StatsOverlay.tsx +78 -0
  355. package/src/components/SurfaceStateOverlay.tsx +24 -0
  356. package/src/components/ToolContributionSurfaces.tsx +65 -0
  357. package/src/components/ToolHost.tsx +370 -0
  358. package/src/components/ToolObject3DPreview.tsx +39 -0
  359. package/src/components/Toolbar.tsx +595 -0
  360. package/src/components/TransientHint.tsx +44 -0
  361. package/src/components/TransportStrip.tsx +174 -0
  362. package/src/components/VersionControlSection.tsx +470 -0
  363. package/src/components/VgaiLogo.css +83 -0
  364. package/src/components/VgaiLogo.tsx +35 -0
  365. package/src/components/ViewportControlsHint.tsx +60 -0
  366. package/src/components/ViewportFurniture.tsx +401 -0
  367. package/src/components/ViewportOverlay.tsx +145 -0
  368. package/src/components/ViewportOverlaysMenu.tsx +153 -0
  369. package/src/components/ViewportShadingMenu.tsx +300 -0
  370. package/src/components/ViewportViewMenu.tsx +101 -0
  371. package/src/components/WorkspaceDocumentSurface.tsx +204 -0
  372. package/src/components/WorkspaceUtilitySurface.tsx +22 -0
  373. package/src/components/WorktreeSwitcher.css +239 -0
  374. package/src/components/WorktreeSwitcher.tsx +890 -0
  375. package/src/components/account-documents.tsx +1162 -0
  376. package/src/components/asset-documents.tsx +812 -0
  377. package/src/components/asset-editor-persistence.ts +216 -0
  378. package/src/components/asset-selection-section.tsx +535 -0
  379. package/src/components/asset-thumbnails.tsx +312 -0
  380. package/src/components/asset-viewers/AudioViewer.tsx +201 -0
  381. package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
  382. package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  383. package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  384. package/src/components/asset-viewers/ImageViewer.tsx +300 -0
  385. package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
  386. package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
  387. package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
  388. package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  389. package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
  390. package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
  391. package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
  392. package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
  393. package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  394. package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
  395. package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  396. package/src/components/asset-viewers/VideoViewer.tsx +101 -0
  397. package/src/components/asset-viewers/shader-source.ts +144 -0
  398. package/src/components/asset-workflow.css +731 -0
  399. package/src/components/board-guides.ts +150 -0
  400. package/src/components/compact-inspector.css +539 -0
  401. package/src/components/core-utilities.tsx +90 -0
  402. package/src/components/editor-notifications.css +40 -0
  403. package/src/components/engine-workspace.css +274 -0
  404. package/src/components/inspector-preview-section.tsx +238 -0
  405. package/src/components/inspector-property-grouping.ts +64 -0
  406. package/src/components/inspector-revert-label.ts +20 -0
  407. package/src/components/inspector-selection.ts +42 -0
  408. package/src/components/inspector-stories-gating.ts +171 -0
  409. package/src/components/inspector-transform-subject.ts +11 -0
  410. package/src/components/inspector-transform.ts +75 -0
  411. package/src/components/kind-documents.tsx +500 -0
  412. package/src/components/palette-action-publisher.tsx +140 -0
  413. package/src/components/primitives/DraftColorInput.tsx +74 -0
  414. package/src/components/product-shell.css +1829 -0
  415. package/src/components/project-tool-documents.tsx +410 -0
  416. package/src/components/scene-documents.tsx +223 -0
  417. package/src/components/stage-keyboard.tsx +37 -0
  418. package/src/components/stage-overlay-set.tsx +107 -0
  419. package/src/components/stage-presence-markers.ts +478 -0
  420. package/src/components/standard-viewport-dressing.ts +417 -0
  421. package/src/components/status-contributions.tsx +403 -0
  422. package/src/components/tool-documents.tsx +309 -0
  423. package/src/components/tool-schema-form.tsx +262 -0
  424. package/src/components/use-after-paint.ts +41 -0
  425. package/src/components/use-project-image-assets.ts +86 -0
  426. package/src/components/viewport-header-controls.css +90 -0
  427. package/src/components/viewport-surface-status.tsx +55 -0
  428. package/src/components/workspace-history.ts +32 -0
  429. package/src/components/workspace-static-panel-registry.tsx +152 -0
  430. package/src/components/workspace-surfaces.css +621 -0
  431. package/src/components/world-documents.tsx +586 -0
  432. package/src/components/world-overlay-gestures.ts +1694 -0
  433. package/src/composite-screenshot.ts +1641 -0
  434. package/src/console-sync.ts +131 -0
  435. package/src/constraint-helper.ts +338 -0
  436. package/src/content-entry-source-registry.ts +184 -0
  437. package/src/coverage/authoring-seam-evidence.ts +300 -0
  438. package/src/coverage/canvas-reveal.ts +192 -0
  439. package/src/coverage/design-time-surfaces.ts +101 -0
  440. package/src/coverage/live-seam-evidence.ts +11 -0
  441. package/src/coverage/ontology-invariants.ts +466 -0
  442. package/src/coverage/session-vitals.ts +501 -0
  443. package/src/coverage/system-seam-evidence.ts +72 -0
  444. package/src/crash-null-boundary.ts +36 -0
  445. package/src/creation-site-edit.ts +1479 -0
  446. package/src/creation-site-registry.ts +160 -0
  447. package/src/delegate-harness-registry.ts +143 -0
  448. package/src/document-context-registry.ts +177 -0
  449. package/src/document-open-registry.ts +200 -0
  450. package/src/document-preview-source.ts +20 -0
  451. package/src/document-renderer-session.ts +138 -0
  452. package/src/editor-api.ts +46 -0
  453. package/src/editor-chrome-capture.ts +139 -0
  454. package/src/editor-commands.ts +164 -0
  455. package/src/editor-console.ts +563 -0
  456. package/src/editor-current-view.ts +82 -0
  457. package/src/editor-document-probe.ts +881 -0
  458. package/src/editor-git-client.ts +115 -0
  459. package/src/editor-host-door.ts +462 -0
  460. package/src/editor-hotkeys.ts +842 -0
  461. package/src/editor-lease-view.ts +39 -0
  462. package/src/editor-lease.ts +415 -0
  463. package/src/editor-mode.ts +19 -0
  464. package/src/editor-notifications.ts +140 -0
  465. package/src/editor-presence.ts +563 -0
  466. package/src/editor-presentation-activity.ts +50 -0
  467. package/src/editor-presentation-notice.ts +42 -0
  468. package/src/editor-runtime.tsx +145 -0
  469. package/src/editor-server-response.ts +86 -0
  470. package/src/editor-session-attribution.ts +75 -0
  471. package/src/editor-session-mode.ts +54 -0
  472. package/src/editor-shell-store.ts +1304 -0
  473. package/src/editor-state-facets.ts +74 -0
  474. package/src/editor-styles.css +33 -0
  475. package/src/editor-view-presentation.ts +810 -0
  476. package/src/editor-viewport.ts +5302 -0
  477. package/src/entity-lod.ts +31 -0
  478. package/src/entity-object.ts +91 -0
  479. package/src/files/file-provider.ts +62 -0
  480. package/src/files/project-files.ts +264 -0
  481. package/src/finders/index.ts +136 -0
  482. package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
  483. package/src/frame/bridge.tsx +1408 -0
  484. package/src/frame/product.ts +77 -0
  485. package/src/gameplay-dom-recording.ts +319 -0
  486. package/src/gameplay-export-state.ts +14 -0
  487. package/src/gameplay-replay.ts +417 -0
  488. package/src/gameplay-session-time.ts +9 -0
  489. package/src/gameplay-sessions.ts +204 -0
  490. package/src/harness-chat-types.ts +253 -0
  491. package/src/hierarchy-component-marks.ts +298 -0
  492. package/src/hierarchy-drop.ts +91 -0
  493. package/src/hierarchy-expansion-state.ts +80 -0
  494. package/src/hierarchy-header-slot.ts +52 -0
  495. package/src/hierarchy-internals.ts +197 -0
  496. package/src/hierarchy-kind-icon.ts +217 -0
  497. package/src/hierarchy-mark-reader.ts +73 -0
  498. package/src/hierarchy-menu-registry.ts +67 -0
  499. package/src/hierarchy-node-rows.ts +307 -0
  500. package/src/hierarchy-panel-view.ts +280 -0
  501. package/src/hierarchy-projection.ts +76 -0
  502. package/src/hierarchy-row-cache.ts +243 -0
  503. package/src/hierarchy-row-model.ts +308 -0
  504. package/src/hierarchy-rows.ts +11 -0
  505. package/src/hierarchy-walk.ts +86 -0
  506. package/src/history/editor-session.ts +25 -0
  507. package/src/history/history-commands.ts +147 -0
  508. package/src/history/history-delegate.ts +163 -0
  509. package/src/history/history-limit-notices.ts +43 -0
  510. package/src/history/history-service.ts +1173 -0
  511. package/src/history/persistence-coordinator.ts +35 -0
  512. package/src/history/project-file-history.ts +386 -0
  513. package/src/history/project-root-history-backends.ts +139 -0
  514. package/src/history/resource-registry.ts +209 -0
  515. package/src/history/snapshot-store.ts +103 -0
  516. package/src/history/source-history-backend.ts +546 -0
  517. package/src/history/types.ts +124 -0
  518. package/src/hmr-registration-group.ts +67 -0
  519. package/src/hmr-stable-react-context.ts +23 -0
  520. package/src/hotkeys.ts +188 -0
  521. package/src/inference-diagnostics.ts +69 -0
  522. package/src/initial-project.ts +80 -0
  523. package/src/inspection/active-subject.ts +578 -0
  524. package/src/inspection/active-surface.ts +142 -0
  525. package/src/inspection/compose.ts +1064 -0
  526. package/src/inspection/display.ts +170 -0
  527. package/src/inspection/document-subject.ts +109 -0
  528. package/src/inspection/game-subject.ts +85 -0
  529. package/src/inspection/model.ts +542 -0
  530. package/src/inspection/null-subject.ts +115 -0
  531. package/src/inspection/serialize.ts +357 -0
  532. package/src/inspection/use-active-inspection.ts +180 -0
  533. package/src/inspector-presentation.ts +201 -0
  534. package/src/inspector-section-registry.ts +221 -0
  535. package/src/instance-source-actions.ts +163 -0
  536. package/src/instanced-presentation.ts +164 -0
  537. package/src/js-heap.ts +71 -0
  538. package/src/key-actions.ts +91 -0
  539. package/src/keymap-presets.ts +401 -0
  540. package/src/layout-policy.ts +31 -0
  541. package/src/learn-links.ts +73 -0
  542. package/src/light-explorer-model.ts +134 -0
  543. package/src/live-canvas-frame.ts +55 -0
  544. package/src/live-document.ts +279 -0
  545. package/src/live-module-source.ts +230 -0
  546. package/src/live-session-registry.ts +220 -0
  547. package/src/live-transition.ts +633 -0
  548. package/src/manifest-project.ts +107 -0
  549. package/src/model-thumbnail.ts +523 -0
  550. package/src/native-selection-style.ts +202 -0
  551. package/src/object3d-document-write-policy.ts +137 -0
  552. package/src/packaged-runtime.ts +108 -0
  553. package/src/palettes/maya.palette.json +57 -0
  554. package/src/palettes/substance.palette.json +57 -0
  555. package/src/pasteboard-module.ts +81 -0
  556. package/src/performance-profiler.ts +367 -0
  557. package/src/performance-sources.ts +69 -0
  558. package/src/play-boot-phase.ts +145 -0
  559. package/src/presentation-surface.ts +248 -0
  560. package/src/project-adapter.ts +1143 -0
  561. package/src/project-asset-refresh.ts +26 -0
  562. package/src/project-declaration-refresh.ts +31 -0
  563. package/src/project-local-state.ts +118 -0
  564. package/src/project-manager.ts +243 -0
  565. package/src/project-module-changes.ts +163 -0
  566. package/src/project-module-split.ts +266 -0
  567. package/src/project-provenance.ts +115 -0
  568. package/src/project-ready.ts +42 -0
  569. package/src/project-session-reset.ts +67 -0
  570. package/src/project-shape.ts +68 -0
  571. package/src/project-tool-discovery.ts +66 -0
  572. package/src/project-tools.ts +107 -0
  573. package/src/project-work-types.ts +149 -0
  574. package/src/projection/three.ts +898 -0
  575. package/src/projection/types.ts +44 -0
  576. package/src/readiness.ts +113 -0
  577. package/src/reflection-probe-helper.ts +142 -0
  578. package/src/reported-play-state.ts +90 -0
  579. package/src/resolve-relative-specifier.ts +33 -0
  580. package/src/scene-document-plan.ts +315 -0
  581. package/src/scene-framing.ts +315 -0
  582. package/src/scene-live-open.ts +210 -0
  583. package/src/scene-view-fog.ts +89 -0
  584. package/src/scoped-game-css.ts +152 -0
  585. package/src/session-orphan-record.ts +193 -0
  586. package/src/session-tombstone.ts +126 -0
  587. package/src/settings/settings-provider.ts +82 -0
  588. package/src/settings-store.ts +345 -0
  589. package/src/shared-view-restore.ts +42 -0
  590. package/src/shell-store-door.ts +45 -0
  591. package/src/source-conflict.ts +122 -0
  592. package/src/spatial-handle-visuals.ts +332 -0
  593. package/src/stage-context.ts +341 -0
  594. package/src/stage-store-registry.ts +63 -0
  595. package/src/stale-chunk-recovery.ts +34 -0
  596. package/src/state-report-deferral.ts +73 -0
  597. package/src/storage/host-files-storage.ts +97 -0
  598. package/src/storage/http-storage.ts +174 -0
  599. package/src/storage/index.ts +75 -0
  600. package/src/storage/mem-storage.ts +149 -0
  601. package/src/storage/path-lock.ts +44 -0
  602. package/src/storage/paths.ts +26 -0
  603. package/src/storage/types.ts +116 -0
  604. package/src/stories/StoryComponentThumbnail.tsx +184 -0
  605. package/src/stories/StoryPreviewMount.tsx +306 -0
  606. package/src/stories/component-content-source.tsx +106 -0
  607. package/src/stories/component-states-source.ts +78 -0
  608. package/src/stories/compose-project-stories.ts +255 -0
  609. package/src/stories/pixi-story-model.ts +30 -0
  610. package/src/stories/prefabs-finder.ts +54 -0
  611. package/src/stories/prefabs-from-stories.ts +182 -0
  612. package/src/stories/project-story-discovery.ts +78 -0
  613. package/src/stories/project-story-regions.ts +24 -0
  614. package/src/stories/story-actions.ts +54 -0
  615. package/src/stories/story-capture-command.ts +124 -0
  616. package/src/stories/story-capture.ts +584 -0
  617. package/src/stories/story-declared-medium.ts +126 -0
  618. package/src/stories/story-discovery.ts +176 -0
  619. package/src/stories/story-document-openers.ts +36 -0
  620. package/src/stories/story-dom-runtime.ts +78 -0
  621. package/src/stories/story-grouping.ts +111 -0
  622. package/src/stories/story-lane.ts +88 -0
  623. package/src/stories/story-mount-turn.ts +27 -0
  624. package/src/stories/story-opener.ts +260 -0
  625. package/src/stories/story-pixi-preview.ts +408 -0
  626. package/src/stories/story-presentation.ts +215 -0
  627. package/src/stories/story-registry.ts +517 -0
  628. package/src/stories/story-three-preview.ts +807 -0
  629. package/src/stories/three-story-model.ts +96 -0
  630. package/src/story-three-preview-runtime.ts +56 -0
  631. package/src/surface-keyboard.ts +112 -0
  632. package/src/surface-state.ts +124 -0
  633. package/src/tab-bootstrap.js +628 -0
  634. package/src/tab-census.ts +198 -0
  635. package/src/tab-lifecycle-client.ts +196 -0
  636. package/src/theme-library.ts +844 -0
  637. package/src/theme-preference.ts +378 -0
  638. package/src/theme.css +3437 -0
  639. package/src/three-viewport/bone-selection-highlight.ts +119 -0
  640. package/src/three-viewport/camera-fit.ts +41 -0
  641. package/src/three-viewport/interactive-renderer.ts +132 -0
  642. package/src/three-viewport/selection-brackets.ts +310 -0
  643. package/src/three-viewport/selection-outline.ts +183 -0
  644. package/src/three-viewport/skeleton-helper.ts +61 -0
  645. package/src/three-viewport/source-color.ts +167 -0
  646. package/src/three-viewport/studio-environment.ts +96 -0
  647. package/src/three-viewport-presentation.ts +23 -0
  648. package/src/tool-contribution-play.ts +74 -0
  649. package/src/tool-loader.ts +1798 -0
  650. package/src/transform-mode-request.ts +57 -0
  651. package/src/transient-hint.ts +78 -0
  652. package/src/trigger-volume-helper.ts +116 -0
  653. package/src/ui-source/adapter-region-includes.ts +241 -0
  654. package/src/ui-source/ensure-import.ts +132 -0
  655. package/src/ui-source/file-region-resolver.ts +302 -0
  656. package/src/ui-source/inspect.ts +775 -0
  657. package/src/ui-source/oid-transform.ts +1845 -0
  658. package/src/ui-source/plan-csf-story.ts +196 -0
  659. package/src/ui-source/plan-extract-component.ts +421 -0
  660. package/src/ui-source/plan-fork-component.ts +621 -0
  661. package/src/ui-source/plan-named-style.ts +95 -0
  662. package/src/ui-source/plan-source-edit.ts +353 -0
  663. package/src/ui-source/r3f-contract-resolver.ts +327 -0
  664. package/src/ui-source/r3f-diagnostic-index.ts +165 -0
  665. package/src/ui-source/r3f-environment-binding.ts +133 -0
  666. package/src/ui-source/r3f-joint-binding.ts +251 -0
  667. package/src/ui-source/r3f-lod-binding.ts +91 -0
  668. package/src/ui-source/r3f-particle-binding.ts +235 -0
  669. package/src/ui-source/r3f-physics-binding.ts +483 -0
  670. package/src/ui-source/r3f-project-contracts.ts +444 -0
  671. package/src/ui-source/relative-import-specifier.ts +34 -0
  672. package/src/ui-source/reparent-guard.ts +544 -0
  673. package/src/ui-source/source-edit-request.ts +97 -0
  674. package/src/ui-source/source-write-backend.ts +618 -0
  675. package/src/ui-source/syntactic-prop-specs.ts +296 -0
  676. package/src/ui-source/tier-source-write-backend.ts +279 -0
  677. package/src/ui-source/ts-ast.ts +171 -0
  678. package/src/ui-source/utility-class-support.ts +169 -0
  679. package/src/ui-source/write-component-default.ts +133 -0
  680. package/src/ui-source/writer.ts +1989 -0
  681. package/src/viewport-activation-timings.ts +840 -0
  682. package/src/viewport-authoring-policy.ts +186 -0
  683. package/src/viewport-controls-hint.ts +57 -0
  684. package/src/viewport-door.ts +237 -0
  685. package/src/viewport-shading-boundary.ts +12 -0
  686. package/src/vite-error-surface.ts +141 -0
  687. package/src/wait-until.ts +37 -0
  688. package/src/workspace-areas.ts +156 -0
  689. package/src/workspace-aux-commands.ts +11 -0
  690. package/src/workspace-available-documents.ts +127 -0
  691. package/src/workspace-core-utilities.ts +31 -0
  692. package/src/workspace-document-ids.ts +59 -0
  693. package/src/workspace-document-registry.ts +612 -0
  694. package/src/workspace-document-restore.ts +145 -0
  695. package/src/workspace-host-commands.ts +141 -0
  696. package/src/workspace-persistence-gate.ts +40 -0
  697. package/src/workspace-play-utilities.ts +44 -0
  698. package/src/workspace-presets.ts +425 -0
  699. package/src/workspace-regions.ts +294 -0
  700. package/src/workspace-state-persistence.ts +536 -0
  701. package/src/workspace-static-panels.ts +73 -0
  702. package/src/workspace-status-registry.ts +121 -0
  703. package/src/workspace-style.ts +239 -0
  704. package/src/workspace-utility-commands.ts +74 -0
  705. package/src/workspace-utility-registry.ts +263 -0
  706. package/src/workspace-viewport-rect.ts +97 -0
  707. package/src/world-adoption.ts +115 -0
  708. package/src/world-document-routing.ts +104 -0
  709. package/vite-plugin-creation-site-write.ts +184 -0
  710. package/vite-plugin-creation-site.ts +80 -0
  711. package/vite-plugin-game-static.ts +303 -0
  712. package/vite-plugin-module-doorways.ts +336 -0
  713. package/vite-plugin-product-contributions.ts +197 -0
  714. package/vite-plugin-project-game-static.ts +125 -0
  715. package/vite-plugin-project-jsx-js.ts +65 -0
  716. package/vite-plugin-project-root-absolute-assets.ts +359 -0
  717. package/vite-plugin-shared-react.ts +420 -0
  718. package/vite-plugin-shared-three.ts +203 -0
  719. package/vite-plugin-ui-oid.ts +2106 -0
@@ -0,0 +1,1064 @@
1
+ /**
2
+ * `composeInspectionSubject` — the ONE pure function that turns the editor's
3
+ * four inspection channels into an {@link InspectionSubject}
4
+ * (`inspection/model.ts`). Design: `docs/ARCHITECTURE-CORE.md` §Editor chrome
5
+ * ("The Inspection Model"); build ledger: `docs/WORK.md` §Inspection Model
6
+ * program, W1.
7
+ *
8
+ * The four channels it wraps, in render order:
9
+ * 1. the adapter's typed `InspectorProvider` descriptors — the loose
10
+ * (ungrouped) grid, then one section per named `group`;
11
+ * 2. `inspector-section-registry.ts`'s matched contributions — opaque custom
12
+ * bodies, in registration order;
13
+ * 3. the shell built-ins — the identity row, the live Preview section,
14
+ * Transform, Stories;
15
+ * 4. the verbs — the visibility eye (lifted OUT of the descriptor grid: the
16
+ * hierarchy already presents visibility as an eye, and the same fact must
17
+ * not reappear as a labelled checkbox) and the Asset Editor jump.
18
+ *
19
+ * It is a FUNCTION, not a store: no cache, no module state, no subscription.
20
+ * Every projection calls it during its own render, so the model is always as
21
+ * fresh as the render that asked for it.
22
+ *
23
+ * With NOTHING selected it composes the same subject shape out of the active
24
+ * adapter's own empty state (`inspection/null-subject.ts`) — W3, so a
25
+ * surface's empty inspector is its own subject and never another surface's.
26
+ *
27
+ * Replacement is resolved HERE and nowhere else, and it is POSITIVE SPACE: a
28
+ * contribution CLAIMS the section id it provides, and a built-in whose id is
29
+ * claimed is simply not composed. A projection sees one list of identified
30
+ * sections and never learns that a built-in existed.
31
+ */
32
+
33
+ import {
34
+ faArrowUpFromBracket,
35
+ faAtom,
36
+ faBookOpen,
37
+ faCamera,
38
+ faCube,
39
+ faDiagramProject,
40
+ faEye,
41
+ faEyeSlash,
42
+ faLightbulb,
43
+ faMusic,
44
+ faPalette,
45
+ faPerson,
46
+ faPuzzlePiece,
47
+ faSliders,
48
+ faUpDownLeftRight,
49
+ type IconDefinition,
50
+ } from '@fortawesome/free-solid-svg-icons';
51
+ import type {
52
+ AuthoringAdapter,
53
+ EditorNode,
54
+ StoryRef,
55
+ Transform,
56
+ TransformChannel,
57
+ TransformEditability,
58
+ } from '@volter/editor-project/adapter';
59
+ import { contentWorldBounds } from '@volter/editor-threejs/viewport/content-bounds';
60
+ import { createElement } from 'react';
61
+ import type * as THREE from 'three';
62
+ import {
63
+ applyAuthoringTransform,
64
+ beginAuthoringTransformEdit,
65
+ commitAuthoringTransformSource,
66
+ endAuthoringTransformEdit,
67
+ removeAuthoringInspectorField,
68
+ removeAuthoringTransform,
69
+ setAuthoringInspectorField,
70
+ } from '../authoring/consumer-actions';
71
+ import { authoringDestination, provenanceForNode } from '../authoring/provenance';
72
+ import { InspectorStoriesSection } from '../components/InspectorStoriesSection';
73
+ import { InspectorTransformSection } from '../components/InspectorTransformSection';
74
+ import {
75
+ InspectorCanvasPreviewBody,
76
+ InspectorComponentPreviewBody,
77
+ InspectorPreviewBody,
78
+ } from '../components/inspector-preview-section';
79
+ import { groupProperties } from '../components/inspector-property-grouping';
80
+ import { rotationDegrees, withTransformChannel } from '../components/inspector-transform';
81
+ import { transformDimensionsFor } from '../components/inspector-transform-subject';
82
+ import {
83
+ type ContentEntry,
84
+ type ContentEntrySource,
85
+ contentEntryForComponent,
86
+ } from '../content-entry-source-registry';
87
+ import {
88
+ type InspectorSectionContribution,
89
+ isSectionsProducer,
90
+ } from '../inspector-section-registry';
91
+ import { describeInstancedPresentation } from '../instanced-presentation';
92
+ import {
93
+ type FieldDescriptor,
94
+ GROUP_SECTION_ORDER,
95
+ groupSectionId,
96
+ type InspectionAction,
97
+ type InspectionFieldIo,
98
+ type InspectionSection,
99
+ type InspectionSubject,
100
+ type InspectionSurfaceKind,
101
+ inspectionAffinityFor,
102
+ PREVIEW_SECTION_ID,
103
+ PREVIEW_SECTION_ORDER,
104
+ PROPERTIES_SECTION_ID,
105
+ PROPERTIES_SECTION_ORDER,
106
+ STORIES_SECTION_ID,
107
+ STORIES_SECTION_ORDER,
108
+ TRANSFORM_SECTION_ID,
109
+ TRANSFORM_SECTION_ORDER,
110
+ } from './model';
111
+ import type { NullInspectionSubject } from './null-subject';
112
+
113
+ /** Above this many properties a group starts collapsed even when the document
114
+ * sets some of them — a 40-field library block is a wall, not a panel. */
115
+ const GROUP_AUTO_OPEN_MAX = 14;
116
+
117
+ /** Property groups are component blocks named by their owner (`RigidBody`,
118
+ * `Material`, `PlayerCharacter`…) — keyword-match the name to a legible
119
+ * icon; the tooltip carries the exact name either way. */
120
+ const GROUP_ICON_RULES: readonly (readonly [RegExp, IconDefinition])[] = [
121
+ [/physic|rigid|collid/i, faAtom],
122
+ [/material/i, faPalette],
123
+ [/light/i, faLightbulb],
124
+ [/camera/i, faCamera],
125
+ [/audio|sound|music/i, faMusic],
126
+ [/player|character|humanoid|person/i, faPerson],
127
+ [/anim|state|machine/i, faDiagramProject],
128
+ ];
129
+
130
+ export function groupIcon(name: string): IconDefinition {
131
+ for (const [pattern, icon] of GROUP_ICON_RULES) {
132
+ if (pattern.test(name)) return icon;
133
+ }
134
+ return faPuzzlePiece;
135
+ }
136
+
137
+ type PreviewRenderer = Extract<InspectionSection['body'], { readonly kind: 'preview' }>['render'];
138
+
139
+ function objectPreviewRenderer(input: {
140
+ readonly object: THREE.Object3D;
141
+ readonly displayName: string;
142
+ readonly previewKey: string;
143
+ readonly actions: readonly InspectionAction[];
144
+ }): PreviewRenderer {
145
+ return (mode) =>
146
+ createElement(InspectorPreviewBody, {
147
+ object: input.object,
148
+ displayName: input.displayName,
149
+ previewKey: input.previewKey,
150
+ actions: input.actions,
151
+ ...(mode ? { mode } : {}),
152
+ });
153
+ }
154
+
155
+ function componentPreviewRenderer(input: {
156
+ readonly picture: { readonly source: ContentEntrySource; readonly entry: ContentEntry };
157
+ readonly previewKey: string;
158
+ readonly actions: readonly InspectionAction[];
159
+ }): PreviewRenderer {
160
+ return (mode) =>
161
+ createElement(InspectorComponentPreviewBody, {
162
+ source: input.picture.source,
163
+ entry: input.picture.entry,
164
+ previewKey: input.previewKey,
165
+ actions: input.actions,
166
+ ...(mode ? { mode } : {}),
167
+ });
168
+ }
169
+
170
+ function canvasPreviewRenderer(input: {
171
+ readonly capture: (size: number) => Promise<string | null>;
172
+ readonly previewKey: string;
173
+ readonly actions: readonly InspectionAction[];
174
+ }): PreviewRenderer {
175
+ return (mode) =>
176
+ createElement(InspectorCanvasPreviewBody, {
177
+ capture: input.capture,
178
+ previewKey: input.previewKey,
179
+ actions: input.actions,
180
+ ...(mode ? { mode } : {}),
181
+ });
182
+ }
183
+
184
+ function selectionPreviewRenderer(input: {
185
+ readonly object: THREE.Object3D | null;
186
+ readonly canvasCapture: ((size: number) => Promise<string | null>) | null | undefined;
187
+ readonly picture: { readonly source: ContentEntrySource; readonly entry: ContentEntry } | null;
188
+ readonly displayName: string;
189
+ readonly previewKey: string;
190
+ readonly actions: readonly InspectionAction[];
191
+ }): PreviewRenderer | null {
192
+ // A camera's preview is its view through the scene, owned by the viewport.
193
+ // An isolated model snapshot of the camera itself contains no image.
194
+ if ((input.object as THREE.Camera | null)?.isCamera) return null;
195
+ if (input.object) {
196
+ return objectPreviewRenderer({
197
+ object: input.object,
198
+ displayName: input.displayName,
199
+ previewKey: input.previewKey,
200
+ actions: input.actions,
201
+ });
202
+ }
203
+ if (input.canvasCapture) {
204
+ return canvasPreviewRenderer({
205
+ capture: input.canvasCapture,
206
+ previewKey: input.previewKey,
207
+ actions: input.actions,
208
+ });
209
+ }
210
+ if (input.picture) {
211
+ return componentPreviewRenderer({
212
+ picture: input.picture,
213
+ previewKey: input.previewKey,
214
+ actions: input.actions,
215
+ });
216
+ }
217
+ return null;
218
+ }
219
+
220
+ /** `transform.position` -> `position`. The three paths the Transform section's
221
+ * write half publishes are the only ones its io is ever handed. */
222
+ function transformChannelOf(path: string): TransformChannel {
223
+ const channel = path.slice('transform.'.length);
224
+ if (channel !== 'position' && channel !== 'rotation' && channel !== 'scale') {
225
+ throw new Error(`The Transform section has no ${JSON.stringify(path)}.`);
226
+ }
227
+ return channel;
228
+ }
229
+
230
+ const IDENTITY_TRANSFORM: Transform = {
231
+ position: [0, 0, 0],
232
+ rotation: [0, 0, 0, 1],
233
+ scale: [1, 1, 1],
234
+ };
235
+
236
+ /** The Stories section's content for this selection, or `null` when the
237
+ * selection has no stories concept at all. Resolved by the caller because
238
+ * the gating is an adapter-TOPOLOGY question (which child adapter owns this
239
+ * row) rather than a composition one. */
240
+ export interface ComposeStoriesInput {
241
+ readonly stories: readonly StoryRef[];
242
+ readonly activeStoryId: string;
243
+ readonly onApplyStory: (storyId: string | null) => void;
244
+ /** B3 — a catalog component node also gets the Isolate toggle. */
245
+ readonly isolation: { readonly isolated: boolean; readonly toggle: () => void } | null;
246
+ /** The provider's own display vocabulary (`StoriesProvider.title`) — an
247
+ * ingested game's contract scenes say "Scenes". Absent ⇒ "Stories". */
248
+ readonly title?: string;
249
+ /** The provider's own reason its list could not be read
250
+ * (`StoriesProvider.unavailable`), or `null` when it ran. */
251
+ readonly unavailableReason?: string | null;
252
+ }
253
+
254
+ export interface ComposeInspectionInput {
255
+ readonly adapter: AuthoringAdapter;
256
+ /** The selected node, or `null` for the NO-SELECTION subject — which is
257
+ * the active surface's own empty state, never a shared blank. */
258
+ readonly nodeId: string | null;
259
+ /** The resolved hierarchy node, or `null` when the active adapter cannot
260
+ * resolve the selected id. */
261
+ readonly node: EditorNode | null;
262
+ /** The surface this subject belongs to — decides its presentation
263
+ * affinity, and nothing else here. Absent in a bounded host, which names
264
+ * no surface and offers no preference. */
265
+ readonly surface?: InspectionSurfaceKind | undefined;
266
+ /** The active adapter's answer to `describeSubject(null)`
267
+ * (`inspection/null-subject.ts`), resolved by the caller the same way
268
+ * `contributions` is. Read only when `nodeId` is `null`; `null` there
269
+ * means this surface honestly has no empty-state subject. */
270
+ readonly nullSubject?: NullInspectionSubject | null;
271
+ /** `matchedInspectorSections(node, adapter)`, in registration order. */
272
+ readonly contributions: readonly InspectorSectionContribution[];
273
+ /** How many OTHER things are selected alongside {@link nodeId} — see
274
+ * {@link InspectionSubject.alsoSelected}. The caller resolves it, because
275
+ * the selection lives on the store and this function is pure over one
276
+ * node. */
277
+ readonly alsoSelected?: number | undefined;
278
+ /**
279
+ * The live `Object3D` this node can be previewed FROM, or `null` when there
280
+ * is none. Present ⇒ the subject gets a Preview section; absent ⇒ it simply
281
+ * has no such section. It is the object rather than an id to look one up by,
282
+ * because "previewable" and "renders honestly" have to be the same fact: a
283
+ * part inside an asset document is a real object that is NOT in the shell's
284
+ * object map, and an id-keyed preview could only have shown a "no longer
285
+ * available" alert for a thing that is on screen.
286
+ */
287
+ readonly previewObject: THREE.Object3D | null;
288
+ /** Live native Canvas/Pixi subtree capture. The mounted surface owns the
289
+ * renderer, so the caller supplies the capture door when one exists. */
290
+ readonly canvasPreview?: ((size: number) => Promise<string | null>) | null;
291
+ /** This node has an Asset Lab document to jump to. */
292
+ readonly assetDocument: { readonly open: () => void } | null;
293
+ readonly stories: ComposeStoriesInput | null;
294
+ /** Notify the shell that the adapter's truth changed (`notifyIngestEdit`). */
295
+ readonly onEdit: () => void;
296
+ /** See {@link OwnedInspectorRail} — present only while a document whose
297
+ * package owns its Properties rail is active. */
298
+ readonly rail?: OwnedInspectorRail | undefined;
299
+ }
300
+
301
+ /**
302
+ * A contribution's identified sections — the one place an
303
+ * `InspectorSectionContribution` becomes `InspectionSection`s.
304
+ *
305
+ * A single-section registration yields exactly one, with an opaque custom
306
+ * body. A PRODUCER (`InspectorSectionsProducer`) yields whatever the node
307
+ * has: the react/DOM adapter's descriptor groups are its Layout / Style /
308
+ * Position / Spacing blocks, and only the node can say how many there are.
309
+ * Either way what comes out is model data — identity, order, icon, body —
310
+ * and the projections cannot tell which shape produced it.
311
+ */
312
+ export function contributionSections(
313
+ contribution: InspectorSectionContribution,
314
+ adapter: AuthoringAdapter,
315
+ nodeId: string | null,
316
+ node: EditorNode | null,
317
+ ): readonly InspectionSection[] {
318
+ if (isSectionsProducer(contribution)) return contribution.sections(node, adapter);
319
+ const Section = contribution.Section;
320
+ return [
321
+ {
322
+ id: contribution.id,
323
+ title: contribution.title,
324
+ // A registration's icon may be a FUNCTION of the subject
325
+ // (`InspectorSectionIcon`) — Blender's Object Data tab draws the mark
326
+ // its object type earns. Resolved here, once per composition, so every
327
+ // projection downstream still receives a plain `IconDefinition`.
328
+ icon:
329
+ typeof contribution.icon === 'function'
330
+ ? contribution.icon(node, adapter)
331
+ : contribution.icon,
332
+ order: contribution.order,
333
+ ...(contribution.railGroup === undefined ? {} : { railGroup: contribution.railGroup }),
334
+ ...(contribution.railDefault === undefined ? {} : { railDefault: contribution.railDefault }),
335
+ body: { kind: 'custom', render: () => createElement(Section, { adapter, nodeId }) },
336
+ },
337
+ ];
338
+ }
339
+
340
+ /**
341
+ * The positive-space replacement protocol, whole: a contribution claims the
342
+ * section ids it PROVIDES, and a built-in whose id is claimed is never
343
+ * composed. A section id is unique within a subject (the compact card keys
344
+ * its tabs by it), so the FIRST claim of an id wins — registration order, the
345
+ * same order everything else here reads.
346
+ *
347
+ * A contribution that loses EVERY claim renders nothing.
348
+ */
349
+ function expandContributions(
350
+ contributions: readonly InspectorSectionContribution[],
351
+ adapter: AuthoringAdapter,
352
+ nodeId: string | null,
353
+ node: EditorNode | null,
354
+ ): {
355
+ sections: InspectionSection[];
356
+ claimed: ReadonlySet<string>;
357
+ /** Which package each contributed section came out of, by section id —
358
+ * what {@link ComposeInspectionInput.rail} filters on. */
359
+ owners: ReadonlyMap<string, string | undefined>;
360
+ } {
361
+ const claimed = new Set<string>();
362
+ const owners = new Map<string, string | undefined>();
363
+ const sections: InspectionSection[] = [];
364
+ for (const contribution of contributions) {
365
+ const produced = contributionSections(contribution, adapter, nodeId, node).filter(
366
+ (section) => !claimed.has(section.id),
367
+ );
368
+ // A producer may also stand a built-in down without replacing it with a
369
+ // section of its own (the descriptor channel of a node with no loose
370
+ // fields).
371
+ const declared = (isSectionsProducer(contribution) ? (contribution.claims ?? []) : []).filter(
372
+ (id) => !claimed.has(id),
373
+ );
374
+ if (produced.length === 0 && declared.length === 0) continue;
375
+ for (const section of produced) {
376
+ claimed.add(section.id);
377
+ owners.set(section.id, contribution.owner);
378
+ }
379
+ for (const id of declared) claimed.add(id);
380
+ sections.push(...produced);
381
+ }
382
+ return { sections, claimed, owners };
383
+ }
384
+
385
+ /**
386
+ * A DOCUMENT'S PACKAGE OWNS ITS PROPERTIES RAIL (owner ruling; WORK.md
387
+ * §Blender in the tab is Blender, "Inspection parity", I2 decision 1).
388
+ *
389
+ * `owner` is the package whose `workspace.document` contribution mounts the
390
+ * ACTIVE document and declared `export const inspectorRail = 'owned'`;
391
+ * `builtins` is what that document listed in `export const inspectorBuiltins`.
392
+ * Absent — the ordinary case — and composition is exactly as it was.
393
+ */
394
+ export interface OwnedInspectorRail {
395
+ readonly owner: string;
396
+ readonly builtins: readonly string[];
397
+ }
398
+
399
+ /**
400
+ * Keep only what an OWNED rail may show: this package's own contributed
401
+ * sections, plus the built-ins the document named by id.
402
+ *
403
+ * WHY A FILTER AND NOT A BRANCH THROUGH THE WHOLE COMPOSER. Every built-in
404
+ * below is composed from live inputs — a preview capture, the transform
405
+ * channel's editability, the descriptor grid's io — and the shell's other
406
+ * doors resolve field paths against exactly those composed sections
407
+ * (`inspection/active-subject.ts`). Composing them and then standing them
408
+ * down keeps one code path for what a section IS, and makes the ownership
409
+ * rule one readable predicate instead of five `if (!rail)` guards. It is also
410
+ * the SMALLEST change: the rule is stated once, here.
411
+ */
412
+ function applyOwnedRail(
413
+ sections: readonly InspectionSection[],
414
+ owners: ReadonlyMap<string, string | undefined>,
415
+ rail: OwnedInspectorRail | undefined,
416
+ ): InspectionSection[] {
417
+ if (rail === undefined) return [...sections];
418
+ return sections.filter(
419
+ (section) => owners.get(section.id) === rail.owner || rail.builtins.includes(section.id),
420
+ );
421
+ }
422
+
423
+ export interface ComposeNullInspectionInput {
424
+ /** The surface's own answer to `describeSubject(null)` — for an asset
425
+ * document that is the DOCUMENT itself
426
+ * (`inspection/document-subject.ts`). */
427
+ readonly nullSubject: NullInspectionSubject | null;
428
+ readonly surface?: InspectionSurfaceKind | undefined;
429
+ /** Contributions matching the empty selection, and the adapter they render
430
+ * against. Both are absent for a subject with no adapter in play — a
431
+ * document with no authoring of its own describes itself — and
432
+ * contributions without an adapter to render them are dropped rather than
433
+ * half-composed. */
434
+ readonly contributions?: readonly InspectorSectionContribution[];
435
+ readonly adapter?: AuthoringAdapter;
436
+ /** See {@link OwnedInspectorRail}. */
437
+ readonly rail?: OwnedInspectorRail | undefined;
438
+ }
439
+
440
+ /**
441
+ * The NO-SELECTION subject: the surface's own empty state
442
+ * (`inspection/null-subject.ts`) plus whatever contributions match the null
443
+ * selection, in one identified section list. There is no descriptor channel,
444
+ * no transform and no preview — there is no node to have any of them — so
445
+ * this branch composes exactly what the surface itself offered and nothing
446
+ * that belongs to another surface.
447
+ *
448
+ * It DOES compose an identity row when the surface named its subject's type
449
+ * (`kindLabel`): an asset document is a thing with a name, a type and a
450
+ * provenance, and both projections render that row exactly as they render a
451
+ * selection's. A three scene's empty state names no type and gets its quiet
452
+ * headline instead. The row is read-only — nothing here renames a document.
453
+ */
454
+ export function composeNullInspectionSubject(input: ComposeNullInspectionInput): InspectionSubject {
455
+ const { adapter, nullSubject } = input;
456
+ const {
457
+ sections: contributed,
458
+ claimed,
459
+ owners,
460
+ } = expandContributions(
461
+ adapter ? (input.contributions ?? []) : [],
462
+ // Only reachable with an adapter (`expandContributions` is handed an
463
+ // empty list otherwise), so the cast never dereferences undefined.
464
+ adapter as AuthoringAdapter,
465
+ null,
466
+ null,
467
+ );
468
+ // The claim protocol is the SAME on both branches. A surface's own
469
+ // no-selection section is a built-in like any other: a contribution
470
+ // claiming `environment` REPLACES the scene's environment block, it does
471
+ // not appear beside it. (W4 — the selected path filtered on `claimed` from
472
+ // W2; this one did not, so the first contribution to claim a null-subject
473
+ // id would have duplicated the section instead of replacing it.)
474
+ const sections = applyOwnedRail(
475
+ [
476
+ ...(nullSubject?.sections ?? []).filter((section) => !claimed.has(section.id)),
477
+ ...contributed,
478
+ ].sort((a, b) => a.order - b.order),
479
+ owners,
480
+ input.rail,
481
+ );
482
+ return {
483
+ // The fallback identity is for a surface that named no empty-state subject
484
+ // and yet HAS an inspector: several contributions match on the active
485
+ // DOCUMENT rather than on a node (a story's arg knobs, the 3D board's
486
+ // Exhibit block), so "nothing selected, and here is the document's own block" is
487
+ // a real thing a human looks at. When there is no such block either, the
488
+ // box unmounts and the wire says so (`inspection/active-subject.ts`
489
+ // answers `NO_INSPECTION`) — this object is then composed and discarded,
490
+ // never shown and never serialized.
491
+ id: nullSubject?.id ?? 'nothing-selected',
492
+ title: nullSubject?.title ?? 'Nothing selected',
493
+ identity: nullSubject?.kindLabel
494
+ ? {
495
+ rename: { readOnly: true, set: () => undefined },
496
+ kindLabel: nullSubject.kindLabel,
497
+ ...(nullSubject.note ? { note: nullSubject.note } : {}),
498
+ }
499
+ : null,
500
+ ...(nullSubject?.hint ? { hint: nullSubject.hint } : {}),
501
+ presentation: { preferred: inspectionAffinityFor(input.surface) },
502
+ quickActions: nullSubject?.quickActions ?? [],
503
+ related: [],
504
+ sections,
505
+ };
506
+ }
507
+
508
+ /**
509
+ * Give a CONTRIBUTED custom body the same `editable` the generic body it
510
+ * REPLACED would have carried.
511
+ *
512
+ * A contribution that claims `properties` / `group:<id>` is saying "I draw
513
+ * these descriptors with my own widgets" — not "these descriptors are no
514
+ * longer writable". But `editable` is what `inspection/active-subject.ts`'s
515
+ * `setActiveInspectionField` resolves a path against, so until this existed,
516
+ * claiming a channel silently closed the only control-API write door onto it.
517
+ * Measured on an ingested game's DOM HUD: every style and prop field answered
518
+ * "The active Inspector has no field at …", because
519
+ * `@vgai/dom/react-inspector-section.tsx` claims `PROPERTIES_SECTION_ID` and
520
+ * renders custom bodies — so the lane the editor's panels could author was
521
+ * unreachable to `vgai eval`'s `editor.setField`, the one door an agent has.
522
+ *
523
+ * The `io` handed in is the SAME one the generic bodies use and the SAME
524
+ * `adapter.inspector.set` the contributed widgets call, so this adds a door,
525
+ * never a second write path. A body that already declares its own `editable`
526
+ * (the Transform section) is left exactly as it is, and a contributed section
527
+ * with an id of its own owns no descriptors and gets nothing.
528
+ */
529
+ function withDescriptorEditable(
530
+ section: InspectionSection,
531
+ io: InspectionFieldIo,
532
+ fieldsById: ReadonlyMap<string, readonly FieldDescriptor[]>,
533
+ ): InspectionSection {
534
+ if (section.body.kind !== 'custom' || section.body.editable) return section;
535
+ const fields = fieldsById.get(section.id);
536
+ if (!fields || fields.length === 0) return section;
537
+ return { ...section, body: { ...section.body, editable: { fields, io } } };
538
+ }
539
+
540
+ export function composeInspectionSubject(input: ComposeInspectionInput): InspectionSubject {
541
+ const { adapter, nodeId, node, contributions, onEdit } = input;
542
+ if (nodeId === null) {
543
+ return composeNullInspectionSubject({
544
+ nullSubject: input.nullSubject ?? null,
545
+ surface: input.surface,
546
+ contributions,
547
+ adapter,
548
+ rail: input.rail,
549
+ });
550
+ }
551
+
552
+ const {
553
+ sections: contributed,
554
+ claimed,
555
+ owners,
556
+ } = expandContributions(contributions, adapter, nodeId, node);
557
+
558
+ const io: InspectionFieldIo = {
559
+ get: (path: string) => adapter.inspector?.get(nodeId, path),
560
+ // Present only when the adapter can show a value without writing it.
561
+ ...(adapter.inspector?.preview
562
+ ? {
563
+ preview: (path: string, value: unknown) => {
564
+ adapter.inspector?.preview?.(nodeId, path, value);
565
+ },
566
+ }
567
+ : {}),
568
+ set: (path: string, value: unknown) => {
569
+ if (!adapter.inspector) return undefined;
570
+ const result = setAuthoringInspectorField(adapter, nodeId, path, value);
571
+ onEdit();
572
+ return result;
573
+ },
574
+ /** Revert-to-default: drop the document's override and let whatever
575
+ * governs the property in its absence take over (Unity's revert arrow).
576
+ * The owning provider's own ack is passed straight through, exactly as
577
+ * `set` does — a removal moves bytes and answers for them. Absent when the
578
+ * adapter declares no removal door, so the door above it can refuse by
579
+ * name rather than write a value that leaves the prop in the file. */
580
+ ...(adapter.inspector?.remove
581
+ ? {
582
+ remove: (path: string) => {
583
+ const result = removeAuthoringInspectorField(adapter, nodeId, path);
584
+ onEdit();
585
+ return result;
586
+ },
587
+ }
588
+ : {}),
589
+ };
590
+
591
+ // The full property set is DRIVEN BY the adapter's InspectorProvider — a
592
+ // generic descriptor list, not a hardcoded panel. 'name' is the identity
593
+ // row's field; everything else is a section body.
594
+ const declared = adapter.inspector?.properties(nodeId) ?? [];
595
+ const nameProperty = declared.find((p) => p.path === 'name');
596
+ const { ungrouped, groups } = groupProperties(declared.filter((p) => p.path !== 'name'));
597
+
598
+ // The hierarchy already presents visibility as the EYE toggle — the same
599
+ // fact must not reappear here as a labelled "Visible" checkbox (owner,
600
+ // 2026-08-06). Lift the UNGROUPED `visible` boolean out of the grid and
601
+ // make it a subject verb; a GROUPED `visible` (a component's own prop)
602
+ // stays in its group untouched.
603
+ const visibleField = ungrouped.find((p) => p.path === 'visible' && p.type === 'boolean');
604
+ const looseFields = visibleField ? ungrouped.filter((p) => p !== visibleField) : ungrouped;
605
+ /** What the SUBJECT itself exposes to a field write — see the `editable`
606
+ * spread at the end of this function for why `name` is in it. */
607
+ const subjectEditableFields: FieldDescriptor[] = [
608
+ ...(nameProperty && nameProperty.readonly !== true ? [nameProperty] : []),
609
+ ...(visibleField ? [visibleField] : []),
610
+ ];
611
+
612
+ const quickActions: InspectionAction[] = [];
613
+ if (visibleField) {
614
+ // Unset reads as visible — `Object3D.visible` defaults true.
615
+ const isVisible = io.get(visibleField.path) !== false;
616
+ const title = isVisible ? 'Visible — click to hide' : 'Hidden — click to show';
617
+ quickActions.push({
618
+ id: 'inspector-visibility-toggle',
619
+ title,
620
+ icon: isVisible ? faEye : faEyeSlash,
621
+ placement: 'identity',
622
+ pressed: !isVisible,
623
+ disabled: visibleField.readonly === true,
624
+ run: async () => {
625
+ await io.set(visibleField.path, !isVisible);
626
+ },
627
+ });
628
+ }
629
+ if (input.assetDocument) {
630
+ quickActions.push({
631
+ id: 'open-asset-editor',
632
+ title: 'Open in Asset Editor',
633
+ label: 'Open in Asset Editor',
634
+ placement: 'preview',
635
+ run: input.assetDocument.open,
636
+ });
637
+ }
638
+ // THERE IS NO MODEL DRILL FROM A PREFAB'S MESH (2026-09-19, M1). Both quick
639
+ // actions that stood here resolved a `geometry={build()}` prop to a
640
+ // TypeScript model module and opened or wrote one. A model is a `.blend`
641
+ // now (ARCHITECTURE-CORE §The project model, "A model is Blender data"), a
642
+ // prefab uses the glTF exported from one, and nothing records WHICH `.blend`
643
+ // produced a given glTF — so there is no `.blend`-targeted drill to replace
644
+ // them with, only a design that does not exist yet. Recorded in WORK.md.
645
+ const sourceCommit = adapter.transforms?.sourceCommit;
646
+ const sourceCommitAvailability = sourceCommit?.availability(nodeId);
647
+ if (sourceCommitAvailability?.available) {
648
+ quickActions.push({
649
+ id: 'commit-live-transform-source',
650
+ title: 'Commit this live transform to its authored source literal',
651
+ label: 'Commit transform to source',
652
+ icon: faArrowUpFromBracket,
653
+ placement: 'identity',
654
+ run: async () => {
655
+ await commitAuthoringTransformSource(adapter, nodeId);
656
+ onEdit();
657
+ },
658
+ });
659
+ }
660
+
661
+ // Which DESCRIPTORS each replaceable built-in section id owns. A
662
+ // contribution that CLAIMS one of these ids draws its own chrome over the
663
+ // very same descriptors, so this is what it must stay writable through —
664
+ // see {@link withDescriptorEditable}.
665
+ const descriptorFieldsById = new Map<string, readonly FieldDescriptor[]>();
666
+ descriptorFieldsById.set(PROPERTIES_SECTION_ID, looseFields);
667
+ for (const group of groups) {
668
+ descriptorFieldsById.set(groupSectionId(group.groupId), group.properties);
669
+ }
670
+
671
+ const sections: InspectionSection[] = [];
672
+ const creationSite = adapter.truth?.resolve(nodeId, 'position').site ?? null;
673
+
674
+ // The live PREVIEW — an ordinary section, first in the order, and present
675
+ // exactly when there is a real native visual authority behind it. A Three
676
+ // selection hands us its live Object3D. A Canvas selection hands us a
677
+ // renderer-backed capture of its live display subtree; a story-backed
678
+ // component remains the fallback where a mounted surface cannot expose a
679
+ // renderer. The
680
+ // `preview`-placed verbs (the Asset Editor jump) render inside either box,
681
+ // so they read the same in the card and the column.
682
+ const previewObject = input.previewObject;
683
+ // The COMPONENT's own picture, from whichever content source admits it
684
+ // (`content-entry-source-registry.ts`) — the fallback where a mounted canvas
685
+ // surface cannot expose a renderer. The shell asks about the component this
686
+ // node IS; what the picture is made of is the source's business.
687
+ //
688
+ // NARROWED 2026-09-18 (unit 11, edge 8): this used to resolve the node's
689
+ // ACTIVE story id against the project story registry, which is a registry
690
+ // question the host may not ask. The component's registered preview and its
691
+ // active story are the same picture whenever the active one is the component's
692
+ // default, which is what `StoriesProvider.active` reports for an unswitched
693
+ // node; a node switched to a non-default variant now shows the component's
694
+ // registered preview instead. Restoring the variant needs the CANVAS surface
695
+ // package that owns this branch (edge 10), not a second door here.
696
+ const componentName = node?.typeLabel ?? node?.label ?? nodeId;
697
+ const componentPicture =
698
+ input.surface === 'canvas' && previewObject === null && input.stories
699
+ ? contentEntryForComponent({
700
+ name: componentName,
701
+ path: creationSite?.anchored === true ? creationSite.file : '',
702
+ surface: 'canvas',
703
+ })
704
+ : null;
705
+ const previewActions = quickActions.filter((action) => action.placement === 'preview');
706
+ const previewRender = selectionPreviewRenderer({
707
+ object: previewObject,
708
+ canvasCapture: input.canvasPreview,
709
+ picture: componentPicture,
710
+ displayName: node?.label ?? nodeId,
711
+ previewKey: nodeId,
712
+ actions: previewActions,
713
+ });
714
+ if (!claimed.has(PREVIEW_SECTION_ID) && previewRender !== null) {
715
+ sections.push({
716
+ id: PREVIEW_SECTION_ID,
717
+ title: 'Preview',
718
+ icon: faCube,
719
+ order: PREVIEW_SECTION_ORDER,
720
+ body: {
721
+ kind: 'preview',
722
+ render: previewRender,
723
+ },
724
+ });
725
+ }
726
+
727
+ const transformDimensions = transformDimensionsFor(adapter, nodeId);
728
+ if (!claimed.has(TRANSFORM_SECTION_ID) && transformDimensions !== null) {
729
+ const transform = adapter.transforms?.get(nodeId) ?? IDENTITY_TRANSFORM;
730
+ // Measured on the ONE node the reader selected, which is what pays for the
731
+ // bounds walk `describeInstancedPresentation` needs (the hierarchy row's
732
+ // detail deliberately asks the cheap question instead). `contentWorldBounds`
733
+ // resolves instance matrices live — three's own cached object box would
734
+ // answer for whatever pose was first asked about.
735
+ const instanced = previewObject
736
+ ? describeInstancedPresentation(previewObject, contentWorldBounds(previewObject))
737
+ : null;
738
+ const channelEditability = Object.fromEntries(
739
+ (['position', 'rotation', 'scale'] as const).map((channel) => [
740
+ channel,
741
+ adapter.transforms?.editability?.(nodeId, channel) ?? {
742
+ writable: adapter.capabilities.transform,
743
+ },
744
+ ]),
745
+ ) as Record<TransformChannel, TransformEditability>;
746
+ // The section's values in the units its ROWS use — degrees for rotation —
747
+ // spelled once so the read channel, the writable field descriptors and the
748
+ // write's own round trip can never quote three different numbers.
749
+ const transformData: Record<TransformChannel, [number, number, number]> = {
750
+ position: [...transform.position],
751
+ rotation: rotationDegrees(transform.rotation),
752
+ scale: [...transform.scale],
753
+ };
754
+ sections.push({
755
+ id: TRANSFORM_SECTION_ID,
756
+ title: 'Transform',
757
+ icon: faUpDownLeftRight,
758
+ order: TRANSFORM_SECTION_ORDER,
759
+ body: {
760
+ kind: 'custom',
761
+ // The section's own values, for the readers that cannot render one
762
+ // (`inspection/serialize.ts` — P18). Same `transform` the body below
763
+ // is handed, converted the same way its rows convert it: the rotation
764
+ // rows DISPLAY Euler XYZ degrees (`rotationDegrees`), so degrees is
765
+ // what the wire carries — a quaternion here would be a second answer
766
+ // to the question the panel already answers.
767
+ data: {
768
+ ...transformData,
769
+ // The same per-channel verdict the rendered rows lock and explain
770
+ // themselves with. It belongs on the wire for the reason the values
771
+ // above do: a reader that cannot render the section still has to be
772
+ // able to ask "can I move this, and if not why not" — and for an
773
+ // ingest mount that answer IS the product surface (the creation-site
774
+ // gate's reason, verbatim).
775
+ editability: channelEditability,
776
+ // The same two facts the human is shown — the row's unit count and
777
+ // the world-anchored sentence — so an agent reading this section
778
+ // cannot conclude the transform means something the panel is warning
779
+ // it does not. Absent entirely for a node that is not an instanced
780
+ // draw, which is nearly every node.
781
+ ...(instanced ? { instanced } : {}),
782
+ },
783
+ // The WRITE half of the three values above, through the same
784
+ // begin/apply/end `onCommit` performs. An agent could read this
785
+ // section and the sentence explaining whether it may move — and had no
786
+ // way to move it; for an ingest root, whose only authoring surface is
787
+ // the editor, that closed the lane entirely.
788
+ editable: {
789
+ fields: (['position', 'rotation', 'scale'] as const).map((channel) => {
790
+ const readonly =
791
+ !adapter.capabilities.transform || !channelEditability[channel].writable;
792
+ return {
793
+ path: `transform.${channel}`,
794
+ label: channel[0]!.toUpperCase() + channel.slice(1),
795
+ type: 'vec3' as const,
796
+ value: transformData[channel],
797
+ readonly,
798
+ // The SAME two declarations a property row carries, for the
799
+ // channel this section owns instead: the adapter's `removable`,
800
+ // and the io below saying the lane has a door at all. Both are
801
+ // read, neither inferred — `remove-inspection-field` refuses by
802
+ // name when either is missing rather than writing a value that
803
+ // would leave the attribute in the file.
804
+ //
805
+ // HOW MUCH `removable` CLAIMS IS THE ADAPTER'S TO SAY, and it is
806
+ // not the same on every lane — see `TransformEditability`
807
+ // (`@volter/editor-project/adapter/authoring`), which apportions it: a lane
808
+ // holding its own source text answers both halves (the callsite
809
+ // carries this channel AND there is a door), while one that
810
+ // holds only an oid → file:line index answers for the DOOR, and
811
+ // the removal's own ack reports whether a byte actually left. So
812
+ // `resettable` here means "this lane will attempt the drop and
813
+ // answer honestly for it", never "a byte is guaranteed to move".
814
+ ...(!readonly &&
815
+ adapter.transforms?.remove &&
816
+ channelEditability[channel].removable === true
817
+ ? { resettable: true }
818
+ : {}),
819
+ };
820
+ }),
821
+ io: {
822
+ get: (path) => transformData[transformChannelOf(path)],
823
+ // `endEdit` closes the gesture AND answers for whatever write it
824
+ // triggered (`TransformProvider.endEdit`), so the gesture's own ack
825
+ // is what this io returns — never a destination read off the
826
+ // surface afterwards.
827
+ set: (path, value) => {
828
+ if (!adapter.transforms) return undefined;
829
+ const next = withTransformChannel(
830
+ transform,
831
+ transformChannelOf(path),
832
+ value as number[],
833
+ );
834
+ beginAuthoringTransformEdit(adapter, nodeId);
835
+ applyAuthoringTransform(adapter, nodeId, next);
836
+ const ack = endAuthoringTransformEdit(adapter, nodeId);
837
+ onEdit();
838
+ return ack;
839
+ },
840
+ // Revert-to-default for a transform channel — the ONE door that
841
+ // can express byte-ABSENCE, because `set` above can only write
842
+ // three numbers. Present exactly when the adapter declares one, so
843
+ // the door above can refuse `REMOVAL_UNAVAILABLE` by name instead
844
+ // of a value write that pretends to be a revert; the provider's
845
+ // own ack is passed straight through, exactly as `set` does.
846
+ ...(adapter.transforms?.remove
847
+ ? {
848
+ remove: (path: string) => {
849
+ const ack = removeAuthoringTransform(adapter, nodeId, transformChannelOf(path));
850
+ onEdit();
851
+ return ack;
852
+ },
853
+ }
854
+ : {}),
855
+ },
856
+ },
857
+ render: () =>
858
+ createElement(InspectorTransformSection, {
859
+ dimensions: transformDimensions,
860
+ transform,
861
+ readOnly: !adapter.capabilities.transform,
862
+ channelEditability,
863
+ ...(instanced?.note ? { note: instanced.note } : {}),
864
+ // The section's gesture IS the adapter's: one `beginEdit` when
865
+ // the hand (or the caret) takes the field, live `apply` per
866
+ // scrub move, and the ONE `endEdit` write on release/Enter — the
867
+ // same shape the gizmo drives, so a scrub or a typed number is
868
+ // one undo step and one remount.
869
+ onBegin: () => {
870
+ beginAuthoringTransformEdit(adapter, nodeId);
871
+ },
872
+ onPreview: (next: Transform) => {
873
+ applyAuthoringTransform(adapter, nodeId, next);
874
+ },
875
+ onCommit: (next: Transform) => {
876
+ applyAuthoringTransform(adapter, nodeId, next);
877
+ endAuthoringTransformEdit(adapter, nodeId);
878
+ onEdit();
879
+ },
880
+ }),
881
+ },
882
+ });
883
+ }
884
+
885
+ // `properties` names the whole DESCRIPTOR CHANNEL — the loose grid plus the
886
+ // `group:<id>` blocks its named groups produce (see the constant's own
887
+ // doc) — so claiming it stands the channel down entirely.
888
+ if (!claimed.has(PROPERTIES_SECTION_ID)) {
889
+ // The loose grid is headed like every other section — "Properties", with
890
+ // its own glyph, in every projection. `visible` lives on the identity row
891
+ // as the eye, so it never earns this section by itself.
892
+ if (looseFields.length > 0) {
893
+ sections.push({
894
+ id: PROPERTIES_SECTION_ID,
895
+ title: 'Properties',
896
+ icon: faSliders,
897
+ order: PROPERTIES_SECTION_ORDER,
898
+ defaultOpen: true,
899
+ body: { kind: 'fields', fields: looseFields, io },
900
+ });
901
+ }
902
+ groups.forEach((group, index) => {
903
+ // A group is one owner's block (a component, a wrapper tag) and
904
+ // collapses like Transform does — the same affordance, because it is
905
+ // the same kind of thing. A wrapper like `<RigidBody>` can declare 40+
906
+ // props, so a block whose fields are almost all unset starts CLOSED: it
907
+ // stays discoverable without burying the props this document sets.
908
+ const authored = group.properties.filter((p) => !p.defaulted).length;
909
+ sections.push({
910
+ id: groupSectionId(group.groupId),
911
+ title: group.name,
912
+ icon: groupIcon(group.name),
913
+ order: GROUP_SECTION_ORDER + index,
914
+ description:
915
+ authored > 0
916
+ ? `${authored} set of ${group.properties.length}`
917
+ : `${group.properties.length} properties`,
918
+ defaultOpen: authored > 0 && group.properties.length <= GROUP_AUTO_OPEN_MAX,
919
+ testId: `ingest-group-${group.groupId}`,
920
+ body: { kind: 'fields', fields: group.properties, io },
921
+ });
922
+ });
923
+ }
924
+
925
+ const stories = input.stories;
926
+ if (!claimed.has(STORIES_SECTION_ID) && stories) {
927
+ sections.push(composeStoriesSection(stories));
928
+ }
929
+
930
+ sections.push(
931
+ ...contributed.map((section) => withDescriptorEditable(section, io, descriptorFieldsById)),
932
+ );
933
+
934
+ // STABLE — equal-ordered contributions keep registration order, which is
935
+ // how contributions that share `CONTRIBUTED_SECTION_ORDER` stay in the
936
+ // order their modules registered them (`Array.prototype.sort` is specified
937
+ // stable).
938
+ sections.sort((a, b) => a.order - b.order);
939
+
940
+ // A DOCUMENT WHOSE PACKAGE OWNS ITS RAIL shows only that package's sections
941
+ // (plus the built-ins it named). One statement of the rule, after everything
942
+ // has been composed — see {@link applyOwnedRail}.
943
+ const railed = applyOwnedRail(sections, owners, input.rail);
944
+
945
+ // Resolved per NODE, not off the active adapter: a composite session's
946
+ // active adapter deliberately declares no provenance of its own (its worlds'
947
+ // declarations differ), so the badge is the GOVERNING child's —
948
+ // `authoring/provenance.ts`. For a plain adapter this is the adapter's own
949
+ // declaration, unchanged.
950
+ const subjectProvenance = provenanceForNode(adapter, nodeId);
951
+ const subjectDestination = authoringDestination(adapter);
952
+
953
+ return {
954
+ id: nodeId,
955
+ title: node?.label ?? '',
956
+ // Identity belongs to the SUBJECT, so every selection has this row: the
957
+ // name, what kind of thing it is, and where it came from. Sections are
958
+ // FACETS of that subject and never stand in for it.
959
+ identity: {
960
+ rename: {
961
+ // Writable exactly when the adapter DECLARES a writable `name` field.
962
+ // An adapter that describes no `name` at all also drops the path in
963
+ // `inspector.set` (the react/DOM lanes return on any non-style path,
964
+ // and a root-group row answers only its manifest facts), so an
965
+ // editable box over one is a control that silently discards what you
966
+ // type — the row still SHOWS the name, it just cannot pretend to
967
+ // rewrite it.
968
+ readOnly: nameProperty === undefined || nameProperty.readonly === true,
969
+ set: (next: string) => io.set('name', next),
970
+ },
971
+ kindLabel: `${node?.typeLabel ?? node?.kind ?? ''}${
972
+ subjectProvenance ? ` · ${subjectProvenance.label}` : ''
973
+ }`,
974
+ // The same fact the line above formats, UNFORMATTED — the identity row
975
+ // draws the kind's glyph and a sentence cannot be drawn. See
976
+ // `InspectionIdentity.kind`.
977
+ ...(node?.kind ? { kind: node.kind } : {}),
978
+ // WHERE this is written back. It used to head the Hierarchy panel as a
979
+ // breadcrumb row of its own; the Outliner's header is one row and names
980
+ // no file, and the datablock this belongs to is the one being inspected
981
+ // (`InspectionDocumentLine`). A composite session declares no single
982
+ // destination, so it gets no line — the same silence its `provenance`
983
+ // keeps, and for the same reason.
984
+ ...(subjectDestination
985
+ ? {
986
+ document: {
987
+ path: subjectDestination,
988
+ ...(subjectProvenance?.detail ? { title: subjectProvenance.detail } : {}),
989
+ },
990
+ }
991
+ : {}),
992
+ // Only an ANCHORED truth earns a note. An unanchored resolution is the
993
+ // honest fallback the composite hands back for a subject its truth index
994
+ // does not reach (a react/DOM element, a synthetic group row): its
995
+ // `reason` is "no live object for this selection", which as a creation
996
+ // note reads "Source · no live object" over a perfectly mounted,
997
+ // editable element — a false provenance line. No anchor ⇒ no note.
998
+ note: creationSite?.anchored
999
+ ? {
1000
+ // The label names WHICH truth this object came out of, because
1001
+ // "Source" over a level-data record would be false: no source
1002
+ // line mentions where a placed robot stands.
1003
+ text: `${creationSite.kind === 'data' ? 'Level data' : 'Source'} · ${creationSite.display}`,
1004
+ title:
1005
+ creationSite.kind === 'data'
1006
+ ? `Placed by record ${creationSite.record} of ${creationSite.file}, the game's own level data`
1007
+ : `Constructed by a \`new\` expression at ${creationSite.file} line ${creationSite.line}, column ${creationSite.col}`,
1008
+ testId: 'inspector-creation-site',
1009
+ }
1010
+ : undefined,
1011
+ },
1012
+ presentation: { preferred: inspectionAffinityFor(input.surface) },
1013
+ // THE SUBJECT'S OWN WRITABLE FIELDS, which is the door `editor.setField`
1014
+ // resolves a path against.
1015
+ //
1016
+ // `name` is one of them, and it was missing. The identity row DRAWS it and
1017
+ // writes it through `io.set('name', …)` — the same call the hierarchy's
1018
+ // inline rename makes — but it was never listed here, so the one door an
1019
+ // agent has answered "The active Inspector has no field at \"name\"" for
1020
+ // every adapter in the estate. Measured 2026-09-21 on a Blender Model
1021
+ // document, where renaming an object is one of the macro adjustments the
1022
+ // document exists for and had no reachable door at all: the Outliner's
1023
+ // rename is a context menu, the Properties rail is the workbench's own
1024
+ // view, and neither is drivable.
1025
+ //
1026
+ // Same shape and same argument as `withDescriptorEditable` above — it adds
1027
+ // a door onto the write path that already exists, never a second one — and
1028
+ // the readonly descriptor is left out so a refusal stays a refusal (the
1029
+ // Blender Outliner returns `readonly: true` for a collection row).
1030
+ ...(subjectEditableFields.length > 0
1031
+ ? { editable: { fields: subjectEditableFields, io } }
1032
+ : {}),
1033
+ ...(input.alsoSelected ? { alsoSelected: input.alsoSelected } : {}),
1034
+ quickActions,
1035
+ related: adapter.related?.links(nodeId) ?? [],
1036
+ sections: railed,
1037
+ };
1038
+ }
1039
+
1040
+ /**
1041
+ * The Stories section from a resolved {@link ComposeStoriesInput}.
1042
+ */
1043
+ export function composeStoriesSection(stories: ComposeStoriesInput): InspectionSection {
1044
+ return {
1045
+ id: STORIES_SECTION_ID,
1046
+ // The provider names its own states when it has a better word for them
1047
+ // (an ingested game's screens are "Scenes"); "Stories" is the default,
1048
+ // not a fixed heading.
1049
+ title: stories.title ?? 'Stories',
1050
+ icon: faBookOpen,
1051
+ order: STORIES_SECTION_ORDER,
1052
+ body: {
1053
+ kind: 'custom',
1054
+ render: () =>
1055
+ createElement(InspectorStoriesSection, {
1056
+ stories: stories.stories,
1057
+ activeStoryId: stories.activeStoryId,
1058
+ onApplyStory: stories.onApplyStory,
1059
+ isolation: stories.isolation,
1060
+ unavailableReason: stories.unavailableReason ?? null,
1061
+ }),
1062
+ },
1063
+ };
1064
+ }