@volter/sdk 0.0.0-stage → 0.5.203

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 +1166 -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 +317 -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 +403 -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,852 @@
1
+ /**
2
+ * Design-time layer stack (B1) — the LIFECYCLE of the design-time layers a
3
+ * caller-owned authoring host shows, over mounts the surface PACKAGES
4
+ * register (`design-time-mount-registry.ts`). Runtime still uses a z-ordered
5
+ * composite, but the editor supplies a distinct host per world document; only
6
+ * Game presents the composed result. Layers default `pointer-events:none`;
7
+ * the eye (`world-session-state.ts`'s `hidden`) hides a layer; `interactive`
8
+ * forwards real pointer events for design-time hover/press testing (a mount
9
+ * whose medium IS the document's subject declares `alwaysInteractive` and
10
+ * skips that toggle); `pickLock` is stored only (B4 consumes it in the pick
11
+ * walk). A layer that throws on mount degrades to its A1
12
+ * `BoundaryAuthoringAdapter` error node (#18) via `mount-failure-report.ts` —
13
+ * never a blank editor; every OTHER world (and the focused three world in
14
+ * particular) keeps working regardless.
15
+ *
16
+ * On a successful mount, the world's initial Boundary adapter is REPLACED in
17
+ * the composite by the adapter its mount minted
18
+ * (`CompositeAuthoringAdapter.replaceChild`, B1).
19
+ *
20
+ * `dom` and `canvas` children are candidates for these hosts. The manifest's
21
+ * sole three content root renders through `the world root's stage`'s own
22
+ * `<canvas>` instead, so this module never mounts a competing Three document.
23
+ *
24
+ * WHAT THIS MODULE NO LONGER KNOWS (2026-09-18, WORK.md §The open-source
25
+ * launch item 12): how either medium mounts. It dispatched them in one
26
+ * expression — `candidate.kind === 'canvas' ? mountCanvasLayer : mountReactLayer`
27
+ * — against a `LayerMountResult` contract whose own docblock said the
28
+ * failure/teardown/play lifecycle was "the SAME for both media", and it
29
+ * hard-coded each medium's staleness rule besides (canvas remounted on a
30
+ * storage write, dom re-projected on a story publish). The contract is the
31
+ * seam; `@volter/editor-game`'s react and canvas lanes register against it now, and the play
32
+ * handoff below tears every layer down and restores its Boundary node the
33
+ * instant play starts exactly as it always did — which is why the LIFECYCLE
34
+ * stays here rather than moving with either mount.
35
+ */
36
+
37
+ import type { AuthoringAdapter } from '@volter/project/adapter';
38
+ import { stackOrder } from '@volter/project/adapter/root-stacking';
39
+ import { editorConsole } from '@volter/sdk/kit/editor-console';
40
+ import { getCurrentProject } from '@volter/sdk/kit/project-manager';
41
+ import { subscribeToolContributions, toolContributionsPublished } from '../tool-loader';
42
+ import { BoundaryAuthoringAdapter, type BoundaryRootInfo } from '@volter/sdk/kit/authoring/boundary-authoring-adapter';
43
+ import type { CompositeAuthoringAdapter } from '@volter/sdk/kit/authoring/composite-authoring-adapter';
44
+ import {
45
+ type DesignTimeMount,
46
+ type DesignTimeMountContext,
47
+ type DesignTimeRootDescriptor,
48
+ designTimeMountFor,
49
+ designTimeMounts,
50
+ type LayerMountResult,
51
+ subscribeDesignTimeMounts,
52
+ } from '@volter/sdk/kit/authoring/design-time-mount-registry';
53
+
54
+ export type { DesignTimeRootDescriptor, LayerMountResult };
55
+ import { queueEditModeRebuild } from '@volter/sdk/kit/authoring/edit-mode-authoring';
56
+ import {
57
+ addMountFailureReport,
58
+ clearMountFailureReport,
59
+ formatMountFailureMessage,
60
+ } from '@volter/sdk/kit/mount-failure-report';
61
+ import {
62
+ getRootCanvasViewport,
63
+ resetRootCanvasViewport,
64
+ setRootCanvasViewport,
65
+ subscribeRootCanvasViewport,
66
+ } from '@volter/sdk/kit/world-canvas-viewport-state';
67
+ import {
68
+ getRootPan,
69
+ panTransformValue,
70
+ type RootViewController,
71
+ resetRootPan,
72
+ subscribeRootPan,
73
+ } from '@volter/sdk/kit/world-pan-state';
74
+ import { isRootHidden, isRootInteractive } from '@volter/sdk/kit/authoring/world-session-state';
75
+ import type { ShellStore } from '@volter/sdk/kit/shell-store';
76
+ import { markGameSurface } from '@volter/sdk/kit/game-surface-defaults';
77
+
78
+ /** D20: Play tears the design-time adapter down, so the selected CSF state
79
+ * must live one level above that adapter to survive Stop's rebuild. Keyed by
80
+ * project + root so equal root ids in different projects never bleed.
81
+ *
82
+ * IT IS STILL HERE, and it is the one lane word this module has left: the
83
+ * board's mount lives in `@volter/editor-game` now, but `components/world-documents.tsx`
84
+ * reads the remembered frame for the UI board document's `presentation()`
85
+ * round-trip, and the host may not import a package. Closing it is the
86
+ * world-documents unit's, which is where the rest of that file's `dom`/
87
+ * `canvas` vocabulary lives too. */
88
+ const selectedPortableStoryByRoot = new Map<string, string>();
89
+
90
+ function portableStorySelectionKey(projectRoot: string, rootId: string): string {
91
+ return `${projectRoot}\0${rootId}`;
92
+ }
93
+
94
+ /** The story a board ROOT last applied (undefined before any selection) —
95
+ * the view-address door reads this so a presented workspace document can
96
+ * say which frame it is on (`EditorViewDocument`'s workspace `story`). */
97
+ export function rememberedPortableStory(projectRoot: string, rootId: string): string | undefined {
98
+ return selectedPortableStoryByRoot.get(portableStorySelectionKey(projectRoot, rootId));
99
+ }
100
+
101
+ /** The write half, for the registered mount that owns the board: it applies
102
+ * the story, so it is what knows which frame the root is on. */
103
+ export function rememberPortableStory(projectRoot: string, rootId: string, storyId: string): void {
104
+ selectedPortableStoryByRoot.set(portableStorySelectionKey(projectRoot, rootId), storyId);
105
+ }
106
+
107
+ /** Bottom-to-top layer z-index base — strictly below the selection overlay's
108
+ * z50 and `ToolStrip`+`ViewportOverlay`'s z1000, so canvas-local editing
109
+ * chrome is never occluded by a design-time layer. */
110
+ const BASE_Z_INDEX = 1;
111
+
112
+ /** One candidate world for a design-time layer — read entirely off its
113
+ * existing `BoundaryAuthoringAdapter` (kind/path/zOrder/pausable are already
114
+ * exposed there, independent of whether the composite has a writable manifest
115
+ * manifest surface — see that adapter's own inspector). */
116
+
117
+ function readCandidates(composite: CompositeAuthoringAdapter): DesignTimeRootDescriptor[] {
118
+ const out: DesignTimeRootDescriptor[] = [];
119
+ for (const { worldId, adapter } of composite.childAdapters()) {
120
+ if (!(adapter instanceof BoundaryAuthoringAdapter)) continue; // the focused (live) world
121
+ const kind = adapter.inspector.get(worldId, 'kind') as string;
122
+ if (kind !== 'dom' && kind !== 'canvas') continue;
123
+ const reason = adapter.inspector.get(worldId, 'reason');
124
+ if (reason !== undefined) continue; // already an error node — nothing new to attempt
125
+ // An `{ ingest }` root belongs to the ingest routes (`ingest/mount-ingest-root.ts`'s
126
+ // `tryManifestIngestRoute*`), which run the game's OWN code and capture
127
+ // what it renders. A design-time layer mounts a first-party document
128
+ // instead, so pointing it at an ingested game is a category error. A
129
+ // `{ module }` canvas root is the same case: its truth is the game's own
130
+ // imperative source, which this lane does not mount.
131
+ const declaredAdapter = adapter.inspector.get(worldId, 'adapter') as string | undefined;
132
+ if (declaredAdapter?.startsWith('ingest') || declaredAdapter?.startsWith('module')) continue;
133
+ out.push({
134
+ worldId,
135
+ kind,
136
+ path: adapter.inspector.get(worldId, 'path') as string | undefined,
137
+ zOrder: Number(adapter.inspector.get(worldId, 'zOrder') ?? 0),
138
+ pausable: adapter.inspector.get(worldId, 'pausable') !== false,
139
+ });
140
+ }
141
+ return out;
142
+ }
143
+
144
+ /** Snapshot the non-Three roots that can own an edit-time document. Capture
145
+ * this before any root mounts: a successful React mount upgrades its
146
+ * Boundary adapter in-place, while the document still needs the manifest
147
+ * path and stacking metadata that Boundary disclosed. */
148
+ export function designTimeRootDescriptors(
149
+ composite: CompositeAuthoringAdapter,
150
+ ): readonly DesignTimeRootDescriptor[] {
151
+ return readCandidates(composite);
152
+ }
153
+
154
+ /**
155
+ * The id the project's UI board mounts under when the project has NO dom root
156
+ * to own it — a project whose UI components live inside an ingested game, say,
157
+ * whose stories are ordinary discovered CSF but whose manifest declares no
158
+ * `dom` root at all.
159
+ *
160
+ * Board presence keys on STORIES, not roots (owner ruling, 2026-08-15), so
161
+ * this case is not exotic: it is simply the board without a root underneath
162
+ * it. The layer mount needs SOME id — it keys pan state, the remembered story
163
+ * selection and the story overlay — and this is that id.
164
+ *
165
+ * No colon, deliberately. `ReactRootAuthoringAdapter` spells `worldId` into a
166
+ * history `displayName` that `history/resource-registry.ts` validates as a
167
+ * project-relative PATH, and a leading `foo:` reads as a URI scheme and throws
168
+ * out of the constructor (measured on the ingest dom surface, whose
169
+ * `<world>:dom-ui` ids hit exactly that).
170
+ */
171
+ export const PROJECT_STORY_BOARD_ROOT_ID = 'ui-components-board';
172
+
173
+ /** Whether a descriptor names an existing child of the composite — the
174
+ * question `replaceChild` answers with a loud warn, asked BEFORE calling it so
175
+ * a rootless board (which by construction has no child) never trips it. */
176
+ function isCompositeChild(composite: CompositeAuthoringAdapter, worldId: string): boolean {
177
+ return composite.childAdapters().some((child) => child.worldId === worldId);
178
+ }
179
+
180
+ /** The rootless UI board's descriptor — no `path`, because there is no root
181
+ * entry to derive a default story from; the board opens on its first story
182
+ * the same way it does for a root whose entry names no story. */
183
+ export function projectStoryBoardDescriptor(): DesignTimeRootDescriptor {
184
+ return {
185
+ worldId: PROJECT_STORY_BOARD_ROOT_ID,
186
+ kind: 'dom',
187
+ path: undefined,
188
+ zOrder: 0,
189
+ pausable: true,
190
+ };
191
+ }
192
+
193
+ /** Re-applies session-local layer CSS (eye + interactive) — cheap, called on
194
+ * every store notification, never triggers a remount. */
195
+ function applySessionStyle(
196
+ layer: HTMLElement,
197
+ worldId: string,
198
+ kind: DesignTimeRootDescriptor['kind'] = 'dom',
199
+ ): void {
200
+ const display = isRootHidden(worldId) ? 'none' : 'block';
201
+ layer.style.display = display;
202
+ // A mount whose content IS the document's subject declares itself always
203
+ // interactive (a click on it is how you select in it). The `interactive`
204
+ // toggle exists for the other case, where design-time hover/press testing
205
+ // of real UI is opt-in because it competes with authoring gestures. Which
206
+ // medium is which is the mount's statement, not this module's.
207
+ layer.style.pointerEvents =
208
+ designTimeMountFor(kind)?.alwaysInteractive || isRootInteractive(worldId) ? 'auto' : 'none';
209
+ // React story labels live in editor chrome above the full-cover selection
210
+ // overlay so they remain clickable. Mirror the owning world's eye state
211
+ // onto that synchronized sibling.
212
+ for (const child of Array.from(layer.parentElement?.children ?? [])) {
213
+ if (child instanceof HTMLElement && child.dataset['volterStoryBoardChromeFor'] === worldId) {
214
+ child.style.display = display;
215
+ }
216
+ }
217
+ }
218
+
219
+ function createLayerElement(
220
+ container: HTMLElement,
221
+ candidate: DesignTimeRootDescriptor,
222
+ zIndex: number,
223
+ canvasScene: boolean,
224
+ ): HTMLElement {
225
+ const { worldId } = candidate;
226
+ const layer = document.createElement('div');
227
+ layer.dataset['worldId'] = worldId;
228
+ layer.dataset['volterRootSurface'] = 'true';
229
+ if (canvasScene) layer.dataset['volterCanvasScene'] = 'true';
230
+ layer.style.position = 'absolute';
231
+ layer.style.top = '0';
232
+ layer.style.left = '0';
233
+ layer.style.width = '100%';
234
+ layer.style.height = '100%';
235
+ layer.style.zIndex = String(zIndex);
236
+ // Same containment as the runtime's per-world surfaces (create-runtime.ts,
237
+ // world-surface cssText): make this layer the containing block for
238
+ // `position:fixed` descendants and clip to the world's rectangle, so a
239
+ // game's full-screen UI (`fixed; inset:0` — the natural idiom) renders
240
+ // inside the viewport instead of floating over the editor chrome. Also
241
+ // makes behavior pan-invariant: without it, space-pan's transform (below)
242
+ // suddenly captured `fixed` children that were page-anchored un-panned.
243
+ layer.style.contain = 'layout paint';
244
+ // A project's root renders as it ships: its inherited text properties start from a page's
245
+ // defaults, not the editor's own typography, which would otherwise inherit into it.
246
+ markGameSurface(layer);
247
+ applySessionStyle(layer, worldId, candidate.kind);
248
+ // D4 (spec27 §8 "space-pan" row) — seed this layer with whatever pan is
249
+ // currently in effect (normally none — `mountDesignTimeLayers` resets pan
250
+ // on every fresh install, see its own doc comment — but re-applying here
251
+ // too keeps this function correct standalone). See `world-pan-state.ts`'s
252
+ // doc comment for the full lockstep-translate soundness argument: THIS
253
+ // element is exactly `this.root`/`hostRect()` in
254
+ // `react-world-authoring-adapter.ts`, i.e. the host `RectProvider.rect(id)`
255
+ // subtracts — panning it is what keeps `rect(id)` pan-invariant.
256
+ applyPanTransform(layer);
257
+ container.appendChild(layer);
258
+ return layer;
259
+ }
260
+
261
+ /** Re-applies the current shared world-pan transform to one layer element —
262
+ * called at creation and by `mountDesignTimeLayers`' pan subscription on
263
+ * every subsequent pan change (see `world-pan-state.ts`). */
264
+ function applyPanTransform(layer: HTMLElement): void {
265
+ // A native Canvas Scene renders through its own editor-camera matrix inside
266
+ // a viewport-sized Pixi surface. CSS-transforming that surface would turn
267
+ // it back into the fixed artboard the Scene document is replacing.
268
+ if (layer.dataset['volterCanvasScene'] === 'true') {
269
+ layer.style.transform = '';
270
+ layer.style.width = '100%';
271
+ layer.style.height = '100%';
272
+ return;
273
+ }
274
+ layer.style.transform = panTransformValue(getRootPan()) ?? '';
275
+ layer.style.transformOrigin = '0 0';
276
+ // A React story board owns a canvas larger than the visible document and
277
+ // sizes the layer to its complete grid. Responsive viewport changes resize
278
+ // each frame through `react-story-board.ts`; do not collapse that board back
279
+ // to the single-runtime-surface dimensions used by React/Pixi roots.
280
+ if (layer.dataset['volterReactStoryBoard'] === 'true') return;
281
+ const viewport = getRootCanvasViewport();
282
+ layer.style.width = viewport.width === null ? '100%' : `${viewport.width}px`;
283
+ layer.style.height = viewport.height === null ? '100%' : `${viewport.height}px`;
284
+ }
285
+
286
+ /**
287
+ * THE CONTRACT every design-time mount answers, whatever its medium — the
288
+ * one this module's own docblock already called "the SAME for both media",
289
+ * exported since the mounts moved into their surface packages
290
+ * (`design-time-mount-registry.ts`).
291
+ */
292
+
293
+ /**
294
+ * Mount design-time React worlds declared by `composite` into a caller's
295
+ * authoring `container`. `descriptors` narrows the mount to one world for the
296
+ * per-world document model; omission preserves the original multi-layer
297
+ * primitive for bounded callers and integration tests. Returns a disposer
298
+ * that must run before the owning composite is released.
299
+ *
300
+ * A no-op (returns a no-op disposer) when there are no layer candidates or no
301
+ * resolvable current project.
302
+ *
303
+ * Play-mode handoff: the editor's Scene and Game tabs are BOTH always
304
+ * mounted (only one is `display`-visible at a time — the Game tab hosts
305
+ * `play-mode.ts`'s OWN, entirely separate composite/mount over a REAL,
306
+ * ticking `Game`). The instant play starts, this module tears its
307
+ * design-time layers down for the rest of THIS edit session: two
308
+ * independent DOM trees for the SAME react world (this inert stand-in,
309
+ * play-mode's live one) would otherwise carry the SAME `data-testid`s
310
+ * (whatever the entry's own JSX declares, e.g. tri-world's `hud-count`)
311
+ * simultaneously in one document — not just visually confusing, but a
312
+ * genuine test/automation hazard (two elements answering to one testid).
313
+ * Stop symmetrically rebuilds edit-mode authoring and restores the selected
314
+ * portable CSF preview. Play and edit roots never coexist, but neither does
315
+ * Play permanently destroy the authoring surface.
316
+ */
317
+ /** Each world's latest layer mount, until it has settled (and, when superseded, been disposed). */
318
+ const settlingMounts = new Map<string, Promise<void>>();
319
+
320
+ export function mountDesignTimeLayers(
321
+ container: HTMLElement,
322
+ composite: CompositeAuthoringAdapter,
323
+ store: ShellStore,
324
+ descriptors?: readonly DesignTimeRootDescriptor[],
325
+ /**
326
+ * Reports the adapter a descriptor's layer minted, or `null` when its layer
327
+ * goes away. Only a descriptor that names an EXISTING composite child is
328
+ * published INTO the composite (`replaceChild` is an in-place swap; it
329
+ * cannot grow the array, by design) — so a rootless UI board or the Dev
330
+ * component board has no child to upgrade, and this is how its document
331
+ * reaches its adapter for Hierarchy/Inspector routing.
332
+ */
333
+ onAdapter?: (worldId: string, adapter: AuthoringAdapter | null) => void,
334
+ presentation?: {
335
+ /** Native Canvas Scene: independent editor camera and viewport renderer,
336
+ * not the DOM/story-board artboard presentation. */
337
+ readonly canvasSceneView: RootViewController;
338
+ },
339
+ /** When set, first-mount work writes named segments onto this document's
340
+ * activation clock (`viewport-activation-timings`). */
341
+ activationDocumentId?: string,
342
+ ): () => void {
343
+ // D4 (spec27 §8 "space-pan" row) — a fresh layer stack starts un-panned:
344
+ // every call here is a "world switch" (initial edit-mode install, or an
345
+ // acknowledged edit-mode re-install — see `world-root-stage.ts`'s
346
+ // `installAll`), so a stale pan offset from a PREVIOUS layer stack must
347
+ // never carry over onto a different one.
348
+ if (presentation) presentation.canvasSceneView.reset();
349
+ else resetRootPan();
350
+ resetRootCanvasViewport();
351
+ // A declared project resolution seeds DOM component preview frames from the
352
+ // same logical screen Play/standalone use. It deliberately does NOT size a
353
+ // native Canvas Scene: that document is an unbounded world view and receives
354
+ // the separate presentation branch above. A responsive preset may still
355
+ // override DOM previews live through ReactCanvasControls.
356
+ const activeProject = getCurrentProject();
357
+ if (!presentation && activeProject?.config.resolution) {
358
+ setRootCanvasViewport(
359
+ activeProject.config.resolution.width,
360
+ activeProject.config.resolution.height,
361
+ );
362
+ }
363
+ // ONE-WAY PLAY HANDOFF, ENTRY HALF. The exit half — the `store.subscribe`
364
+ // listener at the bottom of this function, which flips `playTeardownDone`
365
+ // and calls `suspendForPlay()` when play STARTS — covers "layers were
366
+ // already mounted, then the user hit Play". But it is a TRANSITION
367
+ // listener, so it does nothing at all when play is ALREADY running by the
368
+ // time this function is first called.
369
+ //
370
+ // That ordering is reachable. `the world root's stage`'s `installAll()` (the only
371
+ // caller) runs at the tail of an async chain that fetches
372
+ // `volter.project.json` on the way here, so a Play issued the moment the
373
+ // editor shell is interactive wins the race on a slow or contended host and
374
+ // `store.playState` is already `'playing'` on arrival. Without this guard we
375
+ // mount a design-time react layer anyway — rendering the project's REAL
376
+ // entry component against `createDesignTimeGame()`'s INERT `Game`, whose
377
+ // `roots` array is `[]` forever and whose loop never ticks (see that
378
+ // function's doc comment). A HUD listing worlds therefore renders an empty
379
+ // list from a Game that never registers one — and, because no further
380
+ // playState TRANSITION ever arrives, `suspendForPlay()` never fires and the
381
+ // layer is never torn down. It also carries the duplicate-`data-testid`
382
+ // hazard this module's own doc comment calls out for the transition case.
383
+ //
384
+ // Play mode owns the react world outright while it runs (it mounts its own
385
+ // live tree over a REAL, ticking Game). So the correct behavior when play
386
+ // is already up is the same one `suspendForPlay()` produces: mount NOTHING.
387
+ // Stop requests a clean edit-mode rebuild below; this entry guard therefore
388
+ // needs to listen for Stop even though it mounted no layers itself.
389
+ if (store.playState !== 'stopped') {
390
+ let disposed = false;
391
+ let rebuildRequested = false;
392
+ const unsubscribe = store.subscribe(() => {
393
+ if (disposed || rebuildRequested || store.playState !== 'stopped') return;
394
+ rebuildRequested = true;
395
+ if (!disposed) queueEditModeRebuild();
396
+ });
397
+ return () => {
398
+ disposed = true;
399
+ unsubscribe();
400
+ };
401
+ }
402
+ const projectRoot = activeProject?.rootPath;
403
+ const candidates = descriptors ? [...descriptors] : readCandidates(composite);
404
+ // C3 — the scene-UI child (at most one, the Three world's): already a
405
+ // LIVE `UIAuthoringAdapter` at composite-install time (`edit-mode-
406
+ // authoring.ts` — no Boundary-to-live upgrade dance needed here, unlike
407
+ // react/pixi), so it's a mount candidate in its own right rather than one
408
+ // of `readCandidates`'s Boundary-only entries.
409
+ if (candidates.length === 0 || !projectRoot) {
410
+ return () => {};
411
+ }
412
+ // Narrowed once: `mountCandidate` below is a function declaration, where
413
+ // the guard's control-flow narrowing does not reach.
414
+ const projectRootPath: string = projectRoot;
415
+
416
+ // Bottom-to-top paint order — mirrors `create-runtime.ts`'s own stacking
417
+ // pass, over exactly the roots this module lays out (react/pixi
418
+ // candidates only; the Three world keeps its untouched canvas
419
+ // slot below all of these — see the module doc comment).
420
+ const sorted = stackOrder(candidates.map((c) => ({ id: c.worldId, zOrder: c.zOrder })));
421
+ const orderedCandidates = sorted.map((s) => candidates.find((c) => c.worldId === s.id)!);
422
+
423
+ const candidateByRoot = new Map(orderedCandidates.map((c) => [c.worldId, c]));
424
+ const layers = new Map<string, HTMLElement>();
425
+ const disposersByWorld = new Map<string, () => void>();
426
+ /** Per-world mount generation — a remount supersedes any in-flight mount. */
427
+ const mountEpochs = new Map<string, number>();
428
+ // Worlds whose Boundary child we upgraded to a LIVE design-time adapter
429
+ // (successful react mounts). Tracked so the one-way play handoff can put an
430
+ // HONEST disclosure node back in the composite instead of leaving a live
431
+ // adapter over torn-down DOM (which renders silently empty — a #18 violation).
432
+ const upgraded = new Set<string>();
433
+ /** Worlds whose medium has no registered mount YET — see `mountCandidate`. */
434
+ const awaitingMount = new Set<string>();
435
+ /** Worlds already told, in their Boundary node, that no package registered
436
+ * a mount for their medium. Said once per stack, never twice. */
437
+ const refusedMount = new Set<string>();
438
+ let torndown = false; // the whole-module disposer (the world root's stage unmount) ran
439
+ let playTeardownDone = false; // the one-way play-mode handoff (see doc comment) ran
440
+ let rebuildRequested = false;
441
+
442
+ function disposeEverything(): void {
443
+ for (const dispose of disposersByWorld.values()) {
444
+ try {
445
+ dispose();
446
+ } catch (err) {
447
+ editorConsole.error(`[design-time-layers] dispose failed: ${err}`, 'authoring');
448
+ }
449
+ }
450
+ disposersByWorld.clear();
451
+ for (const layer of layers.values()) layer.remove();
452
+ layers.clear();
453
+ // A reported adapter now points at torn-down DOM. Retract it in the same
454
+ // gesture that freed it — the composite half of this (`suspendForPlay`'s
455
+ // Boundary swap) exists for exactly the same reason.
456
+ for (const candidate of orderedCandidates) onAdapter?.(candidate.worldId, null);
457
+ }
458
+
459
+ /**
460
+ * Play handoff: dispose the design-time layers AND revert every
461
+ * upgraded world back to a `BoundaryAuthoringAdapter` disclosure node
462
+ * (#18 — never silently empty). Not called on the whole-module disposer
463
+ * (`torndown`, the world root's stage unmount), where the composite is going away
464
+ * anyway. Stop dispatches one edit-mode rebuild after the live runtime has
465
+ * torn down, restoring the CSF-backed design surface. The scene-UI layer's DOM is
466
+ * torn down the same way (play mode renders scene-UI through the real
467
+ * engine's HUD mount instead — two live DOM trees for the same testids
468
+ * would be the same hazard `design-time-layers.ts`'s own doc comment
469
+ * already calls out for react roots); its adapter is left alone (still
470
+ * live, just without a mounted layer) since there is no Boundary form of it.
471
+ */
472
+ function suspendForPlay(): void {
473
+ disposeEverything();
474
+ for (const worldId of upgraded) {
475
+ const c = candidateByRoot.get(worldId);
476
+ if (!c) continue;
477
+ const info: BoundaryRootInfo = {
478
+ id: c.worldId,
479
+ kind: c.kind,
480
+ entryOrScenePath: c.path,
481
+ zOrder: c.zOrder,
482
+ pausable: c.pausable,
483
+ };
484
+ composite.replaceChild(
485
+ worldId,
486
+ new BoundaryAuthoringAdapter(
487
+ store,
488
+ info,
489
+ 'Design-time layer suspended by play mode — Stop restores it.',
490
+ ),
491
+ );
492
+ }
493
+ upgraded.clear();
494
+ store.notifyIngestEdit();
495
+ }
496
+
497
+ function mountCandidate(
498
+ candidate: DesignTimeRootDescriptor,
499
+ i: number,
500
+ /** Runs once this mount has settled (mounted or failed) — the hook a
501
+ * re-projection uses to take the PREVIOUS layer down only then. */
502
+ onSettled?: () => void,
503
+ ): void {
504
+ const registered = designTimeMountFor(candidate.kind);
505
+ // NO REGISTERED MOUNT IS NOT A FAILURE, and specifically not YET a
506
+ // failure: contributions load asynchronously (the inventory pass waits
507
+ // for the first viewport frame), so a layer stack installed at project
508
+ // open routinely runs before the package that owns this medium has
509
+ // registered. Wait, silently, with no layer and no console line — the
510
+ // world's own Boundary node is already standing in the composite, which
511
+ // is the disclosure. `awaitingMount` is what the registration
512
+ // subscription below mounts once the package arrives, and what the
513
+ // contribution pass turns into a NAMED refusal if it never does.
514
+ if (!registered) {
515
+ awaitingMount.add(candidate.worldId);
516
+ return;
517
+ }
518
+ awaitingMount.delete(candidate.worldId);
519
+ const epoch = (mountEpochs.get(candidate.worldId) ?? 0) + 1;
520
+ mountEpochs.set(candidate.worldId, epoch);
521
+ const layer = createLayerElement(container, candidate, BASE_Z_INDEX + i, !!presentation);
522
+ layers.set(candidate.worldId, layer);
523
+
524
+ const context: DesignTimeMountContext = {
525
+ projectRootPath,
526
+ store: store,
527
+ ...(presentation ? { view: presentation.canvasSceneView } : {}),
528
+ ...(activationDocumentId ? { activationDocumentId } : {}),
529
+ };
530
+
531
+ // Wrapped so a mount that throws SYNCHRONOUSLY (an implementation that
532
+ // is not itself `async`) still lands in the boundary path below rather
533
+ // than escaping this call.
534
+ // ONE WORLD, ONE MOUNT AT A TIME. A stack torn down while its mount is in
535
+ // flight disposes that mount only when it resolves, and a successor stack
536
+ // opens the same world meanwhile: measured on a canvas project (the
537
+ // authoring session rebuilds its documents once after boot), the second
538
+ // mount's adapter found the world's history journal still driven by the
539
+ // first ("already has a driver") and the Scene never left "Loading". So a
540
+ // mount starts after the previous one for its world has settled and been
541
+ // disposed.
542
+ const previous = settlingMounts.get(candidate.worldId);
543
+ const mountPromise: Promise<LayerMountResult> = (async () => {
544
+ if (previous) await previous;
545
+ return registered.mount(candidate, layer, context);
546
+ })();
547
+
548
+ const settled: Promise<void> = mountPromise
549
+ .then((result) => {
550
+ if (torndown || playTeardownDone || mountEpochs.get(candidate.worldId) !== epoch) {
551
+ result.dispose();
552
+ return;
553
+ }
554
+ if (result.adapter) {
555
+ // A descriptor with no composite child of its own (rootless UI or
556
+ // the Dev component board) is reported below and nowhere else:
557
+ // `replaceChild` would only warn and ignore, which is the right
558
+ // refusal for a real mis-routing and pure noise for a board that
559
+ // never had a child. For a real child, replace FIRST: publishing the
560
+ // mounted adapter into React before the composite owns its ids gives
561
+ // one render where selection belongs to neither surface. Besides a
562
+ // false unowned-id refusal, logging that refusal during render makes
563
+ // the console tray update while RootSelectionOverlay is rendering.
564
+ if (isCompositeChild(composite, candidate.worldId)) {
565
+ composite.replaceChild(candidate.worldId, result.adapter);
566
+ upgraded.add(candidate.worldId);
567
+ }
568
+ onAdapter?.(candidate.worldId, result.adapter);
569
+ } else if (result.pick) {
570
+ // Opaque/foreign Pixi roots can expose a stage pick without a
571
+ // source-authoring adapter. Re-wrap their Boundary with the pick and
572
+ // track it in `upgraded`: `suspendForPlay` must remove a pick that
573
+ // closes over a disposed Pixi stage.
574
+ const info: BoundaryRootInfo = {
575
+ id: candidate.worldId,
576
+ kind: candidate.kind,
577
+ entryOrScenePath: candidate.path,
578
+ zOrder: candidate.zOrder,
579
+ pausable: candidate.pausable,
580
+ };
581
+ composite.replaceChild(
582
+ candidate.worldId,
583
+ new BoundaryAuthoringAdapter(store, info, undefined, result.pick),
584
+ );
585
+ upgraded.add(candidate.worldId);
586
+ }
587
+ disposersByWorld.set(candidate.worldId, result.dispose);
588
+ // PD-1: this root is mounted — retract any failure recorded for it by
589
+ // an earlier attempt (per-world, never the whole list: a sibling root
590
+ // that is still down keeps its own report).
591
+ clearMountFailureReport(candidate.worldId);
592
+ store.notifyIngestEdit();
593
+ onSettled?.();
594
+ })
595
+ .catch((err: unknown) => {
596
+ if (mountEpochs.get(candidate.worldId) !== epoch) return;
597
+ onSettled?.();
598
+ layer.remove();
599
+ if (layers.get(candidate.worldId) === layer) layers.delete(candidate.worldId);
600
+ if (torndown || playTeardownDone) return;
601
+ const message = formatMountFailureMessage(err);
602
+ editorConsole.error(
603
+ `[design-time-layers] world "${candidate.worldId}" (${candidate.kind}) failed to ` +
604
+ `mount: ${message}`,
605
+ 'authoring',
606
+ );
607
+ const info: BoundaryRootInfo = {
608
+ id: candidate.worldId,
609
+ kind: candidate.kind,
610
+ entryOrScenePath: candidate.path,
611
+ zOrder: candidate.zOrder,
612
+ pausable: candidate.pausable,
613
+ };
614
+ onAdapter?.(candidate.worldId, null);
615
+ if (isCompositeChild(composite, candidate.worldId)) {
616
+ composite.replaceChild(
617
+ candidate.worldId,
618
+ new BoundaryAuthoringAdapter(store, info, message),
619
+ );
620
+ }
621
+ addMountFailureReport({
622
+ worldId: candidate.worldId,
623
+ kind: candidate.kind,
624
+ identity: registered.identity,
625
+ message,
626
+ });
627
+ store.notifyIngestEdit();
628
+ });
629
+ settlingMounts.set(candidate.worldId, settled);
630
+ void settled.then(() => {
631
+ if (settlingMounts.get(candidate.worldId) === settled) settlingMounts.delete(candidate.worldId);
632
+ });
633
+ }
634
+ orderedCandidates.forEach((candidate, i) => {
635
+ mountCandidate(candidate, i);
636
+ });
637
+
638
+ /**
639
+ * Dispose one world's mount and mount it again from CURRENT source —
640
+ * the canvas lane's absorb-by-remount unit (storage watch below).
641
+ */
642
+ /**
643
+ * A remount that keeps the OLD layer on screen until the new one has
644
+ * settled. `remountCandidate` tears the layer down first, so every
645
+ * re-projection was a visible flash — "it flickers like a reload, then the
646
+ * change is applied" (runhuman pass 144). The new layer is created on top;
647
+ * when its mount settles the previous one is disposed and removed.
648
+ */
649
+ function reprojectCandidate(candidate: DesignTimeRootDescriptor, i: number): void {
650
+ const prevDispose = disposersByWorld.get(candidate.worldId);
651
+ disposersByWorld.delete(candidate.worldId);
652
+ const prevLayer = layers.get(candidate.worldId);
653
+ layers.delete(candidate.worldId);
654
+ mountCandidate(candidate, i, () => {
655
+ // The new layer mounted hidden — its frames fill in over a second and
656
+ // an empty board on top of the old one IS the flash (runhuman pass
657
+ // 146 still saw it with the old layer kept). Show it only now.
658
+ const next = layers.get(candidate.worldId);
659
+ if (next) next.style.visibility = '';
660
+ if (prevDispose) {
661
+ try {
662
+ prevDispose();
663
+ } catch (err) {
664
+ editorConsole.error(
665
+ `[design-time-layers] re-projection dispose failed: ${err}`,
666
+ 'authoring',
667
+ );
668
+ }
669
+ }
670
+ prevLayer?.remove();
671
+ });
672
+ const next = layers.get(candidate.worldId);
673
+ if (next && next !== prevLayer) next.style.visibility = 'hidden';
674
+ }
675
+
676
+ function remountCandidate(candidate: DesignTimeRootDescriptor, i: number): void {
677
+ const prev = disposersByWorld.get(candidate.worldId);
678
+ disposersByWorld.delete(candidate.worldId);
679
+ if (prev) {
680
+ try {
681
+ prev();
682
+ } catch (err) {
683
+ editorConsole.error(`[design-time-layers] remount dispose failed: ${err}`, 'authoring');
684
+ }
685
+ }
686
+ const oldLayer = layers.get(candidate.worldId);
687
+ if (oldLayer) {
688
+ oldLayer.remove();
689
+ layers.delete(candidate.worldId);
690
+ }
691
+ mountCandidate(candidate, i);
692
+ }
693
+
694
+ // WHEN A MOUNTED LAYER GOES STALE IS THE MOUNT'S STATEMENT, NOT THIS
695
+ // MODULE'S. Two rules used to be hard-coded here, each bought with a
696
+ // measured screen. A canvas layer REMOUNTS when a write lands with no HMR
697
+ // channel to re-execute it: without that a structural write landed in
698
+ // source while the live board kept showing the old world until F5 (runhuman
699
+ // pass 61: "when I duplicated, nothing happened... when you refresh, now it
700
+ // shows"). A dom board RE-PROJECTS when its story registry publishes: same absence of
701
+ // HMR, and a dragged crosshair stayed where the live preview left it while
702
+ // an UNDO changed nothing on screen until F5 — "Ctrl+Z just blinked" for
703
+ // every Windows tester and, measured on the instrument, for macOS too
704
+ // (runhuman passes 135–142; screen read 2026-09-03). Both are facts about a
705
+ // MEDIUM, so both travel with the mount now.
706
+ //
707
+ // WHAT STAYS HERE is the pair of ACTIONS and the one question this module
708
+ // can answer — whether a stale layer may be replaced right now (not while
709
+ // play is running, not after teardown). REMOUNT tears the old layer down
710
+ // first; RE-PROJECT mounts the new one on top and disposes the previous
711
+ // only once it has settled, because an empty board over the old one IS the
712
+ // flash (runhuman pass 146). A mount asks for the one its medium affords.
713
+ // The three world is neither: it runs its own design session with its own
714
+ // absorb protocol (`r3f-design-session.ts`).
715
+ const invalidationStops = new Map<DesignTimeMount, () => void>();
716
+ function bindInvalidations(): void {
717
+ for (const [mount, stop] of invalidationStops) {
718
+ if (designTimeMountFor(mount.kind) === mount) continue;
719
+ // Replaced (an HMR re-evaluation) or removed: its hooks are stale.
720
+ stop();
721
+ invalidationStops.delete(mount);
722
+ }
723
+ for (const mount of designTimeMounts()) {
724
+ if (invalidationStops.has(mount)) continue;
725
+ if (designTimeMountFor(mount.kind) !== mount) continue;
726
+ if (!orderedCandidates.some((c) => c.kind === mount.kind)) continue;
727
+ const stops: Array<() => void> = [];
728
+ const invalidate =
729
+ (apply: (candidate: DesignTimeRootDescriptor, index: number) => void) => (): void => {
730
+ if (torndown || playTeardownDone || store.playState !== 'stopped') return;
731
+ orderedCandidates.forEach((candidate, index) => {
732
+ if (candidate.kind === mount.kind) apply(candidate, index);
733
+ });
734
+ };
735
+ if (mount.remountWhen) stops.push(mount.remountWhen(invalidate(remountCandidate)));
736
+ if (mount.reprojectWhen) stops.push(mount.reprojectWhen(invalidate(reprojectCandidate)));
737
+ invalidationStops.set(mount, () => {
738
+ for (const stop of stops) stop();
739
+ });
740
+ }
741
+ }
742
+ bindInvalidations();
743
+
744
+ // A PACKAGE THAT ARRIVES LATE STILL GETS ITS LAYER. The contribution pass
745
+ // that loads the surface packages is deferred behind the first viewport
746
+ // frame (`project-tool-discovery.ts`), so on a cold open this stack is
747
+ // routinely installed before any of them has registered. Every candidate it
748
+ // could not mount is waiting in `awaitingMount`; a registration is the
749
+ // moment to mount it, and the moment to bind that medium's staleness hooks.
750
+ const unsubMounts = subscribeDesignTimeMounts(() => {
751
+ if (torndown || playTeardownDone || store.playState !== 'stopped') return;
752
+ orderedCandidates.forEach((candidate, index) => {
753
+ if (!awaitingMount.has(candidate.worldId)) return;
754
+ if (!designTimeMountFor(candidate.kind)) return;
755
+ refusedMount.delete(candidate.worldId);
756
+ clearMountFailureReport(candidate.worldId);
757
+ mountCandidate(candidate, index);
758
+ });
759
+ bindInvalidations();
760
+ });
761
+
762
+ // ...AND A MEDIUM NO PACKAGE OWNS SAYS SO. Once a contribution pass has
763
+ // installed, "nothing registered" has stopped being "not yet" and become
764
+ // the answer — the honest emptiness a build whose package list carries no
765
+ // mount for this medium gives. It is a Boundary disclosure, deliberately
766
+ // NOT an `editorConsole.error` and not a mount-failure report: a build
767
+ // shipping no such package is a configuration fact, not a failure, and a
768
+ // later registration above still upgrades it.
769
+ const refuseUnownedMedia = (): void => {
770
+ if (torndown || playTeardownDone) return;
771
+ let refused = false;
772
+ for (const candidate of orderedCandidates) {
773
+ if (!awaitingMount.has(candidate.worldId)) continue;
774
+ if (refusedMount.has(candidate.worldId)) continue;
775
+ if (designTimeMountFor(candidate.kind)) continue;
776
+ refusedMount.add(candidate.worldId);
777
+ if (!isCompositeChild(composite, candidate.worldId)) continue;
778
+ const info: BoundaryRootInfo = {
779
+ id: candidate.worldId,
780
+ kind: candidate.kind,
781
+ entryOrScenePath: candidate.path,
782
+ zOrder: candidate.zOrder,
783
+ pausable: candidate.pausable,
784
+ };
785
+ composite.replaceChild(
786
+ candidate.worldId,
787
+ new BoundaryAuthoringAdapter(
788
+ store,
789
+ info,
790
+ `No package in this editor registered a design-time mount for a "${candidate.kind}" ` +
791
+ 'world, so there is nothing here to author it with.',
792
+ ),
793
+ );
794
+ refused = true;
795
+ }
796
+ if (refused) store.notifyIngestEdit();
797
+ };
798
+ const unsubContributions = subscribeToolContributions(refuseUnownedMedia);
799
+ // A stack installed after the pass has already published missed that edge.
800
+ if (toolContributionsPublished()) refuseUnownedMedia();
801
+
802
+ // D4 (spec27 §8 "space-pan" row) — the lockstep half of the pan feature:
803
+ // re-apply the current shared transform to every currently-mounted layer
804
+ // the instant it changes (`RootSelectionOverlay`'s own outer wrapper binds
805
+ // the SAME `subscribeRootPan`/`panTransformValue` pair independently — see
806
+ // `world-pan-state.ts`'s doc comment for why applying the identical value
807
+ // at both mount points is what keeps `rect(id)` pan-invariant). A cheap
808
+ // style write, never a remount.
809
+ const unsubPan = subscribeRootPan(() => {
810
+ for (const layer of layers.values()) applyPanTransform(layer);
811
+ });
812
+ const unsubViewport = subscribeRootCanvasViewport(() => {
813
+ for (const layer of layers.values()) applyPanTransform(layer);
814
+ });
815
+
816
+ const unsubStore = store.subscribe(() => {
817
+ if (!playTeardownDone && store.playState !== 'stopped') {
818
+ playTeardownDone = true;
819
+ suspendForPlay();
820
+ return;
821
+ }
822
+ if (playTeardownDone && !rebuildRequested && store.playState === 'stopped' && !torndown) {
823
+ rebuildRequested = true;
824
+ // Reuse the world root's stage's one authoritative reinstall path. The event
825
+ // causes this mount's disposer to run before the replacement composite
826
+ // and layers are installed, so no React root or adapter survives across
827
+ // the Play -> Stop boundary.
828
+ if (!torndown) queueEditModeRebuild();
829
+ return;
830
+ }
831
+ // Session toggles (eye/interactive) change via `store.notifyIngestEdit()`
832
+ // (`GameHierarchy.tsx`'s row affordances) — this is a cheap style
833
+ // re-apply, never a remount.
834
+ if (!playTeardownDone) {
835
+ for (const [worldId, layer] of layers) {
836
+ applySessionStyle(layer, worldId, candidateByRoot.get(worldId)?.kind ?? 'dom');
837
+ }
838
+ }
839
+ });
840
+
841
+ return () => {
842
+ torndown = true;
843
+ for (const stop of invalidationStops.values()) stop();
844
+ invalidationStops.clear();
845
+ unsubMounts();
846
+ unsubContributions();
847
+ unsubStore();
848
+ unsubPan();
849
+ unsubViewport();
850
+ disposeEverything();
851
+ };
852
+ }