@volter/sdk 0.0.0-stage → 0.5.204

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