@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,842 @@
1
+ import { collectAllNodeIds } from './authoring/active-adapter';
2
+ import {
3
+ copyAuthoringNodes,
4
+ cutAuthoringNodes,
5
+ duplicateAuthoringNode,
6
+ groupAuthoringNodes,
7
+ pasteAuthoringNodes,
8
+ removeAuthoringNode,
9
+ removeManyAuthoringNodes,
10
+ setAuthoringSelection,
11
+ unwrapAuthoringNode,
12
+ wrapAuthoringNode,
13
+ } from './authoring/consumer-actions';
14
+ import { resolvePanelAuthoring } from './authoring/panel-authoring';
15
+ import {
16
+ currentSelectionScopeId,
17
+ enterSelectedScope,
18
+ exitSelectionScope,
19
+ } from './authoring/selection-scope';
20
+ import { activeSelectionIds, saveActiveAuthoring } from './authoring/shell-document-ops';
21
+ import { resetRootPan } from './authoring/world-pan-state';
22
+ import {
23
+ boxEditForId,
24
+ computeArrowNudgePatch,
25
+ readSpacingValues,
26
+ rectForId,
27
+ } from './components/world-overlay-gestures';
28
+ import { openCommandPalette } from './editor-commands';
29
+ import { editorConsole } from './editor-console';
30
+ import type { EditorShellStore } from './editor-shell-store';
31
+ import type { EditorViewport } from './editor-viewport';
32
+ import type { HistoryCommands } from './history/history-commands';
33
+ import {
34
+ allowsAppUndoWhileEditable,
35
+ getActiveScope,
36
+ type HotkeyBinding,
37
+ installHotkeys,
38
+ isEditableTarget,
39
+ registerHotkeys,
40
+ setActiveScope,
41
+ } from './hotkeys';
42
+ import { type KeyActionScope, registerKeyAction } from './key-actions';
43
+ import {
44
+ type EditorKeyActionId,
45
+ keyChordsFor,
46
+ shortcutFor,
47
+ subscribeEditorKeymap,
48
+ } from './keymap-presets';
49
+ import { stopAllLiveSessions } from './live-session-registry';
50
+ import { requestTransformMode } from './transform-mode-request';
51
+ import { showTransientHint } from './transient-hint';
52
+ import { saveActiveWorkspaceDocument } from './workspace-document-registry';
53
+ import { toggleWorkspaceFocus } from './workspace-host-commands';
54
+ import { cycleEditorWorkspace } from './workspace-presets';
55
+ import { toggleConsoleUtility } from './workspace-utility-commands';
56
+
57
+ // 'inspector' belongs here: the inspector shows the SELECTED NODE, so a user
58
+ // who just edited a field expects node-scoped clipboard/duplicate to still
59
+ // answer (a human pressed Ctrl+D after typing a color and read the silence as
60
+ // "duplicate is broken" — runhuman pass 41). Focused text inputs stay safe:
61
+ // isEditableTarget refuses there, and clipboard keys keep their native-DOM
62
+ // `when` guard.
63
+ const AUTHORING_SELECTION_SCOPES = ['viewport', 'hierarchy', 'inspector'] as const;
64
+
65
+ /**
66
+ * D4 (spec27 §6 D4, §8 "Keyboard / undo / pan-zoom" row) — RECONCILED by the
67
+ * `overlay-panzoom` unit (D-E's own reconciliation note).
68
+ *
69
+ * Space-pan is BUILT: `RootSelectionOverlay.tsx`'s own Space-held +
70
+ * pointer-drag handling (its `spaceHeldRef`/`panDragRef`), writing the shared
71
+ * `world-pan-state.ts` singleton, which `design-time-layers.ts` applies to
72
+ * every world-layer host IN LOCKSTEP with the overlay's own wrapper — a pure
73
+ * CSS `transform: translate()` on both. Not registered here in
74
+ * `editor-hotkeys.ts`'s `registerHotkeys` array because that registry is a
75
+ * single-fire-per-keydown dispatcher with no "held" concept (see
76
+ * `hotkeys.ts`); Space-pan needs a literal hold state, so it follows the
77
+ * SAME precedent this file's own vertex-snap `V`-key handling below already
78
+ * set (a raw `window` keydown/keyup pair) — just owned by the overlay
79
+ * component instead of this file, since the gesture IS a pointer-drag over
80
+ * the overlay's own interaction layer, not a stateless action like
81
+ * duplicate/delete/nudge above.
82
+ *
83
+ * Zoom-fit/selection is a stated EXCLUDE — evidence checked (not assumed):
84
+ * the 3D viewport (`world-root-stage.ts`/`editor-viewport.ts`) already has a
85
+ * real zoom substrate (Three.js `OrbitControls`, numpad view presets), so
86
+ * zoom-fit there is pure duplication, not a gap. The react/DOM world
87
+ * surface's `RectProvider.rect(id)` is `getBoundingClientRect()`-derived and
88
+ * host-relative (`react-world-authoring-adapter.ts`'s `toHostRelative`) —
89
+ * UNLIKE pan (a pure translation, which cancels out of that subtraction with
90
+ * zero consumer changes, per `world-pan-state.ts`'s own soundness doc
91
+ * comment), a host `scale()` bakes the zoom factor into every rect AND every
92
+ * screen-space gesture delta (`toHostLocal`), so it would require an
93
+ * explicit zoom-aware conversion at all ~14 host-relative chrome consumers
94
+ * (selection/hover box, 8 handles, rotate, labels, spacing bands, measure
95
+ * lines, snap guides, badges, parent outline, drag-ghost, reorder marker,
96
+ * empty-container hints, align-toolbar anchor, marquee) and the 3
97
+ * screen-delta gesture patches (`computeResizePatch`/`computeMovePatch`/
98
+ * `computeSpacingPatch`) — a foundation rebase against the ~2360-test unit
99
+ * suite + the full 27x e2e series (all pinned at the implicit `scale=1`
100
+ * frame) that is disproportionate to one keyboard-row affordance the
101
+ * visual-edit reference itself only had live in its own demo host, not its
102
+ * reusable library. No dead keybinding is registered for it.
103
+ */
104
+
105
+ // The five action functions below (`duplicateSelection` … `nudgeSelection`)
106
+ // are exported for two reasons: `registerEditorShellHotkeys` wires them to
107
+ // their keybindings below, AND `editor-hotkeys.test.ts` unit-tests the
108
+ // capability-gate behavior (structure/boxEdit absent -> no-op) directly
109
+ // against them, without needing a full `EditorViewport`/canvas fixture.
110
+
111
+ /** Entity clipboard shortcuts yield to ordinary DOM text selection. Editable
112
+ * controls already yield in `hotkeys.ts`; this covers selectable labels/logs
113
+ * inside the hierarchy or viewport scopes. */
114
+ export function shouldHandleEditorClipboard(): boolean {
115
+ return (globalThis.getSelection?.()?.toString() ?? '').length === 0;
116
+ }
117
+
118
+ /**
119
+ * THE SUBJECT THESE ACTIONS ACT ON — the ACTIVE DOCUMENT's selection, which is
120
+ * the same place they already take their adapter from.
121
+ *
122
+ * `store.selectedEntityIds` is the SHELL store's set, and for a document whose
123
+ * adapter owns its own selection it is a different set entirely: every one of
124
+ * these functions resolved its adapter through `resolvePanelAuthoring` and then
125
+ * read the ids from the shell, so on such a document they saw an empty
126
+ * selection and returned at the guard — a silent no-op with nothing to report.
127
+ *
128
+ * MEASURED 2026-09-21 on the Blender Model document, whose Outliner adapter
129
+ * publishes its selection in Blender's own row ids: with the Sphere selected
130
+ * and highlighted in the panel, `vgai.edit.delete` ran, found
131
+ * `store.selectedEntityIds` empty and deleted nothing — X and Shift+D were
132
+ * dead for every object on that document. `activeSelectionIds` is what
133
+ * `editor.status()` already reports and what the hierarchy panel already
134
+ * draws, so reading it here makes the three agree instead of two against one.
135
+ */
136
+ function actionSelectionIds(store: EditorShellStore): string[] {
137
+ return activeSelectionIds(store);
138
+ }
139
+
140
+ export async function copySelection(store: EditorShellStore): Promise<void> {
141
+ const ids = actionSelectionIds(store);
142
+ if (ids.length === 0) return;
143
+ await copyAuthoringNodes(resolvePanelAuthoring(store).adapter, ids);
144
+ }
145
+
146
+ export async function cutSelection(store: EditorShellStore): Promise<void> {
147
+ const ids = actionSelectionIds(store);
148
+ if (ids.length === 0) return;
149
+ await cutAuthoringNodes(resolvePanelAuthoring(store).adapter, ids);
150
+ }
151
+
152
+ /** Ordinary Paste creates siblings: the current selection's native parent is
153
+ * the destination. With no selection, the adapter's document root is used. */
154
+ export async function pasteSelection(store: EditorShellStore): Promise<void> {
155
+ const adapter = resolvePanelAuthoring(store).adapter;
156
+ const selectedId = actionSelectionIds(store)[0];
157
+ const parentId = selectedId ? (adapter.hierarchy.node(selectedId)?.parentId ?? null) : null;
158
+ await pasteAuthoringNodes(adapter, parentId);
159
+ }
160
+
161
+ /**
162
+ * D4 (spec27 §6 D4 "Ctrl+D duplicate, Del/Backspace delete — route through
163
+ * adapter.structure.* (capability-gated)"). `store.selectedEntityIds` is the
164
+ * shared, adapter-routed selection set (every adapter's own `selection.set`
165
+ * funnels into it); the ACTION goes through the active adapter's
166
+ * `StructureProvider`, and is a capability-gated no-op when that adapter has
167
+ * none (e.g. the ingested `react-dom` adapter, spec:308-309).
168
+ *
169
+ * There is no fork here: every adapter is reached the same way, through its
170
+ * `StructureProvider`.
171
+ *
172
+ * Each id's ack is AWAITED before the next fires, exactly as `deleteSelection`
173
+ * awaits `remove`: for a source lane every duplicate in the loop rewrites the
174
+ * SAME file, and two un-awaited writes each read it before either wrote back,
175
+ * so whichever landed second silently loses the first. That is why the id and
176
+ * the ack come back together (`StructuralIdWrite`) — a synchronous id with a
177
+ * `void`ed write would leave this loop nothing to await. Awaiting a lane whose
178
+ * duplicate is in-memory (`ack: undefined`) is a harmless no-op.
179
+ */
180
+ /**
181
+ * SERIALIZED ACROSS PRESSES. One invocation already awaits each id's write,
182
+ * but a BURST of Ctrl+D presses ran concurrently: every press read the same
183
+ * pre-write selection and the same stale source, so five presses produced five
184
+ * copies with one name at one position, stacked (runhuman pass 66 —
185
+ * "they are all stacked somewhere together... the names are HeroBox 2, 2, 2").
186
+ * Queuing makes each press start from what the previous one produced, which is
187
+ * also what a human means by pressing it again: a chain of copies, each named
188
+ * and offset from the last.
189
+ */
190
+ let duplicateQueue: Promise<void> = Promise.resolve();
191
+
192
+ export function duplicateSelection(store: EditorShellStore): Promise<void> {
193
+ const queued = duplicateQueue.then(() => runDuplicateSelection(store));
194
+ // The queue never inherits a rejection — one failed duplicate must not
195
+ // strand every later press (the commit-queue rule in the canvas lane).
196
+ duplicateQueue = queued.then(
197
+ () => undefined,
198
+ () => undefined,
199
+ );
200
+ return queued;
201
+ }
202
+
203
+ async function runDuplicateSelection(store: EditorShellStore): Promise<void> {
204
+ const selection = actionSelectionIds(store);
205
+ if (selection.length === 0) return;
206
+ const adapter = resolvePanelAuthoring(store).adapter;
207
+ const structure = adapter.structure;
208
+ if (!structure) return;
209
+ // Immediate acknowledgement: a source-lane duplicate takes a visible moment
210
+ // (write + remount), and the silence in between reads as a dead hotkey — a
211
+ // human pressed Ctrl+D repeatedly and ended up with three copies (runhuman
212
+ // pass 41). The hint is published BEFORE the first await and replaced by the
213
+ // done line when the writes land.
214
+ const label =
215
+ selection.length === 1
216
+ ? (adapter.hierarchy.node(selection[0] as string)?.label ?? 'selection')
217
+ : `${selection.length} objects`;
218
+ showTransientHint(`Duplicating ${label}…`);
219
+ for (const id of selection) await duplicateAuthoringNode(adapter, id).ack;
220
+ showTransientHint(`Duplicated ${label}`);
221
+ }
222
+
223
+ /**
224
+ * D4.R2 (spec27 §6 D4 reopen) — a multi-node override delete prefers the
225
+ * batched `structure.removeMany` (ONE undo entry for the whole selection,
226
+ * matching the first-party `store.deleteEntities` batched-undo semantics
227
+ * below) when the active adapter implements it; falls back to the per-id
228
+ * `structure.remove` loop for any adapter that doesn't (N undo entries).
229
+ * `await`ed the same way `remove` is (harmless no-op for a synchronous/
230
+ * in-memory implementer) — see `StructureProvider.removeMany`'s doc comment.
231
+ *
232
+ * SOURCE-CORRUPTION FIX (bug-panel reopen, post-D4.R2) + DELETE-ORDER-
233
+ * RESIDUAL FIX (bug-panel follow-up, post-50f90a6d): the per-id fallback loop
234
+ * used to fire `structure.remove(id)` for every id in RAW selection order —
235
+ * document/top-first, the same order a marquee/DFS walk produces — with NO
236
+ * `await` between iterations. For an adapter whose per-op target offset is
237
+ * resolved from an index that is only refreshed by a real reparse (never
238
+ * between same-file writes — `ReactRootAuthoringAdapter`'s `OidStore.index`),
239
+ * that is a real, default-gesture-reachable source-corruption hazard:
240
+ * deleting an EARLIER element first shifts every LATER element's still-stale
241
+ * `{line, col}` out from under it, and firing every write in the same tick
242
+ * can race the underlying network request, letting a later write silently
243
+ * clobber an earlier one. Both mechanisms are fixed for the fallback loop,
244
+ * together (see the CORRECTNESS INVARIANT comment on
245
+ * `ReactRootAuthoringAdapter.structure`'s per-id `remove` for the full
246
+ * soundness argument, including its own remaining caveat):
247
+ * 1. sort `ids` into REVERSE DOCUMENT ORDER (bottom-most/deepest first) —
248
+ * derived from the SAME DFS pre-order `collectAllNodeIds` walks (the
249
+ * marquee/select-all candidate order), reversed, so a selected
250
+ * descendant is always deleted before a selected ancestor;
251
+ * 2. `await` each `structure.remove(id)` before starting the next, so
252
+ * write N only ever begins once write N-1 has landed on disk.
253
+ * A synchronous/in-memory `remove` (e.g. `UIAuthoringAdapter`'s) is
254
+ * unaffected — awaiting a non-promise return is a harmless no-op, and its own
255
+ * `removeMany` batches instead of ever reaching this loop. This fallback loop
256
+ * is sound ONLY when `collectAllNodeIds`' walk order matches true SOURCE
257
+ * order — `ReactRootAuthoringAdapter` now implements `removeMany` (a single
258
+ * shared-snapshot batched delete, order-independent by construction) exactly
259
+ * so THIS loop is never reached for it; it stays here, documented and tested,
260
+ * as the honest degrade for any override adapter that has no batched delete.
261
+ */
262
+ export async function deleteSelection(store: EditorShellStore): Promise<void> {
263
+ const ids = actionSelectionIds(store);
264
+ if (ids.length === 0) return;
265
+ const adapter = resolvePanelAuthoring(store).adapter;
266
+ const structure = adapter.structure;
267
+ if (!structure) return;
268
+ if (structure.removeMany) {
269
+ await removeManyAuthoringNodes(adapter, ids);
270
+ } else {
271
+ // Reverse DOCUMENT order (bottom-most/deepest first) — see this
272
+ // function's own doc comment. `collectAllNodeIds` walks the adapter's
273
+ // hierarchy in the same DFS pre-order (parent-before-child, top-first)
274
+ // a marquee/select-all uses; an id outside that walk (should not
275
+ // happen — every selected id came FROM this adapter's own hierarchy)
276
+ // sorts last (rank -1) as a defensive fallback, never a throw.
277
+ const rank = new Map(collectAllNodeIds(adapter).map((id, i) => [id, i]));
278
+ const ordered = [...ids].sort((a, b) => (rank.get(b) ?? -1) - (rank.get(a) ?? -1));
279
+ for (const id of ordered) await removeAuthoringNode(adapter, id);
280
+ }
281
+ }
282
+
283
+ /** D4 — `Ctrl+Alt+G` wrap / `Ctrl+Shift+G` unwrap (spec §6 D4, §5 "wrap/
284
+ * unwrap ... via `structure.*`"): no first-party equivalent exists (a 3D
285
+ * entity can't be "wrapped"), so this is override-only, capability-gated on
286
+ * the OPTIONAL `structure.wrap`/`structure.unwrap` (absent for the
287
+ * ingested `react-dom` adapter and the first-party three adapter alike —
288
+ * both correctly no-op). Applies to every selected id, same multi-select
289
+ * loop `duplicateSelection`/`deleteSelection` use. */
290
+ /** Awaited per id for the same reason `deleteSelection` awaits `remove`: two
291
+ * in-flight structural writes against the SAME source file each read it before
292
+ * either wrote back, and whichever lands second silently loses the first. The
293
+ * awaited value is that edit's own `{destination, persisted}` ack. */
294
+ export async function wrapSelection(store: EditorShellStore): Promise<void> {
295
+ const ids = actionSelectionIds(store);
296
+ if (ids.length === 0) return;
297
+ const adapter = resolvePanelAuthoring(store).adapter;
298
+ const structure = adapter.structure;
299
+ if (!structure?.wrap) return;
300
+ for (const id of ids) await wrapAuthoringNode(adapter, id);
301
+ }
302
+
303
+ export async function unwrapSelection(store: EditorShellStore): Promise<void> {
304
+ const ids = actionSelectionIds(store);
305
+ if (ids.length === 0) return;
306
+ const adapter = resolvePanelAuthoring(store).adapter;
307
+ const structure = adapter.structure;
308
+ if (!structure?.unwrap) return;
309
+ for (const id of ids) await unwrapAuthoringNode(adapter, id);
310
+ }
311
+
312
+ /** Group the current selection as one real spatial parent when its adapter
313
+ * supports lossless grouping. One provider call owns one undo entry, and its
314
+ * ack is awaited the same way `wrapSelection` awaits `wrap` — a caller that
315
+ * awaits this function has awaited the byte. */
316
+ export async function groupSelection(store: EditorShellStore): Promise<void> {
317
+ const ids = actionSelectionIds(store);
318
+ if (ids.length === 0) return;
319
+ const adapter = resolvePanelAuthoring(store).adapter;
320
+ if (!adapter.structure?.group) return;
321
+ await groupAuthoringNodes(adapter, ids).ack;
322
+ }
323
+
324
+ /**
325
+ * D4.c (spec §6 D4 "arrow-nudge (↑↓←→, Shift=×10) ... writes margins/
326
+ * offsets"): nudges every currently-selected id whose owner exposes
327
+ * `boxEdit` (capability-gated — silently skips ids that don't, e.g. a 3D
328
+ * entity or an ingested-world node with no spatial write path). Position/
329
+ * margin math is the pure, independently-tested `computeArrowNudgePatch`
330
+ * (`world-overlay-gestures.ts`); this function only resolves the per-id
331
+ * rect/position/margin inputs it needs and brackets the write in a single
332
+ * `begin`/`apply`/`end` (one undo step per nudged node, per B1/D-1).
333
+ */
334
+ export function nudgeSelection(store: EditorShellStore, dx: number, dy: number): void {
335
+ const ids = actionSelectionIds(store);
336
+ if (ids.length === 0) return;
337
+ const adapter = resolvePanelAuthoring(store).adapter;
338
+ for (const id of ids) {
339
+ const boxEdit = boxEditForId(adapter, id);
340
+ if (!boxEdit) continue;
341
+ const rect = rectForId(adapter, id);
342
+ if (!rect) continue;
343
+ const position = adapter.inspector?.get(id, 'style.position');
344
+ const isPositioned = position === 'absolute' || position === 'fixed';
345
+ const spacing = readSpacingValues(adapter, id);
346
+ const patch = computeArrowNudgePatch(dx, dy, isPositioned, rect, {
347
+ left: spacing.marginLeft,
348
+ top: spacing.marginTop,
349
+ });
350
+ if (Object.keys(patch).length === 0) continue;
351
+ boxEdit.begin(id);
352
+ boxEdit.apply(id, patch);
353
+ boxEdit.end(id);
354
+ }
355
+ }
356
+
357
+ // ---------------------------------------------------------------------------
358
+ // The keymap seam
359
+ // ---------------------------------------------------------------------------
360
+ // No binding below spells a literal key. Each names a LOGICAL ACTION and
361
+ // `keymap-presets.ts` supplies the chords the active keymap assigns it, so
362
+ // `vgai` and `blender` are two tables rather than two code paths. See that
363
+ // module's header for the ruling, the collision resolutions, and the boundary
364
+ // against play-mode input isolation (which this seam does not touch).
365
+
366
+ /** How the FRAME reads a binding's scope when it turns one into a `when`
367
+ * clause (`key-actions.ts`): the viewport is the stage, the
368
+ * selection panels are the editor's own parts, global is everywhere in it. */
369
+ function frameScopeOf(scope: HotkeyBinding['scope']): KeyActionScope {
370
+ const scopes = Array.isArray(scope) ? scope : [scope];
371
+ if (scopes.includes('global')) return 'global';
372
+ if (scopes.length === 1 && scopes[0] === 'viewport') return 'stage';
373
+ return 'panel';
374
+ }
375
+
376
+ /**
377
+ * The action ids one `build()` pass registered, collected so the pass's
378
+ * disposal can take them back out. `bind` is the one place an action's body
379
+ * is written, so it is also the one place the frame's table is filled; the
380
+ * collector is how a call inside the build closure reaches the registration
381
+ * that owns it without every call site threading it.
382
+ */
383
+ let keyActionCollector: (() => void)[] | null = null;
384
+
385
+ /** Expand one logical action into the dispatcher's per-chord binding rows,
386
+ * and record what the action DOES in the chord-independent table the frame
387
+ * dispatches through (`key-actions.ts`). */
388
+ function bind(
389
+ action: EditorKeyActionId,
390
+ binding: Omit<HotkeyBinding, 'key' | 'code' | 'mod' | 'shift' | 'alt'> & {
391
+ /** This binding's `when`, in the form the FRAME can ask — no event. Set
392
+ * it wherever `when` ignores its argument; see `KeyActionEntry.enabled`. */
393
+ frameEnabled?: () => boolean;
394
+ },
395
+ ): HotkeyBinding[] {
396
+ const { frameEnabled, ...row } = binding;
397
+ keyActionCollector?.push(
398
+ registerKeyAction({
399
+ id: action,
400
+ run: (event) => row.action(event ?? new KeyboardEvent('keydown')),
401
+ scope: frameScopeOf(row.scope),
402
+ ...(frameEnabled ? { enabled: frameEnabled } : {}),
403
+ }),
404
+ );
405
+ return keyChordsFor(action).map((chord) => ({ ...row, ...chord }));
406
+ }
407
+
408
+ /**
409
+ * Register a binding set that REBUILDS on a keymap switch — the "live, no
410
+ * reload" half of the setting. Nothing else re-registers: the actions are the
411
+ * same objects, only their chords move.
412
+ *
413
+ * The action TABLE is filled by the same pass, and it is filled whoever owns
414
+ * the keyboard: under the frame the chord rows below go unread (the host
415
+ * dispatcher is never installed — `hotkeys.ts`) while the table is what every
416
+ * `vgai.*` VS Code command dispatches into.
417
+ */
418
+ function registerKeymapBindings(build: () => HotkeyBinding[]): () => void {
419
+ const runBuild = (): { rows: HotkeyBinding[]; actions: (() => void)[] } => {
420
+ const actions: (() => void)[] = [];
421
+ const previous = keyActionCollector;
422
+ keyActionCollector = actions;
423
+ try {
424
+ return { rows: build(), actions };
425
+ } finally {
426
+ keyActionCollector = previous;
427
+ }
428
+ };
429
+ let pass = runBuild();
430
+ let dispose = registerHotkeys(pass.rows);
431
+ const disposePass = (): void => {
432
+ dispose();
433
+ for (const drop of pass.actions) drop();
434
+ };
435
+ const unsubscribe = subscribeEditorKeymap(() => {
436
+ disposePass();
437
+ pass = runBuild();
438
+ dispose = registerHotkeys(pass.rows);
439
+ });
440
+ return () => {
441
+ unsubscribe();
442
+ disposePass();
443
+ };
444
+ }
445
+
446
+ /**
447
+ * Shell-lifetime bindings: undo/redo, save, clipboard, delete, command palette.
448
+ * These do not need a Three viewport. Canvas-only projects (Bubbo isolation
449
+ * documents, first-party Pixi worlds) never mount a three stage at all, so they
450
+ * only get these keys if THIS function is registered from the editor shell.
451
+ *
452
+ * `mod+Z` matches Ctrl OR Cmd (`hotkeys.ts`); on Mac the menu prints ⌘Z and
453
+ * both chords fire the same action.
454
+ */
455
+ export function registerEditorShellHotkeys(
456
+ store: EditorShellStore,
457
+ history: HistoryCommands,
458
+ ): () => void {
459
+ // NARRATED undo/redo: the hint names what just happened ("Undid Paste 3
460
+ // Entities"). Undoing a paste empties the selection (the pasted ids die
461
+ // with it), and the silent no-gizmo viewport that follows read as "a Play
462
+ // preview I had to click out of" to a human (runhuman passes 52/53).
463
+ const narratedUndo = (): void => {
464
+ const label = history.getSnapshot().undoLabel;
465
+ void history.undo().then((done) => {
466
+ if (done && label) showTransientHint(`Undid ${label}`);
467
+ });
468
+ };
469
+ const narratedRedo = (): void => {
470
+ const label = history.getSnapshot().redoLabel;
471
+ void history.redo().then((done) => {
472
+ if (done && label) showTransientHint(`Redid ${label}`);
473
+ });
474
+ };
475
+ const unregister = registerKeymapBindings(() => [
476
+ // --- Global (fire regardless of active panel) ---
477
+ // H2: one project-scoped async command queue owns shell undo/redo. Ctrl+Y
478
+ // mirrors Ctrl+Shift+Z (Windows convention) — both are chords of the one
479
+ // `edit.redo` action in the keymap table.
480
+ // Undo/redo also fire inside fields that opted into app undo
481
+ // (`APP_UNDO_PASSTHROUGH_ATTR` — per-keystroke-committing numeric inputs,
482
+ // where native text undo is a dead key); every other editable keeps the
483
+ // guard so typing retains the browser's own undo.
484
+ ...bind('edit.redo', {
485
+ scope: 'global',
486
+ allowInEditable: true,
487
+ when: (e) => !isEditableTarget(e.target) || allowsAppUndoWhileEditable(e.target),
488
+ action: narratedRedo,
489
+ }),
490
+ ...bind('edit.undo', {
491
+ scope: 'global',
492
+ allowInEditable: true,
493
+ when: (e) => !isEditableTarget(e.target) || allowsAppUndoWhileEditable(e.target),
494
+ action: narratedUndo,
495
+ }),
496
+ ...bind('edit.copy', {
497
+ scope: AUTHORING_SELECTION_SCOPES,
498
+ when: shouldHandleEditorClipboard,
499
+ frameEnabled: shouldHandleEditorClipboard,
500
+ action: () => void copySelection(store),
501
+ }),
502
+ ...bind('edit.cut', {
503
+ scope: AUTHORING_SELECTION_SCOPES,
504
+ when: shouldHandleEditorClipboard,
505
+ frameEnabled: shouldHandleEditorClipboard,
506
+ action: () => void cutSelection(store),
507
+ }),
508
+ ...bind('edit.paste', {
509
+ scope: AUTHORING_SELECTION_SCOPES,
510
+ when: shouldHandleEditorClipboard,
511
+ frameEnabled: shouldHandleEditorClipboard,
512
+ action: () => void pasteSelection(store),
513
+ }),
514
+ ...bind('edit.duplicate', {
515
+ scope: AUTHORING_SELECTION_SCOPES,
516
+ action: () => void duplicateSelection(store),
517
+ }),
518
+ // D4 — wrap/unwrap (override-only, capability-gated — see
519
+ // `wrapSelection`/`unwrapSelection`'s doc comments).
520
+ ...bind('edit.group', {
521
+ scope: AUTHORING_SELECTION_SCOPES,
522
+ action: () => void groupSelection(store),
523
+ }),
524
+ ...bind('edit.wrap', {
525
+ scope: AUTHORING_SELECTION_SCOPES,
526
+ action: () => wrapSelection(store),
527
+ }),
528
+ ...bind('edit.unwrap', {
529
+ scope: AUTHORING_SELECTION_SCOPES,
530
+ action: () => unwrapSelection(store),
531
+ }),
532
+ // D4 — arrow-nudge (↑↓←→, Shift=×10 — see `nudgeSelection`'s doc comment).
533
+ ...bind('edit.nudgeUp', { scope: 'viewport', action: () => nudgeSelection(store, 0, -1) }),
534
+ ...bind('edit.nudgeUpCoarse', {
535
+ scope: 'viewport',
536
+ action: () => nudgeSelection(store, 0, -10),
537
+ }),
538
+ ...bind('edit.nudgeDown', { scope: 'viewport', action: () => nudgeSelection(store, 0, 1) }),
539
+ ...bind('edit.nudgeDownCoarse', {
540
+ scope: 'viewport',
541
+ action: () => nudgeSelection(store, 0, 10),
542
+ }),
543
+ ...bind('edit.nudgeLeft', { scope: 'viewport', action: () => nudgeSelection(store, -1, 0) }),
544
+ ...bind('edit.nudgeLeftCoarse', {
545
+ scope: 'viewport',
546
+ action: () => nudgeSelection(store, -10, 0),
547
+ }),
548
+ ...bind('edit.nudgeRight', { scope: 'viewport', action: () => nudgeSelection(store, 1, 0) }),
549
+ ...bind('edit.nudgeRightCoarse', {
550
+ scope: 'viewport',
551
+ action: () => nudgeSelection(store, 10, 0),
552
+ }),
553
+ ...bind('edit.selectAll', {
554
+ scope: AUTHORING_SELECTION_SCOPES,
555
+ action: () => {
556
+ const a = resolvePanelAuthoring(store).adapter;
557
+ setAuthoringSelection(a, collectAllNodeIds(a));
558
+ },
559
+ }),
560
+ // Blender's Alt+A. Unbound in the vgai table, where Escape is the deselect
561
+ // (and carries the scope-exit verb with it); this one only clears.
562
+ ...bind('edit.deselectAll', {
563
+ scope: AUTHORING_SELECTION_SCOPES,
564
+ action: () => setAuthoringSelection(resolvePanelAuthoring(store).adapter, []),
565
+ }),
566
+ ...bind('edit.save', {
567
+ scope: 'global',
568
+ allowInEditable: true,
569
+ action: () => {
570
+ void saveActiveWorkspaceDocument().then((handled) => {
571
+ // Surfaces with no document-local editor fall back to the ACTIVE
572
+ // authoring adapter's own persistence provider.
573
+ if (handled === null) void saveActiveAuthoring(store);
574
+ });
575
+ },
576
+ }),
577
+ // D4 (spec27 §8 "space-pan" row) — Ctrl+0, the conventional "reset
578
+ // zoom/pan to 100%" shortcut (browsers/design tools alike), resets the
579
+ // DOM/react overlay's shared pan translate (`world-pan-state.ts`) back to
580
+ // `(0, 0)`. A no-op when already at rest or when the active surface has
581
+ // no pan-capable world (there is nothing for it to affect either way —
582
+ // `resetRootPan()` itself no-ops at `(0,0)`).
583
+ ...bind('view.resetPan', { scope: 'viewport', action: () => resetRootPan() }),
584
+ ...bind('edit.enterScope', {
585
+ scope: AUTHORING_SELECTION_SCOPES,
586
+ // While playing, Enter belongs to the GAME. The dispatcher preventDefaults
587
+ // every matched binding, so with this bound unconditionally the editor
588
+ // swallowed Enter during play and a game's own Enter binding could never
589
+ // fire — third-person's 'restart' is Enter, and its SQUAD DOWN card says
590
+ // 'PRESS ENTER TO REDEPLOY': a tester pressed it and 'Enter then redeploy
591
+ // doesn't work' (runhuman pass 122; an earlier reproduction had already
592
+ // measured Enter and Escape both exiting Play). Same gate shape as
593
+ // Escape's below, for the same reason.
594
+ when: () => store.playState === 'stopped',
595
+ frameEnabled: () => store.playState === 'stopped',
596
+ action: () => {
597
+ // The PANEL adapter, not the composite: `selection-scope.ts` keys its
598
+ // stack by adapter identity and the Hierarchy's breadcrumb/entry both
599
+ // resolve this one, so all three gestures name the same scope.
600
+ enterSelectedScope(resolvePanelAuthoring(store).adapter);
601
+ },
602
+ }),
603
+ ...bind('edit.delete', {
604
+ scope: AUTHORING_SELECTION_SCOPES,
605
+ action: () => void deleteSelection(store),
606
+ }),
607
+ ...bind('edit.exitScopeOrDeselect', {
608
+ scope: AUTHORING_SELECTION_SCOPES,
609
+ // While playing, Escape is owned by play-mode (stop on Escape) — don't also
610
+ // clear the editor selection out from under the running game. The `when`
611
+ // gate (NOT an action-internal no-op) is load-bearing: the dispatcher
612
+ // preventDefaults every MATCHED binding, and play-mode's own bubble-phase
613
+ // Escape guard (`shouldEscapeStopPlay`) reads `defaultPrevented` as
614
+ // "an overlay consumed this Escape" — an action no-op here would still
615
+ // preventDefault and poison that signal, leaving Escape unable to stop play.
616
+ when: () => currentSelectionScopeId() !== null || store.playState === 'stopped',
617
+ // AND UNDER THE FRAME THAT FALLTHROUGH DOES NOT EXIST, which is why the two
618
+ // gates differ (found by walk 2's beat 17, 2026-09-20). VS Code's keybinding
619
+ // service preventDefaults every rule whose `when` RESOLVES, before the command
620
+ // it names decides anything — so with a `vgai.edit.exitScopeOrDeselect` rule on
621
+ // Escape, play-mode's `window` listener never sees an Escape that is not already
622
+ // `defaultPrevented`. A `frameEnabled` that returned false during play therefore
623
+ // produced neither outcome: the command refused ("the editor has no live handler
624
+ // for it right now") and the keystroke was consumed, so Escape did nothing at all
625
+ // while play ran and only the transport's ■ could end an immersive Play.
626
+ // The frame's Escape is this ONE action, and it does what the gesture means: leave
627
+ // the current scope, or — with none to leave — leave PLAY.
628
+ frameEnabled: () => true,
629
+ action: () => {
630
+ // Same one resolution as the Enter binding above — with the composite
631
+ // here, `exitSelectionScope` saw a FOREIGN adapter and dropped the whole
632
+ // stack instead of popping one level.
633
+ const adapter = resolvePanelAuthoring(store).adapter;
634
+ if (exitSelectionScope(adapter)) return;
635
+ // Nothing to pop. Standalone this is only ever reached with play stopped
636
+ // (the `when` above), so it is the deselect it always was; under the frame
637
+ // it is also the Escape that ends a run, through the registry every lane
638
+ // registers into — the same door `stop` reaches, never play-mode by name
639
+ // (the editor does not import `@vgai/game`).
640
+ if (store.playState !== 'stopped') {
641
+ stopAllLiveSessions();
642
+ return;
643
+ }
644
+ store.select(null);
645
+ },
646
+ }),
647
+ ...bind('view.commandPalette', {
648
+ scope: 'global',
649
+ // THE PALETTE IS THE WORKBENCH'S. The frame hands over an opener that
650
+ // runs `workbench.action.showCommands`. (This action's own CHORD is
651
+ // unbound — ⌘K is VS Code's chord prefix, U6 — so this path is the
652
+ // command, run from the palette itself or from a keybinding a person
653
+ // made.)
654
+ action: () => {
655
+ if (!openCommandPalette()) {
656
+ editorConsole.warn(
657
+ 'The command palette has not handed over its opener yet — run "Volter Editor: Open Workspace" first, or press the workbench\'s own Show All Commands.',
658
+ );
659
+ }
660
+ },
661
+ }),
662
+ ...bind('view.toggleConsole', {
663
+ scope: 'global',
664
+ action: () => toggleConsoleUtility(),
665
+ }),
666
+ // W5 (§6.2 focus/maximize mode) — Ctrl/Cmd+Shift+F. SHORTCUT DECISION,
667
+ // recorded: the benchmarks are Unity's Shift+Space (maximize view under
668
+ // cursor) and VS Code zen's Ctrl+K Z chord — but this registry is
669
+ // single-fire and chord-less, and Space already participates in the
670
+ // overlay's space-pan HOLD gesture (`RootSelectionOverlay`), so both
671
+ // benchmarks' literal keys would collide. mod+shift+F ("Focus") is free
672
+ // in this registry and unclaimed by Chromium on an app page.
673
+ ...bind('view.focusMode', {
674
+ scope: 'global',
675
+ action: () => toggleWorkspaceFocus(),
676
+ }),
677
+ // NAMED WORKSPACES' cycle pair (ARCHITECTURE-CORE §Editor chrome asks for
678
+ // "a cycle shortcut"). Ctrl/Cmd+PageDown / PageUp is BLENDER'S OWN
679
+ // workspace cycle — the vocabulary rule (docs/BLENDER-PARITY.md) applies
680
+ // to the modeling half of the editor, and both keys are unclaimed here,
681
+ // so both keymaps keep them.
682
+ ...bind('workspace.cycleNext', {
683
+ scope: 'global',
684
+ action: () => {
685
+ cycleEditorWorkspace(1);
686
+ },
687
+ }),
688
+ ...bind('workspace.cyclePrevious', {
689
+ scope: 'global',
690
+ action: () => {
691
+ cycleEditorWorkspace(-1);
692
+ },
693
+ }),
694
+ ]);
695
+ installHotkeys();
696
+ return unregister;
697
+ }
698
+
699
+ /**
700
+ * Three-viewport bindings (the gizmo-mode trio, numpad views, vertex snap).
701
+ * Only the 3D Scene document has an `EditorViewport`; canvas isolation
702
+ * documents do not. Which keys the trio lands on is the active KEYMAP's call
703
+ * (`keymap-presets.ts`): W/E/R under `vgai`, G/R/S under `blender`.
704
+ */
705
+ export function registerEditorViewportHotkeys(
706
+ store: EditorShellStore,
707
+ viewport: EditorViewport,
708
+ canvas: HTMLCanvasElement,
709
+ ): () => void {
710
+ const unregister = registerKeymapBindings(() => [
711
+ // --- Viewport-scoped (only when viewport/canvas is active) ---
712
+ ...bind('transform.select', {
713
+ scope: 'viewport',
714
+ action: () => {
715
+ if (!viewport.isFlying) requestTransformMode(store, 'select');
716
+ },
717
+ }),
718
+ ...bind('transform.combined', {
719
+ scope: 'viewport',
720
+ action: () => {
721
+ if (!viewport.isFlying) requestTransformMode(store, 'combined');
722
+ },
723
+ }),
724
+ ...bind('transform.translate', {
725
+ scope: 'viewport',
726
+ action: () => {
727
+ if (!viewport.isFlying) requestTransformMode(store, 'translate');
728
+ },
729
+ }),
730
+ ...bind('transform.rotate', {
731
+ scope: 'viewport',
732
+ action: () => {
733
+ if (!viewport.isFlying) requestTransformMode(store, 'rotate');
734
+ },
735
+ }),
736
+ ...bind('transform.scale', {
737
+ scope: 'viewport',
738
+ action: () => {
739
+ if (!viewport.isFlying) requestTransformMode(store, 'scale');
740
+ },
741
+ }),
742
+ // Snap has a key and says what it did: a human build session asked "how
743
+ // do you toggle the snap" with the magnet button on screen the whole time.
744
+ // The hint names the ACTIVE keymap's key, never a remembered literal.
745
+ // Holding Ctrl/⌘ during a drag snaps temporarily (editor-viewport.ts).
746
+ ...bind('viewport.toggleSnap', {
747
+ scope: 'viewport',
748
+ action: () => {
749
+ store.toggleSnap();
750
+ const { translate, rotate, scale } = store.snapValues;
751
+ const toggles = `${shortcutFor('viewport.toggleSnap') ?? ''} toggles; hold Ctrl while dragging to snap once`;
752
+ showTransientHint(
753
+ store.snapEnabled
754
+ ? `Snap on — ${translate} units, ${rotate}°, ×${scale} (${toggles})`
755
+ : `Snap off (${toggles})`,
756
+ );
757
+ },
758
+ }),
759
+ ...bind('viewport.frameSelection', {
760
+ scope: 'viewport',
761
+ action: () => store.focusOnSelection(),
762
+ }),
763
+ ...bind('viewport.cyclePivot', {
764
+ scope: 'viewport',
765
+ action: () => {
766
+ const pivotModes = ['active-element', 'median-point', 'individual-origins'] as const;
767
+ const idx = pivotModes.indexOf(store.pivotMode);
768
+ store.setPivotMode(pivotModes[(idx + 1) % pivotModes.length]!);
769
+ },
770
+ }),
771
+ ...bind('view.top', { scope: 'viewport', action: () => viewport.setViewPreset('top') }),
772
+ ...bind('view.front', { scope: 'viewport', action: () => viewport.setViewPreset('front') }),
773
+ ...bind('view.right', { scope: 'viewport', action: () => viewport.setViewPreset('right') }),
774
+ ...bind('view.perspective', {
775
+ scope: 'viewport',
776
+ action: () => viewport.setViewPreset('perspective'),
777
+ }),
778
+ ...bind('viewport.snapToFloor', {
779
+ scope: 'viewport',
780
+ action: () => viewport.snapSelectionToFloor(),
781
+ }),
782
+ ]);
783
+
784
+ const onPointerDown = () => setActiveScope('viewport');
785
+ canvas.addEventListener('pointerdown', onPointerDown);
786
+ installHotkeys();
787
+
788
+ // Vertex snap: HOLD to activate. Not a `registerHotkeys` row because that
789
+ // dispatcher is single-fire-per-keydown with no held concept, so this pair
790
+ // reads the same keymap entry directly rather than a literal key.
791
+ const isVertexSnapKey = (e: KeyboardEvent): boolean =>
792
+ keyChordsFor('viewport.vertexSnapHold').some((chord) => chord.key === e.key.toLowerCase());
793
+ const onKeyDown = (e: KeyboardEvent) => {
794
+ if (isVertexSnapKey(e) && !e.metaKey && !e.ctrlKey && !e.repeat) {
795
+ // THE STAGE'S OWN SCOPE, which is what keeps this from being a second
796
+ // keyboard owner: it fires only while the viewport holds the editor's
797
+ // scope, the same fact the frame publishes as `vgai.stage.focused`.
798
+ if (getActiveScope() !== 'viewport') return;
799
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
800
+ store.setVertexSnapActive(true);
801
+ viewport.activateVertexSnap();
802
+ }
803
+ };
804
+ const onKeyUp = (e: KeyboardEvent) => {
805
+ if (isVertexSnapKey(e) && !e.metaKey && !e.ctrlKey) {
806
+ store.setVertexSnapActive(false);
807
+ viewport.deactivateVertexSnap();
808
+ }
809
+ };
810
+ // THE ONE KEY LISTENER THE EDITOR STILL INSTALLS, and it is not a second
811
+ // keyboard owner: a keybinding rule fires on keydown and has no HELD
812
+ // concept, so vertex-snap hold has no workbench counterpart to become. It
813
+ // consumes nothing — no `preventDefault`, no action table — reads only
814
+ // whether a chord is DOWN, and fires only while the viewport holds the
815
+ // editor's own scope. That is the stage-local gesture WORK.md U6 left open,
816
+ // living beside `editor-viewport.ts`'s fly keys and snap-hold, which are
817
+ // gated the same way.
818
+ window.addEventListener('keydown', onKeyDown);
819
+ window.addEventListener('keyup', onKeyUp);
820
+
821
+ return () => {
822
+ unregister();
823
+ canvas.removeEventListener('pointerdown', onPointerDown);
824
+ window.removeEventListener('keydown', onKeyDown);
825
+ window.removeEventListener('keyup', onKeyUp);
826
+ };
827
+ }
828
+
829
+ /** Test/legacy wrapper: shell bindings plus the Three viewport set. */
830
+ export function registerEditorHotkeys(
831
+ store: EditorShellStore,
832
+ viewport: EditorViewport,
833
+ canvas: HTMLCanvasElement,
834
+ history: HistoryCommands,
835
+ ): () => void {
836
+ const unregisterShell = registerEditorShellHotkeys(store, history);
837
+ const unregisterViewport = registerEditorViewportHotkeys(store, viewport, canvas);
838
+ return () => {
839
+ unregisterShell();
840
+ unregisterViewport();
841
+ };
842
+ }