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