@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,968 @@
1
+ /**
2
+ * The PROJECTIONS of the inspection model — Layer 3, and the only place a
3
+ * composed {@link InspectionSubject} becomes pixels.
4
+ *
5
+ * ONE box, two layouts (`inspection/model.ts`):
6
+ * - `column` — the DEFAULT: the subject's full inspector as a slim vertical
7
+ * column. Identity row (name, visibility eye, a LABELED open-for-edit
8
+ * button), then a persistent SECTION-ICON STRIP (one glyph per
9
+ * `subject.sections`, 1:1) that signposts what the subject HAS and jumps
10
+ * to a section, then every section stacked and collapsible;
11
+ * - `card` — the space-tight fallback: a MINI card (preview, name, the same
12
+ * icon strip, the open-for-edit) that the viewport host
13
+ * (`components/CompactInspectorCard.tsx`) can collapse at the floor to a
14
+ * single-line PILL. Clicking any collapsed form restores the column.
15
+ *
16
+ * "The box should actually be an identical thing regardless of what viewport
17
+ * it's on. The only thing that differs is the sections. The sections only
18
+ * differ because the inspector's structured data is different." (owner.) So
19
+ * everything above the sections — the identity row, the name, the kind, the
20
+ * verbs on it, the icon strip — reads the SAME model fields in both layouts;
21
+ * the only differences below are the layout's own arrangement.
22
+ *
23
+ * Two actions stay unambiguous because only ONE is an unlabeled gesture:
24
+ * clicking the box (any collapsed form, the mini card's preview, a strip
25
+ * glyph) EXPANDS/RESTORES the column — an in-place zoom on the SAME subject —
26
+ * while OPEN-FOR-EDIT is ALWAYS a labeled control that navigates to the
27
+ * subject's DEFINITION (a DIFFERENT subject). The definition link is the
28
+ * subject's own `related` link (`inspection/model.ts` {@link SubjectLink},
29
+ * produced by each adapter's `related` provider), surfaced here as the labeled
30
+ * Edit button only when the subject carries one.
31
+ *
32
+ * The projections are DUMB: they read identity, verbs, related links, and each
33
+ * section's id/title/icon/order/body off the model, and they never learn what
34
+ * a section contains, which contribution replaced which built-in, or which
35
+ * surface produced the subject. `surface` appears here for exactly one reason
36
+ * — it is the key the presentation preference is stored under — and a host
37
+ * that names none simply offers no switch.
38
+ */
39
+
40
+ import {
41
+ faArrowUpRightFromSquare,
42
+ faCube,
43
+ faWindowMinimize,
44
+ faXmark,
45
+ } from '@fortawesome/free-solid-svg-icons';
46
+ import {
47
+ Button,
48
+ DraftTextInput,
49
+ EditorIcon,
50
+ IconButton,
51
+ Panel,
52
+ SectionHeader,
53
+ spaceVar,
54
+ themeVars,
55
+ } from '@volter/editor-sdk/widgets';
56
+ import {
57
+ Fragment,
58
+ type KeyboardEvent as ReactKeyboardEvent,
59
+ type ReactNode,
60
+ type UIEvent as ReactUIEvent,
61
+ useCallback,
62
+ useRef,
63
+ useState,
64
+ } from 'react';
65
+ import { hierarchyKindIcon } from '../hierarchy-kind-icon';
66
+ import { setActiveScope } from '../hotkeys';
67
+ import {
68
+ actionsPlacedAt,
69
+ type InspectionAction,
70
+ type InspectionPresentation,
71
+ type InspectionPreviewMode,
72
+ type InspectionSection,
73
+ type InspectionSubject,
74
+ type InspectionSurfaceKind,
75
+ type SubjectLink,
76
+ } from '../inspection/model';
77
+ import { compactInspectorTabs, setInspectorPresentationOverride } from '../inspector-presentation';
78
+ import { useCompactInspectorHost } from './CompactInspectorShell';
79
+ import { InspectorFieldsSection } from './InspectorFieldsSection';
80
+ import { useAfterPaint } from './use-after-paint';
81
+
82
+ /** The body of one composed section, rendered by whichever layout asked for
83
+ * it: a `fields` body is the generic descriptor grid, a `custom` body is an
84
+ * opaque block that renders itself, a `preview` body is the subject's live
85
+ * square view. No projection looks past this. */
86
+ function sectionBody(section: InspectionSection): ReactNode {
87
+ return section.body.kind === 'fields' ? (
88
+ <InspectorFieldsSection fields={section.body.fields} io={section.body.io} />
89
+ ) : (
90
+ section.body.render()
91
+ );
92
+ }
93
+
94
+ /** A subject verb as an icon button (the `identity` placement — today the
95
+ * visibility eye, beside the name field). */
96
+ function QuickActionIconButton({ action }: { readonly action: InspectionAction }) {
97
+ return (
98
+ <IconButton
99
+ data-testid={action.id}
100
+ aria-label={action.title}
101
+ title={action.title}
102
+ aria-pressed={action.pressed}
103
+ disabled={action.disabled}
104
+ onClick={action.run}
105
+ >
106
+ {action.icon && <EditorIcon icon={action.icon} />}
107
+ </IconButton>
108
+ );
109
+ }
110
+
111
+ /**
112
+ * The subject's DEFINITION link — the one whose `id` carries the `definition:`
113
+ * marker its producing adapter stamped (mirroring the `creation-site:`
114
+ * convention), never a POSITIONAL guess. `SubjectLink` has no definition field,
115
+ * so identifying "the definition" by list order would silently promote whatever
116
+ * link an adapter happened to list first (a state machine, say). The marker is
117
+ * the contract; the first link that carries it is the open-for-edit target,
118
+ * and its absence is an honest "no definition to open" — no `[0]` fallback.
119
+ */
120
+ function definitionLink(subject: InspectionSubject): SubjectLink | null {
121
+ return subject.related.find((link) => link.id.startsWith('definition:')) ?? null;
122
+ }
123
+
124
+ /** The related links that are NEITHER construction-site provenance (which rides
125
+ * in `identity.note`) NOR the promoted `definition:` link — the rest of the
126
+ * derivation chain (an asset document's state machine and the like). Filtered
127
+ * by kind, so a demoted definition can never reappear here and a non-definition
128
+ * link is never hidden by position. */
129
+ function otherRelatedLinks(subject: InspectionSubject): readonly SubjectLink[] {
130
+ return subject.related.filter(
131
+ (link) => !link.id.startsWith('creation-site:') && !link.id.startsWith('definition:'),
132
+ );
133
+ }
134
+
135
+ /**
136
+ * The LABELED open-for-edit control (icon + text) — the ONE unlabeled-gesture
137
+ * exception's counterweight: it navigates to the subject's DEFINITION (a
138
+ * DIFFERENT subject whose edits change every instance), so it must never be
139
+ * the primary click. Its text is the link's own title ("Open Crate"), which is
140
+ * the honest name of the real `SubjectLink.open` verb it runs.
141
+ */
142
+ function EditDefinitionButton({ link }: { readonly link: SubjectLink }) {
143
+ return (
144
+ <Button
145
+ variant="secondary"
146
+ size="compact"
147
+ data-testid="inspector-open-definition"
148
+ title={link.title}
149
+ onClick={link.open}
150
+ style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 0 }}
151
+ >
152
+ <EditorIcon icon={faArrowUpRightFromSquare} />
153
+ <span
154
+ style={{
155
+ maxWidth: 120,
156
+ overflow: 'hidden',
157
+ textOverflow: 'ellipsis',
158
+ whiteSpace: 'nowrap',
159
+ }}
160
+ >
161
+ {link.title}
162
+ </span>
163
+ </Button>
164
+ );
165
+ }
166
+
167
+ /** The DERIVATION chain beyond the definition — an asset document's state
168
+ * machine and the like. The definition itself is promoted to the identity
169
+ * row's Edit button, so this row renders only what is left. */
170
+ function RelatedSubjects({ subject }: { readonly subject: InspectionSubject }) {
171
+ const rest = otherRelatedLinks(subject);
172
+ if (rest.length === 0) return null;
173
+ return (
174
+ <div
175
+ data-testid="inspector-related-subjects"
176
+ style={{ display: 'flex', flexWrap: 'wrap', gap: 4, padding: '6px 8px' }}
177
+ >
178
+ {rest.map((link) => (
179
+ <Button key={link.id} variant="ghost" onClick={link.open} title={link.title}>
180
+ {link.title}
181
+ </Button>
182
+ ))}
183
+ </div>
184
+ );
185
+ }
186
+
187
+ /**
188
+ * The SECTION-ICON STRIP — one glyph per `subject.sections`, 1:1
189
+ * (`compactInspectorTabs`). It signposts what the subject HAS and jumps to a
190
+ * section; it is NOT a tab that opens one section into a floating panel. In the
191
+ * column `onJump` scrolls the section into view; on the mini card there are no
192
+ * stacked sections, so `onJump` restores the column.
193
+ */
194
+ function SectionIconStrip({
195
+ subject,
196
+ onJump,
197
+ }: {
198
+ readonly subject: InspectionSubject;
199
+ readonly onJump: (sectionId: string) => void;
200
+ }) {
201
+ const tabs = compactInspectorTabs(subject);
202
+ // The strip is a JUMP AID — a table of contents. It earns its row only
203
+ // when there is real navigation to do (a dense entity subject: preview,
204
+ // transform, property groups, stories…) AND its glyphs can be told
205
+ // apart. Three sections need no navigator, and identical icons signpost
206
+ // nothing — the 1:1 derivation stands (owner, 2026-08-07); an
207
+ // uninformative row earns no pixels (owner-sighted, 2026-08-23).
208
+ if (tabs.length < 5 || new Set(tabs.map((tab) => tab.icon)).size < 3) return null;
209
+ return (
210
+ <div
211
+ className="vgai-inspector-icon-strip"
212
+ role="toolbar"
213
+ aria-label="Sections"
214
+ data-testid="inspector-section-strip"
215
+ >
216
+ {tabs.map((tab) => (
217
+ <IconButton
218
+ key={tab.id}
219
+ size="compact"
220
+ data-testid={`section-icon-${tab.id}`}
221
+ aria-label={tab.title}
222
+ title={tab.title}
223
+ onClick={() => onJump(tab.id)}
224
+ >
225
+ <EditorIcon icon={tab.icon} />
226
+ </IconButton>
227
+ ))}
228
+ </div>
229
+ );
230
+ }
231
+
232
+ /** The subject's live preview at `thumbnail` fill, or a quiet kind glyph when
233
+ * the subject has none (react/DOM/no-Object3D subjects). The picture follows a
234
+ * frame after selection (`components/use-after-paint.ts`), so the glyph holds
235
+ * the geometry until it lands and nothing reflows. Inert: the host owns the
236
+ * single click. */
237
+ function SubjectThumbnail({
238
+ render,
239
+ glyph,
240
+ subjectKey,
241
+ }: {
242
+ readonly render: ((mode?: InspectionPreviewMode) => ReactNode) | null;
243
+ readonly glyph: InspectionSection['icon'];
244
+ readonly subjectKey: string;
245
+ }) {
246
+ const picture = useAfterPaint(subjectKey) ? render : null;
247
+ return picture ? (
248
+ <div aria-hidden="true" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
249
+ {picture('thumbnail')}
250
+ </div>
251
+ ) : (
252
+ <EditorIcon icon={glyph} />
253
+ );
254
+ }
255
+
256
+ /** The editable-or-readonly name field, styled the same either way (a readonly
257
+ * `<input>` invites typing that vanishes, so it stands in as plain text).
258
+ *
259
+ * ITS INK IS THE FIELD'S, NOT A READ-ONLY TONE. The stand-in took
260
+ * `content.dim` (150) where every string Blender draws in its Properties
261
+ * editor inks #e5e5e5 — measured on `properties-object.png`'s datablock row
262
+ * (y 142..178, full width: ceiling 229 with 245 px, nothing above it), the
263
+ * row this one is the counterpart to. A WRONG MEMBER, not a wrong value:
264
+ * read-only is said by the absent caret and the absent hover, not by a
265
+ * quieter ink, and the field already restates every other part of the real
266
+ * control's paint (`compact-inspector.css`'s
267
+ * `[data-testid="ingest-name"][data-readonly="true"]`). */
268
+ function SubjectNameField({ subject }: { readonly subject: InspectionSubject }) {
269
+ const identity = subject.identity;
270
+ if (!identity) return null;
271
+ const summary = [identity.kindLabel, identity.note?.text].filter(Boolean).join(' — ');
272
+ if (identity.rename.readOnly) {
273
+ return (
274
+ <div
275
+ data-testid="ingest-name"
276
+ data-readonly="true"
277
+ {...(summary ? { title: summary } : {})}
278
+ style={{
279
+ flex: 1,
280
+ minWidth: 0,
281
+ fontSize: 'var(--vgai-font-md)',
282
+ fontWeight: 600,
283
+ color: themeVars.content.primary,
284
+ whiteSpace: 'nowrap',
285
+ overflow: 'hidden',
286
+ textOverflow: 'ellipsis',
287
+ }}
288
+ >
289
+ {subject.title}
290
+ </div>
291
+ );
292
+ }
293
+ return (
294
+ <DraftTextInput
295
+ data-testid="ingest-name"
296
+ aria-label="Name"
297
+ value={subject.title}
298
+ onCommit={(next) => {
299
+ if (next.trim() !== '') identity.rename.set(next);
300
+ }}
301
+ data-emphasis="strong"
302
+ {...(summary ? { title: summary } : {})}
303
+ style={{ flex: 1, minWidth: 0 }}
304
+ />
305
+ );
306
+ }
307
+
308
+ /**
309
+ * The subject's identity ROW — name, the identity verbs (the eye), and the
310
+ * labeled open-for-edit button when the subject carries a definition — over a
311
+ * dim kind/provenance line. One component, read by both layouts; a subject
312
+ * with no identity row shows its headline and quiet hint instead.
313
+ */
314
+ function SubjectIdentity({ subject }: { readonly subject: InspectionSubject }) {
315
+ const identity = subject.identity;
316
+ if (!identity) {
317
+ return (
318
+ <div style={{ padding: 12 }}>
319
+ <div style={{ fontSize: 'var(--vgai-font-md)', fontWeight: 600 }}>{subject.title}</div>
320
+ {subject.hint && (
321
+ <div
322
+ data-testid="inspector-subject-hint"
323
+ style={{
324
+ marginTop: 4,
325
+ fontSize: 'var(--vgai-font-base)',
326
+ color: themeVars.content.dim,
327
+ }}
328
+ >
329
+ {subject.hint}
330
+ </div>
331
+ )}
332
+ </div>
333
+ );
334
+ }
335
+ const definition = definitionLink(subject);
336
+ return (
337
+ <div
338
+ className="vgai-inspector-identity"
339
+ // Blender's datablock row — the counterpart to this one — is 21px: its
340
+ // 18px name field plus a hair. An 8px inset all round made ours 37, the
341
+ // tallest thing in the column and the loudest place it stopped reading
342
+ // as Properties. The inset is now the 2px step vertically (the optional
343
+ // second lines below still need a floor) and stays 8px across.
344
+ style={{
345
+ padding: `${spaceVar[1]} ${spaceVar[4]}`,
346
+ borderBottom: '1px solid var(--vgai-structural-divider)',
347
+ }}
348
+ >
349
+ <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
350
+ <SubjectNameField subject={subject} />
351
+ {/* The kind rides the name row — two lines said one thing
352
+ (owner-sighted, 2026-08-23). */}
353
+ {identity.kindLabel && (
354
+ <span
355
+ data-testid="inspector-kind-label"
356
+ style={{
357
+ fontSize: 'var(--vgai-font-base)',
358
+ color: themeVars.content.dim,
359
+ whiteSpace: 'nowrap',
360
+ }}
361
+ >
362
+ {identity.kindLabel}
363
+ </span>
364
+ )}
365
+ {actionsPlacedAt(subject, 'identity').map((action) => (
366
+ <QuickActionIconButton key={action.id} action={action} />
367
+ ))}
368
+ {definition && <EditDefinitionButton link={definition} />}
369
+ </div>
370
+ {/* WHERE this is written. The Hierarchy panel used to head itself with
371
+ this path; Blender's Outliner header is one row and names no file,
372
+ and the datablock row — this one — is where the file belongs. The
373
+ seam word rides `kindLabel` one line above, so this is the path
374
+ alone. */}
375
+ {identity.document && (
376
+ <div
377
+ data-testid="inspector-document-path"
378
+ data-document-path={identity.document.path}
379
+ {...(identity.document.title ? { title: identity.document.title } : {})}
380
+ style={{
381
+ marginTop: spaceVar[1],
382
+ fontSize: 'var(--vgai-font-base)',
383
+ color: themeVars.content.muted,
384
+ whiteSpace: 'nowrap',
385
+ overflow: 'hidden',
386
+ textOverflow: 'ellipsis',
387
+ direction: 'rtl',
388
+ textAlign: 'left',
389
+ }}
390
+ >
391
+ {/* `direction: rtl` ellipsizes at the HEAD, so a long path keeps its
392
+ filename — the half a person reads. The bidi isolate keeps the
393
+ slashes in their authored order inside it. */}
394
+ <bdi>{identity.document.path}</bdi>
395
+ </div>
396
+ )}
397
+ {/* WHAT ELSE THE GIZMO IS HOLDING. The fields below belong to the one
398
+ node named above; a drag moves the whole selection. Saying so is the
399
+ difference between a panel that is scoped and a panel that is wrong
400
+ (`InspectionSubject.alsoSelected`). */}
401
+ {subject.alsoSelected ? (
402
+ <div
403
+ data-testid="inspector-also-selected"
404
+ style={{ marginTop: 6, fontSize: 'var(--vgai-font-base)', color: themeVars.content.dim }}
405
+ >
406
+ {`${subject.alsoSelected + 1} selected — fields edit this one, the gizmo moves all`}
407
+ </div>
408
+ ) : null}
409
+ {identity.note?.text && (
410
+ <div
411
+ data-testid="inspector-subject-note"
412
+ style={{ marginTop: 6, fontSize: 'var(--vgai-font-base)', color: themeVars.content.dim }}
413
+ >
414
+ {identity.note.text}
415
+ </div>
416
+ )}
417
+ </div>
418
+ );
419
+ }
420
+
421
+ /**
422
+ * The PROPERTIES presentation's identity — Blender's Properties editor opens
423
+ * with TWO rows, transcribed from the reference frames at 2x rather than
424
+ * described from memory, and neither is a title bar:
425
+ *
426
+ * - the BREADCRUMB row: a 14 px datablock glyph, the chain it belongs to, and
427
+ * ONE trailing control. `properties-data-edit.png`: the glyph's bounding box
428
+ * is x 80..107 / y 74..101 (28 device px = 14 CSS) and the trailing pin's is
429
+ * x 559..586 at the same size, on the panel's own fill with no well.
430
+ * WHAT THE CHAIN OWNS IS THE CONTEXT CHAIN, NOT A FILE (measured
431
+ * 2026-09-18 on all three tab frames): each link is `glyph + NAME`, the
432
+ * datablock's own name in PLAIN ink, innermost last, `>` between —
433
+ * `[▣] Cube` on the Object and Modifier tabs, `[▣] Cube > [▽] Cube` on
434
+ * Data. Blender has no file anywhere on this row. Ours carried
435
+ * `identity.document.path` here, which is the one fact the reference
436
+ * never shows; the path is now the row's TOOLTIP (and still the
437
+ * `data-document-path` attribute an agent reads), because the Model
438
+ * document's source path has NO other home in our chrome — the editor tab
439
+ * renders the bare title with no `title` attribute, and the window title
440
+ * is the PROJECT's. A reachable fact is not deleted to match a
441
+ * picture; it is moved off the row the picture owns.
442
+ * INK, measured on every frame: the glyph, the name and the `>` are all
443
+ * 229/229/229 — PLAIN UI ink, never the tab's category colour. Only the
444
+ * RAIL is coloured (`#73a1ff` modifiers, `#00d3a2` data). The pin alone
445
+ * is dimmer, 155. Ours inked the breadcrumb glyph `dim` (150) and let the
446
+ * datablock well's glyph fall through to its category (`#73a1ff`), which
447
+ * is the loudest thing on the row and is nowhere in the reference.
448
+ * - the DATABLOCK row: one 20 CSS px composite spanning the panel's inset
449
+ * width — an icon WELL in `widget.menu` (x 76..135 = 30 CSS wide, #272727),
450
+ * a 1 px divider, the name TEXT WELL in `widget.field` (x 138..550,
451
+ * #1c1c1c), and a trailing pushbutton in `widget.regular` (x 553..590,
452
+ * #535353), the whole thing outlined in #3c3c3c at x 74..75 / 591..592 and
453
+ * y 140..141 / 178..179.
454
+ *
455
+ * What we DO NOT draw, by the standing rule that a control writing nowhere is
456
+ * not drawn: Blender's well carries a `⌄` because it PICKS a datablock and its
457
+ * trailing button is the fake-user shield — this editor has neither verb, so
458
+ * the well is the kind glyph alone and the trailing slot carries the subject's
459
+ * own identity verbs (the visibility eye) instead. That eye's home in the
460
+ * reference is the Outliner, which our Hierarchy row already has; this is the
461
+ * shortcut, on the row whose trailing slot the reference spends the same way.
462
+ *
463
+ * Every fact the narrow column's row shows is still here — name, kind, the
464
+ * document path, the identity verbs, the definition link, the multi-selection
465
+ * warning and the note — rearranged into the reference's two rows.
466
+ * {@link SubjectIdentity} is untouched, so the narrow column (every other
467
+ * skin's default) is pixel-identical.
468
+ */
469
+ function PropertiesIdentity({ subject }: { readonly subject: InspectionSubject }) {
470
+ const identity = subject.identity;
471
+ if (!identity) return <SubjectIdentity subject={subject} />;
472
+ const definition = definitionLink(subject);
473
+ /**
474
+ * THE SUBJECT'S KIND GLYPH — not the open tab's, and not the rail's first.
475
+ *
476
+ * MEASURED, and it settles a reading this row has had wrong twice. Blender's
477
+ * breadcrumb leads with the DATABLOCK the editor is about, on every tab:
478
+ * crop the first breadcrumb glyph (28x28 device at x 80..107, y 74..101)
479
+ * out of `properties-object.png`, `properties-modifier.png` and
480
+ * `properties-data-edit.png` and the three are byte-identical — 0 of 784
481
+ * pixels differ — while the open tab is Object, Modifier and Data in turn.
482
+ * On the Modifier tab Blender draws the object square, NOT the wrench.
483
+ *
484
+ * The row first derived this from `sections[0].icon`, which is RAIL ORDER;
485
+ * that looked right only while Data happened to be first, and the rail unit
486
+ * that put Modifiers above Data (Blender's order) broke it. The fix then
487
+ * was the ACTIVE TAB's icon, which is right for the rail's own active plate
488
+ * and wrong here — it agrees with the frames on Object and Data only
489
+ * because those two tabs' glyphs coincide with their datablock's, and
490
+ * disagrees on the one tab that discriminates.
491
+ *
492
+ * `hierarchy-kind-icon.ts` is the host's single kind-name-to-glyph table
493
+ * and the Outliner's rows already read it, so the breadcrumb and the well
494
+ * now read the same table for the same subject. Fallback stays the first
495
+ * section's glyph, for a subject whose adapter names no kind.
496
+ */
497
+ const glyph = identity.kind
498
+ ? hierarchyKindIcon(identity.kind, firstSectionGlyph(subject))
499
+ : firstSectionGlyph(subject);
500
+ const chain = subject.title || identity.kindLabel || '';
501
+ const chainTitle = [identity.kindLabel, identity.document?.path, identity.document?.title]
502
+ .filter(Boolean)
503
+ .join(' — ');
504
+ return (
505
+ <div className="vgai-inspector-identity" data-vgai-identity-layout="properties">
506
+ <div className="vgai-inspector-breadcrumb">
507
+ <EditorIcon icon={glyph} tone="primary" />
508
+ {/* A NAME, not a path: it ellipsizes at the TAIL like every other
509
+ name in the chrome. The head-ellipsis (`direction: rtl`) this row
510
+ used to carry existed for the path, which is now the tooltip. */}
511
+ <span
512
+ className="vgai-inspector-breadcrumb-chain"
513
+ data-testid="inspector-document-path"
514
+ {...(identity.document ? { 'data-document-path': identity.document.path } : {})}
515
+ {...(chainTitle ? { title: chainTitle } : {})}
516
+ >
517
+ <bdi>{chain}</bdi>
518
+ </span>
519
+ {actionsPlacedAt(subject, 'identity').map((action) => (
520
+ <QuickActionIconButton key={action.id} action={action} />
521
+ ))}
522
+ </div>
523
+ {
524
+ <div className="vgai-inspector-datablock">
525
+ <span
526
+ className="vgai-inspector-datablock-kind"
527
+ data-testid="inspector-kind-label"
528
+ {...(identity.kindLabel ? { title: identity.kindLabel } : {})}
529
+ >
530
+ {/* PLAIN ink, not the category's: Blender's well glyph measures 229
531
+ on every frame, the same white as the breadcrumb's. The category
532
+ colour lives in the RAIL and nowhere else in this body. */}
533
+ <EditorIcon icon={glyph} tone="primary" />
534
+ </span>
535
+ <SubjectNameField subject={subject} />
536
+ {definition && <EditDefinitionButton link={definition} />}
537
+ </div>
538
+ }
539
+ {subject.alsoSelected ? (
540
+ <div
541
+ data-testid="inspector-also-selected"
542
+ style={{
543
+ marginTop: spaceVar[3],
544
+ fontSize: 'var(--vgai-font-base)',
545
+ color: themeVars.content.dim,
546
+ }}
547
+ >
548
+ {`${subject.alsoSelected + 1} selected — fields edit this one, the gizmo moves all`}
549
+ </div>
550
+ ) : null}
551
+ {identity.note?.text && (
552
+ <div
553
+ data-testid="inspector-subject-note"
554
+ style={{
555
+ marginTop: spaceVar[3],
556
+ fontSize: 'var(--vgai-font-base)',
557
+ color: themeVars.content.dim,
558
+ }}
559
+ >
560
+ {identity.note.text}
561
+ </div>
562
+ )}
563
+ </div>
564
+ );
565
+ }
566
+
567
+ /** One section under its own header — title AND icon, unconditionally. The
568
+ * disclosure triangle is the STACK's affordance (`collapsible`). The wrapper
569
+ * carries `data-inspector-section` so the icon strip can scroll to it. */
570
+ function ProjectedSection({ section }: { readonly section: InspectionSection }) {
571
+ return (
572
+ <div data-testid={section.testId} data-inspector-section={section.id}>
573
+ <SectionHeader
574
+ label={section.title}
575
+ icon={section.icon}
576
+ description={section.description}
577
+ collapsible
578
+ defaultOpen={section.defaultOpen ?? true}
579
+ >
580
+ {sectionBody(section)}
581
+ </SectionHeader>
582
+ </div>
583
+ );
584
+ }
585
+
586
+ /**
587
+ * The NARROW COLUMN (default): identity row, the section-icon strip, the
588
+ * derivation chain, then every section stacked and collapsible. A strip glyph
589
+ * scrolls its section into view within this column.
590
+ */
591
+ export function NarrowColumn({ subject }: { readonly subject: InspectionSubject }) {
592
+ const sectionsRef = useRef<HTMLDivElement>(null);
593
+ const jumpToSection = (sectionId: string) => {
594
+ const target = sectionsRef.current?.querySelector(`[data-inspector-section="${sectionId}"]`);
595
+ target?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
596
+ };
597
+ return (
598
+ <div style={{ pointerEvents: 'auto' }}>
599
+ <SubjectIdentity subject={subject} />
600
+ <SectionIconStrip subject={subject} onJump={jumpToSection} />
601
+ <RelatedSubjects subject={subject} />
602
+ <div ref={sectionsRef}>
603
+ {subject.sections.map((section) => (
604
+ <Fragment key={section.id}>
605
+ <ProjectedSection section={section} />
606
+ </Fragment>
607
+ ))}
608
+ </div>
609
+ </div>
610
+ );
611
+ }
612
+
613
+ /**
614
+ * Which tab each subject last showed under the PROPERTIES presentation, and
615
+ * the last tab shown anywhere — Blender keeps the Properties editor on the
616
+ * same tab across selections when the new object has it, and that is the
617
+ * behaviour a modeler's hands expect: pick Material, click through objects,
618
+ * still on Material. Keyed by subject id, bounded like the scroll memory.
619
+ */
620
+ const propertiesTabMemory = new Map<string, string>();
621
+ let lastPropertiesTab: string | null = null;
622
+
623
+ /**
624
+ * The PROPERTIES column: identity row and derivation chain as the narrow
625
+ * column has them, then the sections TABBED — a vertical icon rail down the
626
+ * left edge, one section body at a time under a non-collapsible header (a
627
+ * triangle would offer to hide the only thing in the box). Every section
628
+ * earns a rail glyph, 1:1 with `subject.sections` — this rail is the
629
+ * NAVIGATION, not a table of contents, so the strip's "five sections or no
630
+ * strip" economy does not apply.
631
+ */
632
+ export function PropertiesColumn({ subject }: { readonly subject: InspectionSubject }) {
633
+ const tabs = compactInspectorTabs(subject);
634
+ const remembered = propertiesTabMemory.get(subject.id) ?? lastPropertiesTab;
635
+ const [chosen, setChosen] = useState<string | null>(null);
636
+ // WHAT THE RAIL OPENS ON, in order: the tab this person clicked, the one
637
+ // they last used, THE TAB A SECTION DECLARED AS THE DEFAULT, and only then
638
+ // the first tab. The declaration exists because Blender's Properties editor
639
+ // does not open on its first tab: `SpaceProperties.context` is stored per
640
+ // screen in the startup file, and at factory settings
641
+ // `bpy.data.screens['Layout']` reads OBJECT while the rail's first tab is
642
+ // Render (measured against Blender 5.2 LTS, walk 5 parity row 3 — ours
643
+ // opened the cube on the Render tab). A rail whose sections declare nothing
644
+ // opens on its first tab exactly as before.
645
+ const activeId =
646
+ (chosen && tabs.some((tab) => tab.id === chosen) ? chosen : null) ??
647
+ (remembered && tabs.some((tab) => tab.id === remembered) ? remembered : null) ??
648
+ tabs.find((tab) => tab.railDefault)?.id ??
649
+ tabs[0]?.id ??
650
+ null;
651
+ const active = subject.sections.find((section) => section.id === activeId) ?? null;
652
+ const choose = (id: string) => {
653
+ setChosen(id);
654
+ if (propertiesTabMemory.size > 200) propertiesTabMemory.clear();
655
+ propertiesTabMemory.set(subject.id, id);
656
+ lastPropertiesTab = id;
657
+ };
658
+ return (
659
+ // The class carries no paint: it is the one named handle the rail needs
660
+ // to reach the bottom of the panel (`compact-inspector.css`, beside the
661
+ // rail's own `min-height: 100%`). Without a name this wrapper sizes to
662
+ // its content and the rail stops under the last tab.
663
+ <div className="vgai-inspector-properties-fill" style={{ pointerEvents: 'auto' }}>
664
+ {/* THE RAIL IS THE AREA'S FULL LEFT EDGE, and the identity rows sit
665
+ BESIDE it, not above. Measured on `properties-object.png` at 2x: the
666
+ rail column runs x 2..57 from the body's top (y=47) to the frame's
667
+ bottom, and the breadcrumb's own glyph starts at x=80 — everything
668
+ the editor draws is to the right of the rail. Ours used to stack the
669
+ identity across the full width and start the rail under it. */}
670
+ <div className="vgai-inspector-properties" data-testid="inspector-properties">
671
+ <div
672
+ className="vgai-inspector-properties-rail"
673
+ role="tablist"
674
+ aria-orientation="vertical"
675
+ aria-label="Sections"
676
+ >
677
+ {tabs.map((tab, index) => (
678
+ <Fragment key={tab.id}>
679
+ {/* THE RAIL HAS GROUPS, and Blender's draws ground between them:
680
+ `ED_buttons_tabs_list` (`space_buttons.cc:201-255`) calls
681
+ `add_spacer()` between the tool tab, the scene group, the
682
+ collection tab, the object group and Texture, appending a
683
+ `BCONTEXT_SEPARATOR` the rail renders as a gap rather than a
684
+ tab. Measured on `modeling-edit-none.png` down x=2852: 1 CSS
685
+ px of hairline between cells of ONE group, 8.5 across a group
686
+ boundary. Sections that name no group form one trailing group
687
+ — a subject with no `railGroup` anywhere draws no separator
688
+ at all, which is every non-Blender rail. */}
689
+ {index > 0 && tab.railGroup !== tabs[index - 1]?.railGroup ? (
690
+ <div
691
+ className="vgai-inspector-properties-rail-separator"
692
+ data-testid="properties-tab-separator"
693
+ role="presentation"
694
+ />
695
+ ) : null}
696
+ <IconButton
697
+ size="compact"
698
+ role="tab"
699
+ aria-selected={tab.id === activeId}
700
+ data-testid={`properties-tab-${tab.id}`}
701
+ aria-label={tab.title}
702
+ title={tab.title}
703
+ onClick={() => choose(tab.id)}
704
+ >
705
+ {/* A tab glyph, not body text: Blender's Properties tabs are
706
+ 14px in a 28px strip, and the default `sm` rendered these at
707
+ 10 — a rail of specks beside the reference. */}
708
+ <EditorIcon icon={tab.icon} size="xl" />
709
+ </IconButton>
710
+ </Fragment>
711
+ ))}
712
+ </div>
713
+ <div className="vgai-inspector-properties-body" role="tabpanel">
714
+ <PropertiesIdentity subject={subject} />
715
+ <RelatedSubjects subject={subject} />
716
+ {/* NO HEADER OVER THE ACTIVE TAB. Blender's Properties editor never
717
+ heads a tab's content with the tab's own name — measured on three
718
+ frames at 2x: `properties-object.png` runs header → breadcrumb →
719
+ datablock row → the `Transform` CARD; `properties-data-edit.png`
720
+ runs the same three rows → the `Vertex Groups` card;
721
+ `modeling.png`'s Modifier tab → `Add Modifier`. The rail already
722
+ says which editor you are in, and a slab repeating it was the
723
+ full-bleed bar at the top of our column. The sections BELOW keep
724
+ their own headers, because in the reference they are the cards. */}
725
+ {active ? (
726
+ <div data-testid={active.testId} data-inspector-section={active.id}>
727
+ {sectionBody(active)}
728
+ </div>
729
+ ) : null}
730
+ {/* Sections that host publishing contributions stay live behind the
731
+ active tab (`InspectionSection.keepMounted`). */}
732
+ {subject.sections
733
+ .filter((section) => section.keepMounted && section.id !== activeId)
734
+ .map((section) => (
735
+ <div
736
+ key={section.id}
737
+ data-inspector-section={section.id}
738
+ data-inspector-section-hidden="true"
739
+ style={{ display: 'none' }}
740
+ >
741
+ {sectionBody(section)}
742
+ </div>
743
+ ))}
744
+ </div>
745
+ </div>
746
+ </div>
747
+ );
748
+ }
749
+
750
+ /** The mini card's preview, or a kind glyph for a no-preview subject. */
751
+ function firstSectionGlyph(subject: InspectionSubject) {
752
+ return subject.sections[0]?.icon ?? faCube;
753
+ }
754
+
755
+ /**
756
+ * The MINI card — the space-tight fallback: the subject's preview (or a kind
757
+ * glyph) above its name and eye, the same section-icon strip, and the labeled
758
+ * open-for-edit when the subject carries a definition. Clicking the preview or
759
+ * a strip glyph EXPANDS the column (an in-place zoom on the same subject);
760
+ * `minimize` (when the viewport host provides one) drops to the pill.
761
+ *
762
+ * `surface` is the key the expand preference is written under; a bounded host
763
+ * that names none renders the card without the expand affordance.
764
+ */
765
+ export function MiniInspectorCard({
766
+ subject,
767
+ surface,
768
+ }: {
769
+ readonly subject: InspectionSubject;
770
+ readonly surface?: InspectionSurfaceKind | undefined;
771
+ }) {
772
+ const host = useCompactInspectorHost();
773
+ const previewSection = subject.sections.find((section) => section.body.kind === 'preview');
774
+ const previewBody = previewSection?.body.kind === 'preview' ? previewSection.body : null;
775
+ const definition = definitionLink(subject);
776
+ const expand = surface ? () => setInspectorPresentationOverride(surface, 'column') : null;
777
+ const inIdentity = subject.identity !== null;
778
+ return (
779
+ <div
780
+ className="vgai-mini-inspector"
781
+ data-testid="inspector-panel"
782
+ data-vgai-inspector-presentation="card"
783
+ onPointerDown={() => setActiveScope('inspector')}
784
+ >
785
+ {host && (
786
+ <IconButton
787
+ size="compact"
788
+ className="vgai-mini-inspector-minimize"
789
+ data-testid="inspector-minimize"
790
+ aria-label="Minimize inspector"
791
+ title="Minimize inspector"
792
+ onClick={host.minimize}
793
+ >
794
+ <EditorIcon icon={faWindowMinimize} />
795
+ </IconButton>
796
+ )}
797
+ {/* The preview (or kind glyph) is the primary EXPAND gesture — clicking
798
+ the box restores the column. */}
799
+ <div
800
+ className="vgai-mini-inspector-preview"
801
+ data-testid="inspector-mini-preview"
802
+ {...(expand
803
+ ? {
804
+ role: 'button',
805
+ tabIndex: 0,
806
+ 'aria-label': `Expand inspector for ${subject.title}`,
807
+ title: `Expand inspector for ${subject.title}`,
808
+ onClick: expand,
809
+ onKeyDown: (event: ReactKeyboardEvent) => {
810
+ if (event.key === ' ' || event.key === 'Enter') {
811
+ event.preventDefault();
812
+ expand();
813
+ }
814
+ },
815
+ }
816
+ : {})}
817
+ >
818
+ <SubjectThumbnail
819
+ render={previewBody ? previewBody.render : null}
820
+ glyph={firstSectionGlyph(subject)}
821
+ subjectKey={subject.id}
822
+ />
823
+ </div>
824
+ {inIdentity ? (
825
+ <div className="vgai-mini-inspector-identity">
826
+ <SubjectNameField subject={subject} />
827
+ {actionsPlacedAt(subject, 'identity').map((action) => (
828
+ <QuickActionIconButton key={action.id} action={action} />
829
+ ))}
830
+ </div>
831
+ ) : (
832
+ <div className="vgai-mini-inspector-identity">
833
+ <div style={{ flex: 1, minWidth: 0, fontSize: 'var(--vgai-font-md)', fontWeight: 600 }}>
834
+ {subject.title}
835
+ </div>
836
+ </div>
837
+ )}
838
+ <SectionIconStrip subject={subject} onJump={() => expand?.()} />
839
+ {definition && (
840
+ <div className="vgai-mini-inspector-actions">
841
+ <EditDefinitionButton link={definition} />
842
+ </div>
843
+ )}
844
+ </div>
845
+ );
846
+ }
847
+
848
+ /**
849
+ * The PILL — the floor: a single line carrying the subject's thumb-glyph, its
850
+ * name, and an expand affordance. The whole lozenge restores the column;
851
+ * `onClear` is the minimized state's only route to deselect (there is no
852
+ * viewport gesture reaching a minimized box).
853
+ */
854
+ export function InspectorPill({
855
+ subject,
856
+ onExpand,
857
+ onClear,
858
+ }: {
859
+ readonly subject: InspectionSubject;
860
+ readonly onExpand: () => void;
861
+ readonly onClear?: (() => void) | undefined;
862
+ }) {
863
+ const previewSection = subject.sections.find((section) => section.body.kind === 'preview');
864
+ const previewBody = previewSection?.body.kind === 'preview' ? previewSection.body : null;
865
+ return (
866
+ <div className="vgai-inspector-pill" data-testid="inspector-pill">
867
+ {/* The role-annotated div is the sanctioned clickable-card idiom — a raw
868
+ native control here trips the design-system product-chrome scan. */}
869
+ <div
870
+ role="button"
871
+ tabIndex={0}
872
+ className="vgai-inspector-pill-open"
873
+ aria-label={`Expand inspector for ${subject.title}`}
874
+ title={`Expand inspector for ${subject.title}`}
875
+ onClick={onExpand}
876
+ onKeyDown={(event) => {
877
+ if (event.key === ' ' || event.key === 'Enter') {
878
+ event.preventDefault();
879
+ onExpand();
880
+ }
881
+ }}
882
+ >
883
+ <span className="vgai-inspector-pill-thumb" aria-hidden="true">
884
+ {previewBody ? (
885
+ previewBody.render('thumbnail')
886
+ ) : (
887
+ <EditorIcon icon={firstSectionGlyph(subject)} />
888
+ )}
889
+ </span>
890
+ <span className="vgai-inspector-pill-name">{subject.title}</span>
891
+ </div>
892
+ {onClear && (
893
+ <IconButton
894
+ data-testid="inspector-fab-clear-selection"
895
+ aria-label="Clear selection"
896
+ title="Clear selection"
897
+ onClick={onClear}
898
+ >
899
+ <EditorIcon icon={faXmark} />
900
+ </IconButton>
901
+ )}
902
+ </div>
903
+ );
904
+ }
905
+
906
+ export interface InspectionProjectionProps {
907
+ readonly subject: InspectionSubject;
908
+ /** Which layout to render. The live shell reads this off the composed
909
+ * subject through `inspection/display.ts`; it is never re-derived here. */
910
+ readonly presentation: InspectionPresentation;
911
+ /** The surface this projection is showing, when it is the live shell's —
912
+ * the key the presentation-switch affordances write their override under.
913
+ * Omitted in bounded design-system hosts, which offer no switch. */
914
+ readonly surface?: InspectionSurfaceKind;
915
+ }
916
+
917
+ /**
918
+ * Column scroll memory across the RE-PROJECTION remount. A source write's HMR
919
+ * re-projection drops the selection for ~a beat (the measured, unpinned drop
920
+ * `react-world-authoring-adapter.ts#scheduleSelectionRepair` documents), which
921
+ * unmounts this panel entirely; the repair restores the SAME subject and the
922
+ * fresh mount landed at the top — measured live: the column node is REPLACED
923
+ * and `scrollTop` resets to 0 within 600ms of any style write, which the
924
+ * blind walker read as "every Fill edit scrolls the inspector back to top"
925
+ * (Fill just sits below the fold). Keyed by subject id, so a genuinely new
926
+ * selection still opens at the top while the same subject keeps its place.
927
+ */
928
+ const columnScrollMemory = new Map<string, number>();
929
+
930
+ /** ONE subject, projected. */
931
+ export function InspectionProjectionView({
932
+ subject,
933
+ presentation,
934
+ surface,
935
+ }: InspectionProjectionProps) {
936
+ const subjectId = subject.id;
937
+ const restoreScroll = useCallback(
938
+ (el: HTMLDivElement | null) => {
939
+ if (el) el.scrollTop = columnScrollMemory.get(subjectId) ?? 0;
940
+ },
941
+ [subjectId],
942
+ );
943
+ if (presentation === 'card') {
944
+ return <MiniInspectorCard subject={subject} {...(surface ? { surface } : {})} />;
945
+ }
946
+ return (
947
+ <Panel
948
+ name="Inspector"
949
+ hideHeader
950
+ className="vgai-content-frost"
951
+ data-testid="inspector-panel"
952
+ data-vgai-inspector-presentation={presentation}
953
+ onPointerDown={() => setActiveScope('inspector')}
954
+ style={{ overflowY: 'auto' }}
955
+ ref={restoreScroll}
956
+ onScroll={(e: ReactUIEvent<HTMLDivElement>) => {
957
+ if (columnScrollMemory.size > 200) columnScrollMemory.clear();
958
+ columnScrollMemory.set(subjectId, e.currentTarget.scrollTop);
959
+ }}
960
+ >
961
+ {presentation === 'properties' ? (
962
+ <PropertiesColumn subject={subject} />
963
+ ) : (
964
+ <NarrowColumn subject={subject} />
965
+ )}
966
+ </Panel>
967
+ );
968
+ }