@volter/sdk 0.0.0-stage → 0.5.204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (523) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +20 -0
  3. package/README.md +38 -3
  4. package/package.json +510 -4
  5. package/src/account.ts +210 -0
  6. package/src/chrome.ts +88 -0
  7. package/src/client.ts +1646 -0
  8. package/src/commands.ts +66 -0
  9. package/src/contributions.ts +619 -0
  10. package/src/css-numeric-style.ts +97 -0
  11. package/src/document-probe.ts +282 -0
  12. package/src/editor-view.ts +225 -0
  13. package/src/extension.ts +40 -0
  14. package/src/generations.ts +178 -0
  15. package/src/host.ts +1157 -0
  16. package/src/http-transport.browser.ts +14 -0
  17. package/src/http-transport.node.ts +19 -0
  18. package/src/index.ts +131 -0
  19. package/src/kit/CapabilityCoverageSection.tsx +185 -0
  20. package/src/kit/account-client.ts +333 -0
  21. package/src/kit/action-registry.ts +317 -0
  22. package/src/kit/active-product.ts +76 -0
  23. package/src/kit/active-project.ts +155 -0
  24. package/src/kit/adapter-editor-config.ts +25 -0
  25. package/src/kit/adapter-module.ts +7 -0
  26. package/src/kit/adapter-observation.ts +49 -0
  27. package/src/kit/animation/animation-clock.ts +479 -0
  28. package/src/kit/animation/stage-transport.ts +385 -0
  29. package/src/kit/api/assets.ts +365 -0
  30. package/src/kit/api/project-open.ts +355 -0
  31. package/src/kit/api/project-source.ts +180 -0
  32. package/src/kit/api/project-state.ts +110 -0
  33. package/src/kit/api/relay.ts +270 -0
  34. package/src/kit/api/themes.ts +45 -0
  35. package/src/kit/api-asset-library-wire.ts +45 -0
  36. package/src/kit/api-base.ts +10 -0
  37. package/src/kit/api-build.ts +99 -0
  38. package/src/kit/api-git-wire.ts +56 -0
  39. package/src/kit/api-logs.ts +92 -0
  40. package/src/kit/api-project-identity.ts +74 -0
  41. package/src/kit/api-settings.ts +36 -0
  42. package/src/kit/api-worktrees.ts +205 -0
  43. package/src/kit/asset-capabilities.ts +344 -0
  44. package/src/kit/asset-compare-core.ts +171 -0
  45. package/src/kit/asset-editor-context.tsx +101 -0
  46. package/src/kit/asset-events.ts +96 -0
  47. package/src/kit/asset-inspector-actions.ts +87 -0
  48. package/src/kit/asset-selection-viewer-registry.ts +113 -0
  49. package/src/kit/asset-selection.ts +146 -0
  50. package/src/kit/asset-thumbnails.ts +25 -0
  51. package/src/kit/asset-viewers.ts +115 -0
  52. package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
  53. package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
  54. package/src/kit/asset-workflow/asset-ledger.ts +156 -0
  55. package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
  56. package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
  57. package/src/kit/asset-workflow/asset-types.ts +142 -0
  58. package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
  59. package/src/kit/asset-workflow/audio-waveform.ts +22 -0
  60. package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
  61. package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
  62. package/src/kit/asset-workflow/image-view-scale.ts +31 -0
  63. package/src/kit/asset-workflow/import-contract.ts +124 -0
  64. package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
  65. package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
  66. package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
  67. package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
  68. package/src/kit/asset-workflow/project-content.ts +288 -0
  69. package/src/kit/asset-workflow/project-source-index.ts +545 -0
  70. package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
  71. package/src/kit/authoring/active-adapter.ts +200 -0
  72. package/src/kit/authoring/active-systems.ts +422 -0
  73. package/src/kit/authoring/adapter-key.ts +18 -0
  74. package/src/kit/authoring/authoring-asset-url.ts +27 -0
  75. package/src/kit/authoring/bootstrap-state.ts +49 -0
  76. package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
  77. package/src/kit/authoring/canvas-scene-guides.ts +84 -0
  78. package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
  79. package/src/kit/authoring/consumer-actions.ts +531 -0
  80. package/src/kit/authoring/design-time-layers.ts +852 -0
  81. package/src/kit/authoring/design-time-mount-registry.ts +244 -0
  82. package/src/kit/authoring/edit-mode-authoring.ts +637 -0
  83. package/src/kit/authoring/empty-project-authoring.ts +22 -0
  84. package/src/kit/authoring/instance-source-menu.ts +135 -0
  85. package/src/kit/authoring/layered-pick.ts +185 -0
  86. package/src/kit/authoring/mounted-root-subjects.ts +146 -0
  87. package/src/kit/authoring/no-authoring-adapter.ts +59 -0
  88. package/src/kit/authoring/object3d-document-persistence.ts +122 -0
  89. package/src/kit/authoring/panel-authoring.ts +121 -0
  90. package/src/kit/authoring/project-authoring-session.ts +105 -0
  91. package/src/kit/authoring/provenance.ts +99 -0
  92. package/src/kit/authoring/react-canvas-navigation.ts +259 -0
  93. package/src/kit/authoring/react-design-canvas-style.ts +20 -0
  94. package/src/kit/authoring/react-story-board.ts +917 -0
  95. package/src/kit/authoring/selection-scope.ts +195 -0
  96. package/src/kit/authoring/shell-document-ops.ts +169 -0
  97. package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
  98. package/src/kit/authoring/story-board-presentation.ts +111 -0
  99. package/src/kit/authoring/three-root.ts +67 -0
  100. package/src/kit/authoring/viewport-tool-context.ts +73 -0
  101. package/src/kit/authoring/viewport-tool-owner.ts +38 -0
  102. package/src/kit/authoring/world-session-state.ts +101 -0
  103. package/src/kit/authoring-seam-evidence.ts +300 -0
  104. package/src/kit/availability-tick.ts +66 -0
  105. package/src/kit/bitmap-label.ts +120 -0
  106. package/src/kit/boot-routing.ts +392 -0
  107. package/src/kit/breakpoint-state.ts +43 -0
  108. package/src/kit/build-identity.ts +16 -0
  109. package/src/kit/bytes-codec.ts +62 -0
  110. package/src/kit/cancellation-reason.ts +58 -0
  111. package/src/kit/canvas-frames.ts +88 -0
  112. package/src/kit/capture-camera-pose.ts +77 -0
  113. package/src/kit/capture-size.ts +88 -0
  114. package/src/kit/chrome-registry.ts +159 -0
  115. package/src/kit/chrome-slot-registry.ts +91 -0
  116. package/src/kit/collaboration-client.ts +264 -0
  117. package/src/kit/collaboration-presence.ts +41 -0
  118. package/src/kit/command-dispatch.ts +19 -0
  119. package/src/kit/command-listener.ts +2182 -0
  120. package/src/kit/command-registry.ts +71 -0
  121. package/src/kit/component-board-registry.ts +205 -0
  122. package/src/kit/component-states-registry.ts +199 -0
  123. package/src/kit/components/AlignToolbar.tsx +204 -0
  124. package/src/kit/components/ApplicationMenus.tsx +372 -0
  125. package/src/kit/components/AssetEditorShell.tsx +216 -0
  126. package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
  127. package/src/kit/components/BoardRulers.tsx +354 -0
  128. package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
  129. package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
  130. package/src/kit/components/ChromeSlot.tsx +20 -0
  131. package/src/kit/components/CodeView.tsx +470 -0
  132. package/src/kit/components/CompactInspectorShell.tsx +39 -0
  133. package/src/kit/components/ConsolePanel.tsx +273 -0
  134. package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
  135. package/src/kit/components/InspectionProjection.tsx +932 -0
  136. package/src/kit/components/Inspector.tsx +270 -0
  137. package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
  138. package/src/kit/components/InspectorFieldsSection.tsx +290 -0
  139. package/src/kit/components/InspectorStoriesSection.tsx +92 -0
  140. package/src/kit/components/InspectorToolSection.tsx +96 -0
  141. package/src/kit/components/InspectorTransformSection.tsx +245 -0
  142. package/src/kit/components/LightExplorerPanel.tsx +433 -0
  143. package/src/kit/components/MediaProperties.tsx +145 -0
  144. package/src/kit/components/ProjectHeader.tsx +328 -0
  145. package/src/kit/components/ReactCanvasControls.tsx +358 -0
  146. package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
  147. package/src/kit/components/RootTextEditor.tsx +79 -0
  148. package/src/kit/components/SaveStatus.tsx +70 -0
  149. package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
  150. package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
  151. package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
  152. package/src/kit/components/ToolHost.tsx +380 -0
  153. package/src/kit/components/Toolbar.tsx +811 -0
  154. package/src/kit/components/TransientHint.tsx +44 -0
  155. package/src/kit/components/VersionControlSection.tsx +470 -0
  156. package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
  157. package/src/kit/components/VolterLogo.tsx +18 -0
  158. package/src/kit/components/WorktreeSwitcher.tsx +712 -0
  159. package/src/kit/components/account-documents.tsx +1162 -0
  160. package/src/kit/components/asset-documents.tsx +794 -0
  161. package/src/kit/components/asset-editor-persistence.ts +216 -0
  162. package/src/kit/components/asset-selection-section.tsx +545 -0
  163. package/src/kit/components/asset-thumbnails.tsx +307 -0
  164. package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
  165. package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  166. package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
  167. package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
  168. package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
  169. package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
  170. package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  171. package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
  172. package/src/kit/components/asset-viewers/shader-source.ts +144 -0
  173. package/src/kit/components/board-guides.ts +150 -0
  174. package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
  175. package/src/kit/components/canvas-temporary-pivot.ts +34 -0
  176. package/src/kit/components/core-utilities.tsx +94 -0
  177. package/src/kit/components/inspector-preview-section.tsx +223 -0
  178. package/src/kit/components/inspector-revert-label.ts +20 -0
  179. package/src/kit/components/inspector-selection.ts +42 -0
  180. package/src/kit/components/inspector-stories-gating.ts +171 -0
  181. package/src/kit/components/inspector-transform-subject.ts +11 -0
  182. package/src/kit/components/inspector-transform.ts +88 -0
  183. package/src/kit/components/kind-documents.tsx +544 -0
  184. package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
  185. package/src/kit/components/project-tool-documents.tsx +402 -0
  186. package/src/kit/components/scene-documents.tsx +221 -0
  187. package/src/kit/components/status-contributions.tsx +407 -0
  188. package/src/kit/components/tool-documents.tsx +302 -0
  189. package/src/kit/components/tool-schema-form.tsx +262 -0
  190. package/src/kit/components/use-after-paint.ts +41 -0
  191. package/src/kit/components/use-project-image-assets.ts +86 -0
  192. package/src/kit/components/workspace-history.ts +32 -0
  193. package/src/kit/components/world-documents.tsx +570 -0
  194. package/src/kit/components/world-overlay-gestures.ts +1939 -0
  195. package/src/kit/composite-screenshot.ts +2238 -0
  196. package/src/kit/content-entry-source-registry.ts +184 -0
  197. package/src/kit/contribution-surfaces.ts +48 -0
  198. package/src/kit/coverage/canvas-reveal.ts +192 -0
  199. package/src/kit/coverage/design-time-surfaces.ts +101 -0
  200. package/src/kit/coverage/ontology-invariants.ts +466 -0
  201. package/src/kit/coverage/session-vitals.ts +503 -0
  202. package/src/kit/crash-null-boundary.ts +36 -0
  203. package/src/kit/creation-site-edit.ts +1491 -0
  204. package/src/kit/creation-site-registry.ts +160 -0
  205. package/src/kit/delegate-harness-registry.ts +134 -0
  206. package/src/kit/document-areas.ts +70 -0
  207. package/src/kit/document-context-registry.ts +193 -0
  208. package/src/kit/document-open-registry.ts +200 -0
  209. package/src/kit/document-play-extension.ts +221 -0
  210. package/src/kit/document-preview-source.ts +20 -0
  211. package/src/kit/document-renderer-session.ts +138 -0
  212. package/src/kit/document-stage-sessions.ts +26 -0
  213. package/src/kit/document-viewports.ts +120 -0
  214. package/src/kit/editor-api.ts +46 -0
  215. package/src/kit/editor-chrome-capture.ts +136 -0
  216. package/src/kit/editor-commands.ts +176 -0
  217. package/src/kit/editor-console.ts +580 -0
  218. package/src/kit/editor-current-view.ts +56 -0
  219. package/src/kit/editor-document-probe.ts +1168 -0
  220. package/src/kit/editor-git-client.ts +115 -0
  221. package/src/kit/editor-hotkeys.ts +728 -0
  222. package/src/kit/editor-lease-view.ts +39 -0
  223. package/src/kit/editor-lease.ts +415 -0
  224. package/src/kit/editor-mode.ts +19 -0
  225. package/src/kit/editor-notifications.ts +140 -0
  226. package/src/kit/editor-presence.ts +563 -0
  227. package/src/kit/editor-presentation-activity.ts +58 -0
  228. package/src/kit/editor-presentation-notice.ts +42 -0
  229. package/src/kit/editor-runtime.tsx +147 -0
  230. package/src/kit/editor-server-response.ts +86 -0
  231. package/src/kit/editor-session-attribution.ts +85 -0
  232. package/src/kit/editor-session-mode.ts +54 -0
  233. package/src/kit/editor-state-facets.ts +74 -0
  234. package/src/kit/editor-view-presentation.ts +777 -0
  235. package/src/kit/environment-images.ts +58 -0
  236. package/src/kit/eyedropper-session.ts +60 -0
  237. package/src/kit/files/file-provider.ts +62 -0
  238. package/src/kit/files/project-files.ts +270 -0
  239. package/src/kit/finders/index.ts +137 -0
  240. package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
  241. package/src/kit/frame/frame-parts.ts +30 -0
  242. package/src/kit/framed-document-capture.ts +34 -0
  243. package/src/kit/game-globals-prelude.ts +143 -0
  244. package/src/kit/game-surface-defaults.ts +33 -0
  245. package/src/kit/gameplay-dom-recording.ts +318 -0
  246. package/src/kit/gameplay-export-state.ts +14 -0
  247. package/src/kit/gameplay-replay.ts +417 -0
  248. package/src/kit/gameplay-session-time.ts +9 -0
  249. package/src/kit/gameplay-sessions.ts +204 -0
  250. package/src/kit/hierarchy-component-marks.ts +298 -0
  251. package/src/kit/hierarchy-internals.ts +197 -0
  252. package/src/kit/hierarchy-kind-icon.ts +217 -0
  253. package/src/kit/hierarchy-menu-registry.ts +67 -0
  254. package/src/kit/hierarchy-node-rows.ts +307 -0
  255. package/src/kit/hierarchy-panel-view.ts +280 -0
  256. package/src/kit/hierarchy-projection.ts +76 -0
  257. package/src/kit/hierarchy-row-media.ts +45 -0
  258. package/src/kit/hierarchy-row-model.ts +308 -0
  259. package/src/kit/hierarchy-rows.ts +11 -0
  260. package/src/kit/hierarchy-walk.ts +86 -0
  261. package/src/kit/history/editor-session.ts +25 -0
  262. package/src/kit/history/history-commands.ts +147 -0
  263. package/src/kit/history/history-delegate.ts +187 -0
  264. package/src/kit/history/history-limit-notices.ts +43 -0
  265. package/src/kit/history/history-service.ts +1189 -0
  266. package/src/kit/history/persistence-coordinator.ts +35 -0
  267. package/src/kit/history/project-file-history.ts +386 -0
  268. package/src/kit/history/project-root-history-backends.ts +139 -0
  269. package/src/kit/history/resource-registry.ts +209 -0
  270. package/src/kit/history/snapshot-store.ts +103 -0
  271. package/src/kit/history/source-history-backend.ts +546 -0
  272. package/src/kit/history-types.ts +124 -0
  273. package/src/kit/hmr-registration-group.ts +67 -0
  274. package/src/kit/hmr-stable-react-context.ts +23 -0
  275. package/src/kit/hotkeys.ts +190 -0
  276. package/src/kit/inference-diagnostics.ts +69 -0
  277. package/src/kit/initial-project.ts +80 -0
  278. package/src/kit/inspection/active-subject.ts +571 -0
  279. package/src/kit/inspection/active-surface.ts +142 -0
  280. package/src/kit/inspection/compose-subject.ts +1055 -0
  281. package/src/kit/inspection/compose.ts +7 -0
  282. package/src/kit/inspection/display.ts +171 -0
  283. package/src/kit/inspection/document-subject.ts +109 -0
  284. package/src/kit/inspection/game-subject.ts +85 -0
  285. package/src/kit/inspection/null-subject.ts +119 -0
  286. package/src/kit/inspection/serialize.ts +357 -0
  287. package/src/kit/inspection/use-active-inspection.ts +180 -0
  288. package/src/kit/inspection-model.ts +542 -0
  289. package/src/kit/inspection-node-media.ts +58 -0
  290. package/src/kit/inspector-presentation.ts +203 -0
  291. package/src/kit/inspector-property-grouping.ts +64 -0
  292. package/src/kit/inspector-section-registry.ts +221 -0
  293. package/src/kit/instance-source-actions.ts +163 -0
  294. package/src/kit/js-heap.ts +71 -0
  295. package/src/kit/key-actions.ts +91 -0
  296. package/src/kit/keymap-presets.ts +428 -0
  297. package/src/kit/layout-policy.ts +31 -0
  298. package/src/kit/light-explorer-model.ts +134 -0
  299. package/src/kit/live-canvas-frame.ts +55 -0
  300. package/src/kit/live-document.ts +296 -0
  301. package/src/kit/live-gesture-lock.ts +50 -0
  302. package/src/kit/live-seam-evidence.ts +11 -0
  303. package/src/kit/live-session-registry.ts +220 -0
  304. package/src/kit/live-transition.ts +391 -0
  305. package/src/kit/manifest-project.ts +107 -0
  306. package/src/kit/module-fetch-diagnosis.ts +192 -0
  307. package/src/kit/mount-failure-report.ts +154 -0
  308. package/src/kit/native-selection-style.ts +497 -0
  309. package/src/kit/object3d-document-write-policy.ts +137 -0
  310. package/src/kit/packaged-runtime.ts +108 -0
  311. package/src/kit/palettes/maya.palette.json +57 -0
  312. package/src/kit/palettes/substance.palette.json +57 -0
  313. package/src/kit/performance-profiler.ts +367 -0
  314. package/src/kit/performance-sources.ts +69 -0
  315. package/src/kit/photograph-notice.ts +141 -0
  316. package/src/kit/play-boot-phase.ts +166 -0
  317. package/src/kit/play-camera-flight.ts +35 -0
  318. package/src/kit/png-encode.worker.ts +26 -0
  319. package/src/kit/presentation-surface.ts +248 -0
  320. package/src/kit/product-command.ts +90 -0
  321. package/src/kit/project-adapter.ts +1140 -0
  322. package/src/kit/project-asset-refresh.ts +23 -0
  323. package/src/kit/project-asset-roots.ts +68 -0
  324. package/src/kit/project-local-state.ts +151 -0
  325. package/src/kit/project-manager.ts +243 -0
  326. package/src/kit/project-module-changes.ts +201 -0
  327. package/src/kit/project-module-split.ts +270 -0
  328. package/src/kit/project-play-layers.ts +25 -0
  329. package/src/kit/project-provenance.ts +115 -0
  330. package/src/kit/project-ready.ts +42 -0
  331. package/src/kit/project-shape.ts +68 -0
  332. package/src/kit/project-tools.ts +107 -0
  333. package/src/kit/projection-types.ts +44 -0
  334. package/src/kit/readiness.ts +113 -0
  335. package/src/kit/renderer-resource-counts.ts +27 -0
  336. package/src/kit/reported-play-state.ts +90 -0
  337. package/src/kit/resolve-contributed-command.ts +14 -0
  338. package/src/kit/resolve-relative-specifier.ts +33 -0
  339. package/src/kit/retained-document-states.ts +91 -0
  340. package/src/kit/scene-document-plan.ts +320 -0
  341. package/src/kit/scene-live-open.ts +210 -0
  342. package/src/kit/scoped-game-css.ts +152 -0
  343. package/src/kit/served-url.ts +5 -0
  344. package/src/kit/session-close.ts +17 -0
  345. package/src/kit/session-tombstone.ts +127 -0
  346. package/src/kit/settings/settings-provider.ts +82 -0
  347. package/src/kit/settings-store.ts +348 -0
  348. package/src/kit/shell-document-state.ts +27 -0
  349. package/src/kit/shell-store-door.ts +45 -0
  350. package/src/kit/shell-store.ts +722 -0
  351. package/src/kit/source-conflict.ts +122 -0
  352. package/src/kit/stage-context.ts +377 -0
  353. package/src/kit/stage-invalidation.ts +25 -0
  354. package/src/kit/stage-store-registry.ts +69 -0
  355. package/src/kit/startup-failure.ts +80 -0
  356. package/src/kit/state-report-deferral.ts +73 -0
  357. package/src/kit/storage/host-files-storage.ts +97 -0
  358. package/src/kit/storage/http-storage.ts +174 -0
  359. package/src/kit/storage/index.ts +75 -0
  360. package/src/kit/storage/mem-storage.ts +158 -0
  361. package/src/kit/storage/path-lock.ts +44 -0
  362. package/src/kit/storage/paths.ts +26 -0
  363. package/src/kit/storage-types.ts +127 -0
  364. package/src/kit/stories/StoryPreviewMount.tsx +306 -0
  365. package/src/kit/stories/compose-project-stories.ts +255 -0
  366. package/src/kit/stories/prefabs-finder.ts +54 -0
  367. package/src/kit/stories/prefabs-from-stories.ts +182 -0
  368. package/src/kit/stories/project-story-regions.ts +24 -0
  369. package/src/kit/stories/story-capture.ts +579 -0
  370. package/src/kit/stories/story-declared-medium.ts +126 -0
  371. package/src/kit/stories/story-discovery.ts +176 -0
  372. package/src/kit/stories/story-dom-runtime.ts +78 -0
  373. package/src/kit/stories/story-grouping.ts +111 -0
  374. package/src/kit/stories/story-mount-turn.ts +27 -0
  375. package/src/kit/stories/story-presentation.ts +215 -0
  376. package/src/kit/stories/story-preview-component.ts +7 -0
  377. package/src/kit/stories/story-registry.ts +530 -0
  378. package/src/kit/stories-scope.ts +35 -0
  379. package/src/kit/story-document-openers.ts +36 -0
  380. package/src/kit/story-thumbnails.ts +47 -0
  381. package/src/kit/surface-keyboard.ts +101 -0
  382. package/src/kit/surface-state.ts +135 -0
  383. package/src/kit/system-seam-evidence.ts +72 -0
  384. package/src/kit/tab-census.ts +202 -0
  385. package/src/kit/tab-lifecycle-client.ts +227 -0
  386. package/src/kit/theme-library.ts +897 -0
  387. package/src/kit/theme-preference.ts +429 -0
  388. package/src/kit/three-viewport-presentation.ts +23 -0
  389. package/src/kit/tool-contribution-play.ts +74 -0
  390. package/src/kit/tool-loader.ts +1918 -0
  391. package/src/kit/transform-mode-request.ts +66 -0
  392. package/src/kit/transient-hint.ts +78 -0
  393. package/src/kit/transport-strip.tsx +174 -0
  394. package/src/kit/ui-source/adapter-region-includes.ts +238 -0
  395. package/src/kit/ui-source/file-region-resolver.ts +302 -0
  396. package/src/kit/ui-source/inspect.ts +775 -0
  397. package/src/kit/ui-source/source-write-backend.ts +605 -0
  398. package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
  399. package/src/kit/user-local-state.ts +105 -0
  400. package/src/kit/viewport-activation-timings.ts +840 -0
  401. package/src/kit/viewport-editor-controls.ts +22 -0
  402. package/src/kit/viewport-presentation.ts +668 -0
  403. package/src/kit/viewport-surface-status.tsx +55 -0
  404. package/src/kit/wait-until.ts +37 -0
  405. package/src/kit/worker-call-metrics.ts +166 -0
  406. package/src/kit/workspace-areas.ts +191 -0
  407. package/src/kit/workspace-aux-commands.ts +11 -0
  408. package/src/kit/workspace-available-documents.ts +142 -0
  409. package/src/kit/workspace-core-utilities.ts +31 -0
  410. package/src/kit/workspace-document-ids.ts +59 -0
  411. package/src/kit/workspace-document-registry.ts +624 -0
  412. package/src/kit/workspace-document-restore.ts +146 -0
  413. package/src/kit/workspace-host-commands.ts +141 -0
  414. package/src/kit/workspace-persistence-gate.ts +40 -0
  415. package/src/kit/workspace-play-utilities.ts +44 -0
  416. package/src/kit/workspace-presets.ts +446 -0
  417. package/src/kit/workspace-regions.ts +276 -0
  418. package/src/kit/workspace-static-panels.ts +73 -0
  419. package/src/kit/workspace-status-registry.ts +121 -0
  420. package/src/kit/workspace-storage.ts +35 -0
  421. package/src/kit/workspace-style.ts +226 -0
  422. package/src/kit/workspace-utility-commands.ts +74 -0
  423. package/src/kit/workspace-utility-registry.ts +263 -0
  424. package/src/kit/world-adoption-event.ts +23 -0
  425. package/src/kit/world-adoption.ts +115 -0
  426. package/src/kit/world-canvas-viewport-state.ts +35 -0
  427. package/src/kit/world-document-routing.ts +104 -0
  428. package/src/kit/world-pan-state.ts +198 -0
  429. package/src/kit/write-pipe.ts +173 -0
  430. package/src/layout-arrangements.ts +5 -0
  431. package/src/layouts.tsx +108 -0
  432. package/src/looks.ts +16 -0
  433. package/src/project/output-roots.ts +73 -0
  434. package/src/project/tab-census.ts +155 -0
  435. package/src/project-tool-catalog.ts +104 -0
  436. package/src/selection.tsx +107 -0
  437. package/src/services.ts +18 -0
  438. package/src/session/build-report.ts +22 -0
  439. package/src/session/collaboration-types.ts +262 -0
  440. package/src/session/command-table.ts +327 -0
  441. package/src/session/discovery.ts +100 -0
  442. package/src/session/editor-brand.ts +48 -0
  443. package/src/session/editor-compatibility.ts +329 -0
  444. package/src/session/editor-control-lifecycle.ts +68 -0
  445. package/src/session/editor-control-protocol.ts +5 -0
  446. package/src/session/entrypoint-selection-readers.ts +66 -0
  447. package/src/session/entrypoint-selection-source.ts +120 -0
  448. package/src/session/game-css-scope.ts +30 -0
  449. package/src/session/hosted-attachment.ts +225 -0
  450. package/src/session/limited-view.ts +82 -0
  451. package/src/session/product-create.ts +24 -0
  452. package/src/session/product-locator.ts +478 -0
  453. package/src/session/project-module-url.ts +242 -0
  454. package/src/session/project-serving.ts +164 -0
  455. package/src/session/project-upgrade.ts +669 -0
  456. package/src/session/registry-format.ts +210 -0
  457. package/src/session/relative-path-guard.ts +56 -0
  458. package/src/session/scoped-game-css.ts +461 -0
  459. package/src/session/source-glob.ts +15 -0
  460. package/src/session/tool-contribution-convention.ts +123 -0
  461. package/src/session/workbench-locator.ts +712 -0
  462. package/src/session.ts +41 -0
  463. package/src/share.ts +160 -0
  464. package/src/source-analysis.ts +28 -0
  465. package/src/source-authoring.ts +439 -0
  466. package/src/tools/errors.ts +91 -0
  467. package/src/tools/provider-execution.ts +70 -0
  468. package/src/tools/registry.ts +341 -0
  469. package/src/tools/types.ts +159 -0
  470. package/src/transport.ts +100 -0
  471. package/src/types.ts +1693 -0
  472. package/src/views.ts +164 -0
  473. package/src/widgets/design-system.ts +93 -0
  474. package/src/widgets/editor-appearance.ts +151 -0
  475. package/src/widgets/editor-material.ts +83 -0
  476. package/src/widgets/icon-set-registry.ts +105 -0
  477. package/src/widgets/index.ts +71 -0
  478. package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
  479. package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
  480. package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
  481. package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
  482. package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
  483. package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
  484. package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
  485. package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
  486. package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
  487. package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
  488. package/src/widgets/inspector-widgets/color-utils.ts +201 -0
  489. package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
  490. package/src/widgets/inspector-widgets/index.ts +25 -0
  491. package/src/widgets/inspector-widgets/shared.tsx +140 -0
  492. package/src/widgets/interactive-edit-scope.ts +33 -0
  493. package/src/widgets/patterns/Dialog.tsx +140 -0
  494. package/src/widgets/patterns/Fields.tsx +44 -0
  495. package/src/widgets/patterns/List.tsx +25 -0
  496. package/src/widgets/patterns/StateSurface.tsx +40 -0
  497. package/src/widgets/patterns/Surfaces.tsx +122 -0
  498. package/src/widgets/patterns/Tabs.tsx +80 -0
  499. package/src/widgets/patterns/Toolbar.tsx +72 -0
  500. package/src/widgets/patterns/Tree.tsx +72 -0
  501. package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
  502. package/src/widgets/primitives/Button.tsx +62 -0
  503. package/src/widgets/primitives/ColorInput.tsx +78 -0
  504. package/src/widgets/primitives/DraftTextInput.tsx +63 -0
  505. package/src/widgets/primitives/EditorIcon.tsx +157 -0
  506. package/src/widgets/primitives/FormControls.tsx +88 -0
  507. package/src/widgets/primitives/HoverPreview.tsx +96 -0
  508. package/src/widgets/primitives/JsonInput.tsx +113 -0
  509. package/src/widgets/primitives/Layout.tsx +100 -0
  510. package/src/widgets/primitives/Menu.tsx +161 -0
  511. package/src/widgets/primitives/NumberInput.tsx +169 -0
  512. package/src/widgets/primitives/Panel.tsx +80 -0
  513. package/src/widgets/primitives/SectionHeader.tsx +77 -0
  514. package/src/widgets/primitives/Text.tsx +54 -0
  515. package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
  516. package/src/widgets/primitives/Tooltip.tsx +204 -0
  517. package/src/widgets/primitives/Vec3Input.tsx +70 -0
  518. package/src/widgets/primitives/banner-tones.ts +32 -0
  519. package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
  520. package/src/widgets/primitives/editor-icons.ts +254 -0
  521. package/src/widgets/primitives/panel-header-styles.ts +42 -0
  522. package/src/widgets/theme.ts +2841 -0
  523. package/src/widgets/z-index.ts +25 -0
@@ -0,0 +1,1140 @@
1
+ /**
2
+ * THE ADAPTER LOADER — the host half of `editor/volter.adapter.ts`.
3
+ *
4
+ * ARCHITECTURE-CORE §The editor protocol: every game supplies a SERVER — its
5
+ * adapter — and the editor is a universal CLIENT of it. This module is where a
6
+ * project's adapter becomes a live binding table:
7
+ *
8
+ * 1. the project has `editor/volter.adapter.ts` ⇒ **the project's own file wins,
9
+ * always**. It is imported through the SAME door every other
10
+ * project-owned module goes through (`project-module-url.ts`'s
11
+ * `fsImportPath` — the ONE owner of `/@fs/` urls for project code, PD-3)
12
+ * and its default export is parsed by `parseAdapterDefinition`, which
13
+ * rejects an unknown key by name. This branch is FIRST because a game
14
+ * supplies its own protocol server: a project that ships the declaration
15
+ * has stated its table, and no host-side table may quietly outrank it —
16
+ * not even when its ingest root id happens to match a registry key
17
+ * (a user's `cuberun` folder is not ours);
18
+ * 2. the project is an INGEST GAME whose adapter the host's in-tree registry
19
+ * holds, and it shipped none of its own ⇒ that module is the adapter.
20
+ * Placement follows REALM (ARCHITECTURE-CORE §The editor protocol): a
21
+ * vendored game's bytes are served verbatim, so its host-realm declaration
22
+ * lives in `ingest/games/<id>/editor/volter.adapter.ts` and is imported through the
23
+ * registry glob, never through the project's file routes. **This branch is
24
+ * REACHABLE only because the step-1 probe can answer "no"**: the realm
25
+ * rule keeps a vendored game's adapter out of its served bundle, but
26
+ * "the file is not on disk" and "the probe says absent" were different
27
+ * answers until `probeProjectFile` learned to recognize the SPA fallback
28
+ * — a 200 serving `index.html` for every missing project-root path, on
29
+ * both the dev server and the hosted surface. An existence check reading
30
+ * `res.ok` is a constant TRUE here and makes this branch dead code, which
31
+ * is exactly how every vendored game silently lost its scene table once.
32
+ * **A binding taken from here is NEVER silent**:
33
+ * the facet's `source` says `'registry'` and its `modulePath` names the
34
+ * repo file, so the editor's `status` command reports whose declaration is running. A
35
+ * project bound this way did not ship that table; the host did, and the
36
+ * user is entitled to read that off the same door as everything else;
37
+ * 3. the project has NONE ⇒ it gets `nativeAdapter()`. **That absence IS the
38
+ * declared native default, not a silent fallback**: the same binding table
39
+ * the template ships explicitly, chosen because the project declared
40
+ * nothing else — there is no third behavior for "no adapter".
41
+ *
42
+ * The ordering also settles the one folder that can answer BOTH probes: an
43
+ * in-tree ingest fixture keeps its manifest and its adapter side by side, so
44
+ * opening `ingest/games/<id>/` as a folder project reaches THE SAME FILE
45
+ * through the project door. One declaration, one winner, decided by a rule that
46
+ * does not ask which population the project belongs to.
47
+ *
48
+ * It is also the ONE sanctioned importer of `@editor/finders`
49
+ * (`packages/engine/test/finder-import-boundary.test.ts` is the tripwire). The
50
+ * engine never runs a finder nobody selected; the loader runs exactly the
51
+ * selections the adapter's table names, with the inputs gathered here.
52
+ *
53
+ * The resolved table is published as the `adapter` facet of `/__editor/state`
54
+ * (`command-listener.ts`'s `collectState`), which is the minimal proof-of-load
55
+ * client: the editor's `status` command / the editor's `eval 'await editor.status()'` command read which
56
+ * adapter loaded, its regions, and its scene table.
57
+ *
58
+ * The OBSERVATION table has one client and it is not the facet: the ingest
59
+ * mount's system-adapter assembly (`ingest/ingest-render-debug.ts`'s
60
+ * `wireIngestSystems`) projects the declarations onto the host's ordinary
61
+ * `SystemAdapters.debug` alongside the game's own contract projection, so
62
+ * `game.providers()` / `game.state()` reach an adapter declaration through the
63
+ * SAME door first-party content uses. The facet publishes only the statically
64
+ * readable half (ids, kinds, whether an answer is bound); the closures travel
65
+ * host-side through `adapter-observation.ts`, because a closure cannot be a
66
+ * wire shape.
67
+ *
68
+ * The scene table has TWO real clients (ARCHITECTURE-CORE §Editor). The EDIT
69
+ * TAB ROW is the first: `components/scene-documents.tsx` subscribes to this
70
+ * facet and projects `scenes` as workspace documents — every authorable scene
71
+ * a tab, the default open by default — and `command-listener.ts`'s `open` verb
72
+ * resolves one entry through the same pure decision table
73
+ * (`scene-document-plan.ts`). Play's `open()`, walking the table as live
74
+ * navigation, is the second and is a later rung.
75
+ */
76
+
77
+ import type { EntrypointSource, FinderInput, ProjectComponentRef } from './finders/index';
78
+ import {
79
+ type FinderResult,
80
+ type ProjectSourceFile,
81
+ finderReadsSources,
82
+ registerContributedFinder,
83
+ runFinderSelection,
84
+ } from './finders/index';
85
+ import { fsImportPath } from '@volter/sdk/session/project-module-url';
86
+ import type {
87
+ AdapterDefinition,
88
+ AdapterRegion,
89
+ DocumentEntry,
90
+ ObservationDeclaration,
91
+ ObservationKind,
92
+ } from '@volter/project/adapter/adapter-module';
93
+ import {
94
+ nativeAdapter,
95
+ parseAdapterDefinition,
96
+ regionsFromManifestRoots,
97
+ unmatchedRegionIncludeIds,
98
+ } from '@volter/project/adapter/adapter-module';
99
+ import { declaredRoots, ingestRoots } from '@volter/project/adapter/manifest-interpreter';
100
+ import type { ResolvedGameManifest } from '@volter/project/manifest/load';
101
+ import { activeProjectKey } from '@volter/sdk/kit/active-project';
102
+ import { setAdapterEditorConfiguration } from '@volter/sdk/kit/adapter-editor-config';
103
+ import { setAdapterInputBinding, setAdapterObservations } from '@volter/sdk/kit/adapter-observation';
104
+ import { connectSourceFileEvents } from '@volter/sdk/kit/asset-events';
105
+ import {
106
+ listProjectComponents,
107
+ listProjectSourceFiles,
108
+ probeProjectFile,
109
+ readProjectSourceText,
110
+ } from '@volter/sdk/kit/editor-api';
111
+ import { editorConsole } from '@volter/sdk/kit/editor-console';
112
+ import { fetchGameManifest } from '@volter/sdk/kit/manifest-project';
113
+ import { setPresentationRegions } from '@volter/sdk/kit/presentation-surface';
114
+ import { type ActiveProject, getCurrentProject, onProjectChange } from '@volter/sdk/kit/project-manager';
115
+ import { projectModuleChangeMatches, subscribeProjectModuleChange } from '@volter/sdk/kit/project-module-changes';
116
+ import { notifyProjectShapeChanged, registerDocumentKindsSupplier } from '@volter/sdk/kit/project-shape';
117
+ import { resolveRelativeSpecifier } from '@volter/sdk/kit/resolve-relative-specifier';
118
+ import { storyPrefabsFinder } from './stories/prefabs-finder';
119
+ import {
120
+ contributedFinderModules,
121
+ refreshProjectToolContributions,
122
+ subscribeToolContributions,
123
+ } from './tool-loader';
124
+
125
+ import { ADAPTER_MODULE_FILENAME } from '@volter/sdk/kit/adapter-module';
126
+
127
+ /** The resolved scene table — the adapter's declarations plus what its own
128
+ * finder selections found. */
129
+ export interface ResolvedDocumentTable {
130
+ readonly entries: readonly DocumentEntry[];
131
+ /** `null` = nothing settled a default, which stays a fact rather than a pick. */
132
+ readonly default: string | null;
133
+ }
134
+
135
+ /**
136
+ * One observation slot as it travels over `/__editor/state` — the statically
137
+ * readable half of an {@link ObservationDeclaration}, and nothing else.
138
+ *
139
+ * `answer` is a CLOSURE the host evaluates against the mounted game, so it
140
+ * cannot be serialized and is deliberately not here; `answered` carries the one
141
+ * fact its presence states. That split is the two-state rule made visible on
142
+ * the wire: `answered: false` reads as "declared, nothing binds it yet", which
143
+ * is a different answer from the slot not appearing at all. The closures
144
+ * themselves stay host-side in `adapter-observation.ts`, where the mount reads
145
+ * them.
146
+ */
147
+ export interface ProjectObservationFacet {
148
+ readonly id: string;
149
+ readonly kind: ObservationKind;
150
+ /** Whether the declaration binds an `answer`. */
151
+ readonly answered: boolean;
152
+ }
153
+
154
+ export interface ProjectAdapterFacet {
155
+ /**
156
+ * WHOSE declaration is running — the one field that answers it, because
157
+ * "a module loaded" never did.
158
+ *
159
+ * - `project` — the project's own `editor/volter.adapter.ts` supplied the table.
160
+ * - `registry` — the HOST's in-tree ingest registry supplied it, matched on
161
+ * this project's ingest root id. The project did not ship this
162
+ * declaration; a reader must be able to see that without pattern-matching
163
+ * a path, which is why it is a value here and not an inference from
164
+ * {@link modulePath}.
165
+ * - `native` — the project declared none and got `nativeAdapter()`, the
166
+ * declared native default.
167
+ */
168
+ readonly source: 'project' | 'registry' | 'native';
169
+ /**
170
+ * Where the loaded module came from, or `null` for the native default:
171
+ * project-relative for a project that owns its own file, and the REPO path
172
+ * (`packages/editor/src/ingest/games/<id>/editor/volter.adapter.ts`) for a registry
173
+ * binding, whose adapter is host-realm by the placement rule and therefore
174
+ * has no project-relative home to name.
175
+ */
176
+ readonly modulePath: string | null;
177
+ readonly editor?: Omit<NonNullable<AdapterDefinition['editor']>, 'Layout'> & {
178
+ readonly layout?: string;
179
+ };
180
+ readonly regions: readonly AdapterRegion[];
181
+ readonly scenes: ResolvedDocumentTable;
182
+ /** The observation table, wire-shaped. See {@link ProjectObservationFacet}. */
183
+ readonly observation: readonly ProjectObservationFacet[];
184
+ /** Finder diagnostics — what could not be answered, and why. Never swallowed. */
185
+ readonly notes: readonly string[];
186
+ /** True while a declared finder has no registration yet — a contribution's
187
+ * finder before the contribution pass — so the table is NOT final. The
188
+ * chrome treats the document kinds as unknown until this clears, rather
189
+ * than concluding "no model" from a table the pass is about to fill. */
190
+ readonly documentsPending: boolean;
191
+ /**
192
+ * A load/parse failure — or a probe that could not answer — named. Non-null
193
+ * means the project's OWN adapter did not load, and what stood in is
194
+ * whatever {@link source} says: a registry binding, or the declared native
195
+ * default. Never swallowed, because the editor still opening on a broken
196
+ * `editor/volter.adapter.ts` is only acceptable while it SAYS the file is broken.
197
+ */
198
+ readonly error: string | null;
199
+ }
200
+
201
+ let _facet: ProjectAdapterFacet | null = null;
202
+ /** The parsed declaration behind {@link _facet}, kept for
203
+ * {@link projectAdapterDefinition}. `null` whenever no declaration FILE was
204
+ * parsed — see that function for why the native default is not stored here. */
205
+ let _definition: AdapterDefinition | null = null;
206
+ let _owner = '__unscoped__';
207
+ /** The load in flight, so a reader can wait for the answer instead of racing
208
+ * it. See {@link projectAdapterDefinition}. */
209
+ let _inFlight: Promise<void> | null = null;
210
+ let _refreshEpoch = 0;
211
+ const _listeners = new Set<() => void>();
212
+
213
+ /** The loaded adapter table, or `null` before the first load completes —
214
+ * "nobody looked yet" is deliberately distinct from "the project has none". */
215
+ export function projectAdapterFacet(): ProjectAdapterFacet | null {
216
+ return _owner === activeProjectKey() ? _facet : null;
217
+ }
218
+
219
+ /**
220
+ * THE PARSED DECLARATION, for the one consumer that needs the whole thing:
221
+ * `binding-resolver.ts`, which hands it to `resolveRootBinding` so it lands on
222
+ * `binding.project.definition`.
223
+ *
224
+ * Two deliberate properties:
225
+ *
226
+ * - It **awaits the load in flight**. A resolve that read module state
227
+ * synchronously would answer `null` for a project whose adapter is still
228
+ * loading — a project-switch-then-Play race — and `null` on that binding
229
+ * means "this project declares no adapter", which would be a fabricated
230
+ * fact. Waiting is what makes the `null` honest.
231
+ * - The **declared native default is `null` here**, not `nativeAdapter()`.
232
+ * `binding.project.definition` answers "what did the PROJECT declare", so a
233
+ * project that shipped no declaration file must read as `null`. Which table
234
+ * actually stood in — project, registry, or native — is
235
+ * {@link ProjectAdapterFacet.source}'s job, and that is where a consumer
236
+ * asking the other question looks.
237
+ */
238
+ export async function projectAdapterDefinition(): Promise<AdapterDefinition | null> {
239
+ if (_inFlight) await _inFlight;
240
+ return _owner === activeProjectKey() ? _definition : null;
241
+ }
242
+
243
+ /** Initial layout waits for the adapter declaration, not deferred finder contributions. */
244
+ export function waitForProjectAdapter(): Promise<void> {
245
+ const ready = () => !getCurrentProject() || Boolean(projectAdapterFacet());
246
+ if (ready()) return Promise.resolve();
247
+ return new Promise<void>((resolve) => {
248
+ const stop = subscribeProjectAdapter(() => {
249
+ if (ready()) {
250
+ stop();
251
+ resolve();
252
+ }
253
+ });
254
+ });
255
+ }
256
+
257
+ /** A package opening a document during boot needs the final finder-backed
258
+ * table, not the initial declaration with contributed finders still pending. */
259
+ export async function resolvedProjectDocumentTable(): Promise<ResolvedDocumentTable> {
260
+ await (await import('@volter/sdk/kit/initial-project')).projectBootstrapSettled();
261
+ await refreshProjectToolContributions();
262
+ await refreshProjectAdapter();
263
+ while (_inFlight) await _inFlight;
264
+ const facet = projectAdapterFacet();
265
+ if (!facet || facet.documentsPending)
266
+ throw new Error('The project document table could not finish loading; inspect the project diagnostics.');
267
+ return facet.scenes;
268
+ }
269
+
270
+ export function subscribeProjectAdapter(fn: () => void): () => void {
271
+ _listeners.add(fn);
272
+ return () => _listeners.delete(fn);
273
+ }
274
+
275
+ function publish(
276
+ next: ProjectAdapterFacet | null,
277
+ observation: readonly ObservationDeclaration[] = [],
278
+ input: AdapterDefinition['input'] = undefined,
279
+ definition: AdapterDefinition | null = null,
280
+ ): void {
281
+ _owner = activeProjectKey();
282
+ _facet = next;
283
+ _definition = definition;
284
+ setAdapterEditorConfiguration(definition?.editor);
285
+ // The region table's OTHER reader, pushed rather than pulled: the capture and
286
+ // staleness sites ask "what is this project presented on" from inside the
287
+ // ingest mount and play-mode, and having them import this loader would drag
288
+ // its whole graph along. See `presentation-surface.ts`'s note — this stays the
289
+ // one place regions are decided.
290
+ setPresentationRegions(next?.regions ?? []);
291
+ // Same push, same reason, for the half of the observation table that cannot
292
+ // travel over the wire: the `answer` closures the ingest mount evaluates
293
+ // against the mounted game (`adapter-observation.ts`).
294
+ setAdapterObservations(observation);
295
+ setAdapterInputBinding(input ?? null);
296
+ for (const fn of _listeners) fn();
297
+ notifyProjectShapeChanged();
298
+ }
299
+
300
+ /** The wire half of one declaration — closures stripped, the fact of one kept. */
301
+ function observationFacet(declaration: ObservationDeclaration): ProjectObservationFacet {
302
+ return {
303
+ id: declaration.id,
304
+ kind: declaration.kind,
305
+ answered: declaration.answer !== undefined,
306
+ };
307
+ }
308
+
309
+ /** Test seam: drop the loaded table, both halves of it. */
310
+ export function __resetProjectAdapterForTest(): void {
311
+ _facet = null;
312
+ _definition = null;
313
+ _inFlight = null;
314
+ _refreshEpoch++;
315
+ setAdapterEditorConfiguration();
316
+ setAdapterObservations([]);
317
+ setAdapterInputBinding(null);
318
+ }
319
+
320
+ /**
321
+ * Import the project's own adapter module. Cache-busted like every other
322
+ * design-time project load, because a saved `editor/volter.adapter.ts` must be readable
323
+ * without restarting the editor.
324
+ *
325
+ * The url is built by `fsImportPath` (project-module-url.ts) and NOT by
326
+ * `projectEntryImportUrl`: a mount epoch exists to keep the roots of ONE mount
327
+ * on one module instance, and the adapter is not part of any mount — it is a
328
+ * binding table read WITHOUT booting the game. Taking an epoch here would
329
+ * inflate the mount-epoch census for a module no root shares.
330
+ */
331
+ function editorConfigurationFacet(editor: NonNullable<AdapterDefinition['editor']>) {
332
+ const { Layout, ...configuration } = editor;
333
+ return {
334
+ ...configuration,
335
+ ...(Layout ? { layout: Layout.displayName ?? Layout.name ?? 'Project layout' } : {}),
336
+ };
337
+ }
338
+
339
+ async function importAdapterModule(rootPath: string): Promise<unknown> {
340
+ // The server-less tier has no `/@fs/`. A storage-backed project's adapter
341
+ // loads through the same in-browser bundling door as every project module
342
+ // (fresh bundle per call — the cache-bust for free); a STAGED hosted
343
+ // example/ingest — reached only when the realm-correct probe found the
344
+ // file among its served bytes — bundles it from its own staged URL. Never
345
+ // the backend for those: it still holds some other project
346
+ // (`browserProjectStorageBacked`, the 2026-08-27 leak).
347
+ const url = `${fsImportPath(rootPath, ADAPTER_MODULE_FILENAME)}?t=${Date.now()}`;
348
+ return (await import(/* @vite-ignore */ url)) as unknown;
349
+ }
350
+
351
+ /**
352
+ * Map a component to its region by SURFACE. Exact, not heuristic: the manifest
353
+ * schema enforces at most ONE world root per medium (ARCHITECTURE-CORE
354
+ * §Roots), so a surface names at most one region.
355
+ */
356
+ function regionForSurface(regions: readonly AdapterRegion[], surface: string): string | null {
357
+ return regions.find((region) => region.surface === surface)?.id ?? null;
358
+ }
359
+
360
+ /**
361
+ * Resolve one of an entrypoint's relative import specifiers against the paths
362
+ * the project's own component index reports. Extension/index resolution is a
363
+ * filesystem question and the finders are pure, so the host answers it here —
364
+ * from a real index, never by guessing an extension onto a path.
365
+ */
366
+ function makeResolveModule(
367
+ components: readonly { readonly path: string }[],
368
+ ): (specifier: string, fromPath: string) => string | null {
369
+ const paths = new Set(components.map((component) => component.path));
370
+ return (specifier, fromPath) => resolveRelativeSpecifier(specifier, fromPath, paths);
371
+ }
372
+
373
+ /**
374
+ * The ingest game this project IS, or `null` — the key the in-tree adapter
375
+ * registry is looked up by.
376
+ *
377
+ * It is the manifest's own single `{ ingest }` root id, which is a DECLARATION
378
+ * read off the project (never a folder-name guess), and it is the same id
379
+ * `ingest/mount-ingest-root.ts` mounts by. Two ingest roots is not this
380
+ * module's error to report — that route already fails loudly on it
381
+ * (`reportTooManyIngestRoots`) — so here it simply names no game rather than
382
+ * picking one.
383
+ */
384
+ function ingestGameIdOf(manifest: ResolvedGameManifest): string | null {
385
+ const declared = ingestRoots(manifest);
386
+ return declared.length === 1 ? declared[0]!.id : null;
387
+ }
388
+
389
+ /** What a table with no finder selections is resolved against. */
390
+ const NO_FINDER_INPUT: FinderInput = { entrypoints: [], components: [] };
391
+
392
+ async function gatherFinderInput(
393
+ manifest: ResolvedGameManifest,
394
+ regions: readonly AdapterRegion[],
395
+ definition: AdapterDefinition,
396
+ ): Promise<FinderInput> {
397
+ const entrypoints: EntrypointSource[] = [];
398
+ for (const root of declaredRoots(manifest)) {
399
+ if (!root.entry) continue;
400
+ entrypoints.push({
401
+ regionId: root.id,
402
+ path: root.entry,
403
+ source: await readProjectSourceText(root.entry),
404
+ });
405
+ }
406
+
407
+ let components: ProjectComponentRef[] = [];
408
+ const listing = await listProjectComponents().catch(
409
+ (error: unknown): Awaited<ReturnType<typeof listProjectComponents>> => ({
410
+ ok: false,
411
+ reason: error instanceof Error ? error.message : String(error),
412
+ }),
413
+ );
414
+ if (listing.ok) {
415
+ components = listing.entries.map((component) => ({
416
+ name: component.name,
417
+ path: component.path,
418
+ region: regionForSurface(regions, component.surface),
419
+ }));
420
+ } else {
421
+ // No component index (the route is unavailable): prefabs
422
+ // still resolve from their stories, they just carry no source path. That
423
+ // degrade is fine; being silent about it was not — a prefab missing its
424
+ // source path looked like the prefab, not like the index.
425
+ editorConsole.warn(
426
+ `Component index unavailable, so prefabs resolve without source paths: ${listing.reason}`,
427
+ 'project',
428
+ );
429
+ }
430
+
431
+ // Every selection's `include` globs, read once: a contributed finder sees
432
+ // the project through these sources and nothing else. The globs whose finder
433
+ // reads TEXT are kept apart from the ones whose finder takes paths alone
434
+ // (`FinderRegistration.reads`), so only the first are ever fetched.
435
+ const includes = new Set<string>();
436
+ const textIncludes: RegExp[] = [];
437
+ for (const selection of definition.documents.find ?? []) {
438
+ const readsText = finderReadsSources(selection.finder);
439
+ for (const glob of (selection as { include?: readonly string[] }).include ?? []) {
440
+ includes.add(glob);
441
+ if (readsText) textIncludes.push(globToRegExp(glob));
442
+ }
443
+ }
444
+ const sources: ProjectSourceFile[] = [];
445
+ const files: string[] = [];
446
+ if (includes.size > 0) {
447
+ // Newest first: the table keeps this order, so "the one being worked on"
448
+ // is the first candidate when a fresh checkout has to pick a document.
449
+ const paths = await listProjectSourceFiles([...includes], { order: 'mtime' }).catch(
450
+ () => [] as string[],
451
+ );
452
+ for (const path of paths) {
453
+ files.push(path);
454
+ // A PATHS-ONLY FINDER'S FILES ARE LISTED, NEVER FETCHED. `@volter/editor-blender`'s
455
+ // `modelsFromBlendFiles` includes `.blend` files and says `reads: 'files'`;
456
+ // fetching each one through the text-source route asked a multi-megabyte
457
+ // binary of a route capped at 2 MiB of text — measured live, a `413` per
458
+ // model per resolve, five on one page load, for bytes nothing read.
459
+ if (!textIncludes.some((glob) => glob.test(path))) continue;
460
+ // A BINARY under a TEXT finder's globs is still never decoded into a
461
+ // source: reading one as text would hand a finder a string of replacement
462
+ // characters and call it a source. A NUL byte is the test because it is
463
+ // the one thing no text source contains, and it asks the bytes rather
464
+ // than a list of extensions the host would then own.
465
+ const source = await readProjectSourceText(path);
466
+ if (source !== null && !source.includes('\0')) sources.push({ path, source });
467
+ }
468
+ }
469
+ return {
470
+ entrypoints,
471
+ components,
472
+ sources,
473
+ files,
474
+ resolveModule: makeResolveModule(components),
475
+ };
476
+ }
477
+
478
+ export type { EntrypointSource, FinderInput, ProjectComponentRef };
479
+
480
+ /**
481
+ * Assemble finder input from parts the caller already gathered. The live
482
+ * loader reads those parts off the editor session; the headless portfolio
483
+ * run reads them off disk. Both hand the SAME shape to
484
+ * {@link resolveDocumentTable}, which is the one fold.
485
+ */
486
+ export function buildFinderInput(parts: {
487
+ readonly entrypoints: readonly EntrypointSource[];
488
+ readonly components?: readonly ProjectComponentRef[];
489
+ }): FinderInput {
490
+ const components = parts.components ?? [];
491
+ return {
492
+ entrypoints: parts.entrypoints,
493
+ components,
494
+ resolveModule: makeResolveModule(components),
495
+ };
496
+ }
497
+
498
+ /** Run the adapter's own finder selections and fold them into one table. */
499
+ export function resolveDocumentTable(
500
+ definition: AdapterDefinition,
501
+ input: FinderInput,
502
+ ): {
503
+ table: ResolvedDocumentTable;
504
+ notes: string[];
505
+ /** Selections whose finder is not registered (yet). */
506
+ unresolved: number;
507
+ } {
508
+ const entries: DocumentEntry[] = [...(definition.documents.entries ?? [])];
509
+ const notes: string[] = [];
510
+ const settled: string[] = [];
511
+ let unresolved = 0;
512
+ for (const selection of definition.documents.find ?? []) {
513
+ let result: FinderResult;
514
+ try {
515
+ result = runFinderSelection(selection, input);
516
+ } catch (error) {
517
+ // A finder a CONTRIBUTION registers is not there until the contribution
518
+ // pass has run, which follows the first adapter load; the table is
519
+ // resolved again when it does (`subscribeToolContributions` below). A
520
+ // selection that stays unresolved is a standing note, never a throw.
521
+ notes.push(
522
+ `finder ${JSON.stringify(selection.finder)}: ${error instanceof Error ? error.message : String(error)}`,
523
+ );
524
+ unresolved += 1;
525
+ continue;
526
+ }
527
+ entries.push(...result.entries);
528
+ notes.push(...result.notes);
529
+ if (result.default !== undefined) settled.push(result.default);
530
+ }
531
+ // The adapter's own declaration wins; otherwise a default stands only when
532
+ // exactly one finder settled one. Two candidates is a CHOICE, and the host
533
+ // does not make choices the game's author can state.
534
+ const fallback = settled.length === 1 ? settled[0]! : null;
535
+ return {
536
+ table: { entries, default: definition.documents.default ?? fallback },
537
+ notes,
538
+ unresolved,
539
+ };
540
+ }
541
+
542
+ /** Which adapter this project resolved to, and the provenance of the choice. */
543
+ export interface ResolvedAdapterChoice {
544
+ readonly definition: AdapterDefinition;
545
+ readonly source: 'project' | 'registry' | 'native';
546
+ readonly modulePath: string | null;
547
+ readonly error: string | null;
548
+ }
549
+
550
+ /** The project's own `editor/volter.adapter.ts` probe, as data — the live loader and
551
+ * the headless run answer this the same way, from different IO. */
552
+ export type ProjectAdapterProbe =
553
+ | { readonly kind: 'present'; readonly module: unknown }
554
+ | { readonly kind: 'broken'; readonly error: string }
555
+ | { readonly kind: 'absent' }
556
+ | { readonly kind: 'unreachable'; readonly error: string };
557
+
558
+ /**
559
+ * AN ADAPTER DEFINITION IS SOURCED, NOT GLOBBED — the door a LANE registers
560
+ * its host-realm declarations through, in the family of
561
+ * `workspace-document-restore.ts` (a kind owns its persisted state),
562
+ * `document-open-registry.ts` (a kind owns how it opens),
563
+ * `chrome-slot-registry.ts` (the host owns the place, a package owns what sits
564
+ * there) and `content-entry-source-registry.ts` (the host owns the Content
565
+ * scope, a package owns why a component is content).
566
+ *
567
+ * IT LIVES IN THIS FILE, not beside those, and the reason is the meter. Its
568
+ * only consumer is {@link resolveAdapterDefinition} below — the one place the
569
+ * three-way precedence lives — so a separate module is a host file traded for
570
+ * a host file: MEASURED, `main.tsx` stayed at 494 with the door split out and
571
+ * dropped to 493 with it here. A registry whose whole readership is one
572
+ * function belongs in that function's module.
573
+ *
574
+ * WHAT IT REPLACED. This module used to import `ingest/registry.ts` directly
575
+ * for branch 2, so the host's boot closure carried the ingest lane's
576
+ * `import.meta.glob` over `src/ingest/games/*` — every repo-vendored game's
577
+ * manifest and adapter module in every editor boot, a `models` build that
578
+ * mounts no unmodified game included. The lane that OWNS those declarations is
579
+ * `@volter/editor-game`, which already imports that registry from three of its own
580
+ * ingest modules; it registers here from `contributions/ingest.service.ts`.
581
+ *
582
+ * NOTHING REGISTERED IS A REAL ANSWER, AND IT IS A LOUD ONE — see
583
+ * {@link RegistryAdapterProbe}'s `unanswered`.
584
+ */
585
+ export interface AdapterDefinitionSource {
586
+ /** The source's id, in its own vocabulary. Part of every teaching message. */
587
+ readonly id: string;
588
+ /** Which module registered it. Re-registering the same owner+id REPLACES,
589
+ * so an HMR re-evaluation leaves one source, not two. */
590
+ readonly owner: string;
591
+ /** Ascending; ties keep registration order. */
592
+ readonly order?: number;
593
+ /** The repo path of the declaration this source holds for `rootId`, or
594
+ * `null` when it holds none. Never throws — a source that cannot answer
595
+ * says so by returning `null`. */
596
+ readonly modulePathFor: (rootId: string) => string | null;
597
+ /** Import that declaration. Only called when `modulePathFor` named a file;
598
+ * a module that exists and throws is LEFT to throw, so the loader can name
599
+ * the failure rather than swallow it. */
600
+ readonly importModule: (rootId: string) => Promise<unknown>;
601
+ }
602
+
603
+ const _adapterSources: AdapterDefinitionSource[] = [];
604
+
605
+ /** Install a source. Returns the teardown. */
606
+ export function registerAdapterDefinitionSource(source: AdapterDefinitionSource): () => void {
607
+ const stale = _adapterSources.findIndex(
608
+ (item) => item.owner === source.owner && item.id === source.id,
609
+ );
610
+ if (stale >= 0) _adapterSources.splice(stale, 1);
611
+ _adapterSources.push(source);
612
+ _adapterSources.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
613
+ return () => {
614
+ const at = _adapterSources.indexOf(source);
615
+ if (at >= 0) _adapterSources.splice(at, 1);
616
+ };
617
+ }
618
+
619
+ /** The first source that claims this root id, and the file it claims it
620
+ * through — `null` when none does, which is NOT "this project has no
621
+ * adapter" (see {@link RegistryAdapterProbe}). */
622
+ function adapterDefinitionBinding(
623
+ rootId: string,
624
+ ): { readonly source: AdapterDefinitionSource; readonly modulePath: string } | null {
625
+ for (const source of _adapterSources) {
626
+ const modulePath = source.modulePathFor(rootId);
627
+ if (modulePath !== null) return { source, modulePath };
628
+ }
629
+ return null;
630
+ }
631
+
632
+ /**
633
+ * The SOURCED-declaration probe, as data (`adapter-definition-source-registry.ts`).
634
+ *
635
+ * `unanswered` is this half's `unreachable`, and it exists for the same reason
636
+ * the project half's does: a manifest that DECLARES an ingest root has stated
637
+ * which lane owns its declaration, so "no registered source holds one" is an
638
+ * unanswered question about the BUILD, never an absence in the project. It is
639
+ * the anti-shim rule's standing warning — the native default still stands in so
640
+ * the editor opens, and the failure is named rather than mounted quietly.
641
+ *
642
+ * `absent` remains the honest answer of the DISK half (`coverage/
643
+ * portfolio-resolve.ts`), which looks at the filesystem and can genuinely say
644
+ * the file is not there.
645
+ */
646
+ export type RegistryAdapterProbe =
647
+ | { readonly kind: 'present'; readonly module: unknown; readonly path: string }
648
+ | { readonly kind: 'broken'; readonly path: string; readonly error: string }
649
+ | { readonly kind: 'unanswered'; readonly rootId: string; readonly sources: number }
650
+ | { readonly kind: 'absent' };
651
+
652
+ function adapterModuleDefault(module_: unknown): unknown {
653
+ if (module_ !== null && typeof module_ === 'object' && 'default' in module_) {
654
+ return (module_ as { default: unknown }).default;
655
+ }
656
+ return module_;
657
+ }
658
+
659
+ /**
660
+ * THE THREE-WAY PRECEDENCE as a pure function over already-loaded modules.
661
+ * {@link resolveAdapterDefinition} is the live IO half; the headless portfolio
662
+ * run is the disk half. Neither may invent a fourth branch.
663
+ */
664
+ export function selectAdapterDefinition(
665
+ project: ProjectAdapterProbe,
666
+ registry: RegistryAdapterProbe,
667
+ ): ResolvedAdapterChoice {
668
+ if (project.kind === 'present') {
669
+ try {
670
+ return {
671
+ definition: parseAdapterDefinition(adapterModuleDefault(project.module)),
672
+ source: 'project',
673
+ modulePath: ADAPTER_MODULE_FILENAME,
674
+ error: null,
675
+ };
676
+ } catch (err) {
677
+ return {
678
+ definition: nativeAdapter(),
679
+ source: 'native',
680
+ modulePath: null,
681
+ error: `${ADAPTER_MODULE_FILENAME} did not load — ${describe(err)}`,
682
+ };
683
+ }
684
+ }
685
+ if (project.kind === 'broken') {
686
+ return {
687
+ definition: nativeAdapter(),
688
+ source: 'native',
689
+ modulePath: null,
690
+ error: `${ADAPTER_MODULE_FILENAME} did not load — ${project.error}`,
691
+ };
692
+ }
693
+
694
+ const unknown = project.kind === 'unreachable' ? project.error : null;
695
+
696
+ if (registry.kind === 'present') {
697
+ try {
698
+ return {
699
+ definition: parseAdapterDefinition(adapterModuleDefault(registry.module)),
700
+ source: 'registry',
701
+ modulePath: registry.path,
702
+ error: unknown,
703
+ };
704
+ } catch (err) {
705
+ return {
706
+ definition: nativeAdapter(),
707
+ source: 'native',
708
+ modulePath: null,
709
+ error: `${registry.path} did not load — ${describe(err)}`,
710
+ };
711
+ }
712
+ }
713
+ if (registry.kind === 'broken') {
714
+ return {
715
+ definition: nativeAdapter(),
716
+ source: 'native',
717
+ modulePath: null,
718
+ error: `${registry.path} did not load — ${registry.error}`,
719
+ };
720
+ }
721
+ if (registry.kind === 'unanswered') {
722
+ return {
723
+ definition: nativeAdapter(),
724
+ source: 'native',
725
+ modulePath: null,
726
+ error:
727
+ `volter.project.json declares the ingest root \`${registry.rootId}\`, and no registered ` +
728
+ `adapter-definition source holds a declaration for it ` +
729
+ `(${registry.sources} source${registry.sources === 1 ? '' : 's'} registered). The lane ` +
730
+ `that owns a vendored game's declaration is \`@volter/editor-game\`, which registers through ` +
731
+ `its \`ingest.service.ts\` contribution — so either this product does not compose that ` +
732
+ `package, ` +
733
+ `or its contribution pass did not load it. The declared native default is standing in, ` +
734
+ `which means this game's own scene table is NOT what the editor is showing.`,
735
+ };
736
+ }
737
+
738
+ return { definition: nativeAdapter(), source: 'native', modulePath: null, error: unknown };
739
+ }
740
+
741
+ /**
742
+ * Regions + scene table + observation from a chosen definition. The live
743
+ * loader and the headless run both land here — one fold, two IO fronts.
744
+ */
745
+ export function projectAdapterTableFrom(
746
+ manifest: ResolvedGameManifest,
747
+ choice: ResolvedAdapterChoice,
748
+ finderInput: FinderInput,
749
+ ): ProjectAdapterFacet {
750
+ const { definition, source, modulePath, error } = choice;
751
+ const regions =
752
+ definition.regions === 'manifest-roots'
753
+ ? regionsFromManifestRoots(declaredRoots(manifest), definition.regionIncludes)
754
+ : [...definition.regions];
755
+ const unmatchedRegions = unmatchedRegionIncludeIds(
756
+ declaredRoots(manifest),
757
+ definition.regionIncludes,
758
+ );
759
+ const { table, notes, unresolved } = resolveDocumentTable(definition, finderInput);
760
+ return {
761
+ source,
762
+ modulePath,
763
+ regions,
764
+ scenes: table,
765
+ documentsPending: unresolved > 0,
766
+ ...(definition.editor ? { editor: editorConfigurationFacet(definition.editor) } : {}),
767
+ observation: definition.observation.map(observationFacet),
768
+ notes:
769
+ unmatchedRegions.length > 0
770
+ ? [
771
+ ...notes,
772
+ `adapter: regionIncludes names ${unmatchedRegions.map((id) => `\`${id}\``).join(', ')}, ` +
773
+ `which no root in volter.project.json declares (roots: ${declaredRoots(manifest)
774
+ .map((root) => `\`${root.id}\``)
775
+ .join(', ')}) — those globs were merged onto nothing.`,
776
+ ]
777
+ : notes,
778
+ error,
779
+ };
780
+ }
781
+
782
+ /**
783
+ * PICK THE ADAPTER — the three-way decision this module's header states, and
784
+ * the ONLY place the precedence lives.
785
+ *
786
+ * The project's OWN file is probed FIRST and outranks the registry, whatever id
787
+ * its ingest root declares.
788
+ *
789
+ * `probeProjectFile`, not `projectFileExists`, and that is the correctness of
790
+ * this function rather than a style preference: an existence probe that reads
791
+ * `res.ok` is a MEASURED CONSTANT TRUE here, because both shipped surfaces
792
+ * answer a missing project-root path with the editor's own `index.html` at 200
793
+ * (the dev server's SPA fallback; `docs/DEPLOY.md` §Surface 3 for hosted).
794
+ * Ordering a can't-say-no probe ahead of the registry made the registry branch
795
+ * unreachable — every vendored game lost its scene table to a fabricated
796
+ * "editor/volter.adapter.ts did not load". The probe now discriminates by CONTENT (an
797
+ * HTML document is the fallback, not the file) and reports "could not tell" as
798
+ * an answer of its own.
799
+ *
800
+ * Never throws: every failure comes back as a named `error` beside a table that
801
+ * still stands up, because an editor that dies on a project's config file
802
+ * cannot be used to fix that file.
803
+ */
804
+ async function resolveAdapterDefinition(
805
+ project: ActiveProject,
806
+ manifest: ResolvedGameManifest,
807
+ ): Promise<ResolvedAdapterChoice> {
808
+ const ingestGameId = ingestGameIdOf(manifest);
809
+ const presence = await probeProjectFile(ADAPTER_MODULE_FILENAME);
810
+
811
+ if (presence === 'present') {
812
+ try {
813
+ const module_ = (await importAdapterModule(project.rootPath)) as { default?: unknown };
814
+ return selectAdapterDefinition({ kind: 'present', module: module_ }, { kind: 'absent' });
815
+ } catch (err) {
816
+ // NO fallback to the registry. The file is really there and it is really
817
+ // broken, and quietly running the host's table for a game whose author is
818
+ // staring at their own syntax error is exactly the silence the
819
+ // project-first rule exists to remove. The failure is named and the
820
+ // declared native default stands in, so the editor still opens.
821
+ return selectAdapterDefinition({ kind: 'broken', error: describe(err) }, { kind: 'absent' });
822
+ }
823
+ }
824
+
825
+ // `unreachable` is not an absence — it is an unanswered question, and saying
826
+ // so is the difference between "this project has no adapter" and "we could
827
+ // not find out". A table is published either way, because the editor has to
828
+ // open; which one stood in is what `source` says.
829
+ const projectProbe: ProjectAdapterProbe =
830
+ presence === 'unreachable'
831
+ ? {
832
+ kind: 'unreachable',
833
+ error:
834
+ `${ADAPTER_MODULE_FILENAME} could not be probed — the project's file routes did not ` +
835
+ `answer, so whether this project owns an adapter is UNKNOWN.`,
836
+ }
837
+ : { kind: 'absent' };
838
+
839
+ if (ingestGameId === null) return selectAdapterDefinition(projectProbe, { kind: 'absent' });
840
+
841
+ // THE MANIFEST ALREADY KNOWS THE LANE, so the host is not guessing. This
842
+ // project declares an ingest root and shipped no adapter of its own, which
843
+ // means its declaration lives in a SOURCE a package registers
844
+ // ({@link registerAdapterDefinitionSource}) — and contributions load behind
845
+ // the opening viewport frame (`project-tool-discovery.ts`), while this runs
846
+ // at editor init (`startProjectAdapterLoad`). A document whose owner has not
847
+ // loaded cannot resolve; saying so by WAITING is honest, where mounting the
848
+ // native default and re-mounting when the registration lands is a silent
849
+ // degrade followed by a correction (owner decision, WORK.md §The open-source
850
+ // launch, "THE GAP IS 2"). The wait is paid ONLY here: a project with no
851
+ // ingest root never reaches this line, and one whose source is already
852
+ // registered (any resolve after the first) short-circuits below.
853
+ let binding = adapterDefinitionBinding(ingestGameId);
854
+ if (binding === null) {
855
+ await waitForContributedAdapterSources(ingestGameId);
856
+ binding = adapterDefinitionBinding(ingestGameId);
857
+ }
858
+
859
+ if (binding === null) {
860
+ const unanswered = selectAdapterDefinition(projectProbe, {
861
+ kind: 'unanswered',
862
+ rootId: ingestGameId,
863
+ sources: _adapterSources.length,
864
+ });
865
+ // THE FACET IS NOT A DOOR THE USER READS. `error` reaches
866
+ // `/__editor/state` and the editor's `status` command prints it, but the editor's `console` command stayed
867
+ // SILENT while the editor showed a vendored game the native default's
868
+ // scene table — measured live, 2026-09-19, by removing the wait above and
869
+ // reading both doors. "Unresolved editor console is remaining work" is the
870
+ // contract every `volter` verb enforces, so a standing warning that never
871
+ // enters that ledger is not standing. Warning weight, not error: the
872
+ // editor opens and is usable, and what is wrong is the BUILD's lane list.
873
+ if (unanswered.error) editorConsole.warn(unanswered.error, 'ingest');
874
+ return unanswered;
875
+ }
876
+
877
+ try {
878
+ const module_ = (await binding.source.importModule(ingestGameId)) as { default?: unknown };
879
+ return selectAdapterDefinition(projectProbe, {
880
+ kind: 'present',
881
+ module: module_,
882
+ path: binding.modulePath,
883
+ });
884
+ } catch (err) {
885
+ return selectAdapterDefinition(projectProbe, {
886
+ kind: 'broken',
887
+ path: binding.modulePath,
888
+ error: describe(err),
889
+ });
890
+ }
891
+ }
892
+
893
+ /**
894
+ * THE WAIT, AND ITS NARRATION — one contribution pass per project open.
895
+ *
896
+ * `refreshProjectToolContributions` resolves only once the NEWEST pass has
897
+ * installed (its own docblock, and the four callers that already treat it that
898
+ * way — `project-tool-discovery.ts`, `components/tool-documents.tsx`,
899
+ * `workspace-state-persistence.ts`, `editor-view-presentation.ts`). It is a
900
+ * known startup cost, not a hung fetch: the workspace restore takes the same
901
+ * await for the same reason and records it at ~9s on a cold `full` scaffold.
902
+ *
903
+ * LATCHED PER PROJECT, and that is correctness rather than thrift. The pass's
904
+ * completion fires `subscribeToolContributions`, whose hook below re-resolves
905
+ * the table when the FINDER set changed — so an unlatched await would start a
906
+ * fresh pass inside every re-resolve it caused, and the two would drive each
907
+ * other forever. One pass per project open; every later resolve reads the
908
+ * settled promise.
909
+ *
910
+ * AND IT SAYS SO ON SCREEN. `components/ProjectLayout.tsx` already holds the
911
+ * boot behind `waitForProjectAdapter()` with a `role="status"` line; while this
912
+ * promise is pending that line NAMES the mechanism instead of reading "Loading
913
+ * project layout…" for nine silent seconds. An action whose success can be
914
+ * invisible must narrate itself.
915
+ */
916
+ let _sourceWaitOwner: string | null = null;
917
+ let _sourceWait: Promise<void> | null = null;
918
+ let _waitNarration: string | null = null;
919
+
920
+ /** What the adapter load is waiting for, for the boot's own status line —
921
+ * `null` whenever it is not waiting on anything a user should be told about. */
922
+ export function projectAdapterWaitNarration(): string | null {
923
+ return _waitNarration;
924
+ }
925
+
926
+ function waitForContributedAdapterSources(rootId: string): Promise<void> {
927
+ const owner = activeProjectKey();
928
+ if (_sourceWaitOwner !== owner) {
929
+ _sourceWaitOwner = owner;
930
+ _sourceWait = null;
931
+ }
932
+ if (!_sourceWait) {
933
+ _waitNarration =
934
+ `Loading the ingest lane — this project declares the unmodified-game root ` +
935
+ `“${rootId}”, whose adapter declaration a package supplies.`;
936
+ for (const fn of _listeners) fn();
937
+ _sourceWait = refreshProjectToolContributions()
938
+ .catch(() => {})
939
+ .finally(() => {
940
+ _waitNarration = null;
941
+ for (const fn of _listeners) fn();
942
+ });
943
+ }
944
+ return _sourceWait;
945
+ }
946
+
947
+ /**
948
+ * Load the open project's adapter and publish the resolved table.
949
+ *
950
+ * Never throws, for the reason {@link resolveAdapterDefinition} records.
951
+ */
952
+ /** The project's contributed finders, registered into this host's registry
953
+ * before the table resolves (ARCHITECTURE-CORE §The project model). A
954
+ * module that is not a finder is reported by path, never silently skipped. */
955
+ let unregisterContributedFinders: Array<() => void> = [];
956
+ let registeredFinderSignature = '';
957
+ function registerProjectFinders(): void {
958
+ const modules = contributedFinderModules();
959
+ const signature = modules.map((m) => m.entryPath).join('|');
960
+ if (
961
+ signature === registeredFinderSignature &&
962
+ modules.every((m) => registeredModules.has(m.module as object))
963
+ )
964
+ return;
965
+ for (const off of unregisterContributedFinders) off();
966
+ unregisterContributedFinders = [];
967
+ registeredModules = new WeakSet();
968
+ for (const { entryPath, module } of modules) {
969
+ try {
970
+ unregisterContributedFinders.push(
971
+ registerContributedFinder((module as { finder?: unknown } | null)?.finder, entryPath),
972
+ );
973
+ registeredModules.add(module as object);
974
+ } catch (error) {
975
+ editorConsole.error(error instanceof Error ? error.message : String(error), 'project');
976
+ }
977
+ }
978
+ registeredFinderSignature = signature;
979
+ }
980
+ let registeredModules = new WeakSet<object>();
981
+
982
+ // THE KIT'S OWN FINDER, registered once. `prefabsFromStories` is portable
983
+ // CSF's — the kit's design-time state (ARCHITECTURE-CORE §The target shape) —
984
+ // and it reads the story registry itself rather than taking a `FinderInput`
985
+ // field, which is what keeps the finder contract free of one lane's data.
986
+ // This file is the ONE sanctioned importer of the engine's finder namespace
987
+ // (`@editor/finders`'s header states the rule), so the registration is
988
+ // here rather than beside the rest of the lane in `stories/story-lane.ts`.
989
+ registerContributedFinder(storyPrefabsFinder, 'packages/sdk/src/kit/stories/prefabs-finder.ts');
990
+
991
+ // A contribution pass that changed the finder set re-resolves the table.
992
+ subscribeToolContributions(() => {
993
+ const modules = contributedFinderModules();
994
+ const changed =
995
+ modules.map((m) => m.entryPath).join('|') !== registeredFinderSignature ||
996
+ !modules.every((m) => registeredModules.has(m.module as object));
997
+ if (changed && getCurrentProject()) void refreshProjectAdapter();
998
+ });
999
+
1000
+ registerDocumentKindsSupplier(() =>
1001
+ _facet && !_facet.documentsPending
1002
+ ? [...new Set(_facet.scenes.entries.map((entry) => entry.kind))]
1003
+ : null,
1004
+ );
1005
+
1006
+ export function refreshProjectAdapter(): Promise<void> {
1007
+ const run = loadAndPublishProjectAdapter(++_refreshEpoch, getCurrentProject());
1008
+ _inFlight = run;
1009
+ void run.then(() => {
1010
+ if (_inFlight === run) _inFlight = null;
1011
+ });
1012
+ return run;
1013
+ }
1014
+
1015
+ async function loadAndPublishProjectAdapter(
1016
+ epoch: number,
1017
+ project: ActiveProject | null,
1018
+ ): Promise<void> {
1019
+ // Declaration, story, and contribution refreshes can overlap. Only the
1020
+ // latest pass for the still-active project may replace its document table.
1021
+ const current = (): boolean => epoch === _refreshEpoch && getCurrentProject() === project;
1022
+ if (!project) {
1023
+ publish(null);
1024
+ return;
1025
+ }
1026
+
1027
+ let manifest: ResolvedGameManifest;
1028
+ try {
1029
+ manifest = await fetchGameManifest();
1030
+ } catch (error) {
1031
+ if (!current()) return;
1032
+ publish({
1033
+ source: 'native',
1034
+ modulePath: null,
1035
+ regions: [],
1036
+ scenes: { entries: [], default: null },
1037
+ observation: [],
1038
+ notes: [],
1039
+ documentsPending: false,
1040
+ error: `adapter: the project's manifest did not load, so no region could be derived — ${describe(error)}`,
1041
+ });
1042
+ return;
1043
+ }
1044
+
1045
+ if (!current()) return;
1046
+ const choice = await resolveAdapterDefinition(project, manifest);
1047
+ if (!current()) return;
1048
+ registerProjectFinders();
1049
+
1050
+ // Gathering finder input reads the project's own entrypoint sources and
1051
+ // component index; a table with no finder SELECTIONS has nothing to feed, and
1052
+ // an ingest game's sources are not the host's to read at all. Skipping is the
1053
+ // honest read of the declaration, not an optimization dressed as one.
1054
+ const regionsPreview =
1055
+ choice.definition.regions === 'manifest-roots'
1056
+ ? regionsFromManifestRoots(declaredRoots(manifest), choice.definition.regionIncludes)
1057
+ : [...choice.definition.regions];
1058
+ const input =
1059
+ (choice.definition.documents.find ?? []).length > 0
1060
+ ? await gatherFinderInput(manifest, regionsPreview, choice.definition)
1061
+ : NO_FINDER_INPUT;
1062
+ if (!current()) return;
1063
+ const facet = projectAdapterTableFrom(manifest, choice, input);
1064
+ // `native` is the DECLARED DEFAULT standing in for a project that shipped no
1065
+ // declaration file, so it is not a declaration this project made — see
1066
+ // `projectAdapterDefinition`.
1067
+ publish(
1068
+ facet,
1069
+ choice.definition.observation,
1070
+ choice.definition.input,
1071
+ choice.source === 'native' ? null : choice.definition,
1072
+ );
1073
+ }
1074
+
1075
+ function describe(error: unknown): string {
1076
+ return error instanceof Error ? error.message : String(error);
1077
+ }
1078
+
1079
+ /** Does a project-relative path fall under any declared finder's `include`? */
1080
+ function finderIncludesMatch(path: string): boolean {
1081
+ // No parsed declaration in hand (the native default, or a load in flight):
1082
+ // the globs are unknown, so a src change is treated as relevant rather than
1083
+ // dropped — a stale table costs more than a spare resolve.
1084
+ if (!_definition) return true;
1085
+ const selections = (_definition.documents.find ?? []) as ReadonlyArray<{
1086
+ include?: readonly string[];
1087
+ }>;
1088
+ for (const selection of selections) {
1089
+ for (const glob of selection.include ?? []) {
1090
+ if (globToRegExp(glob).test(path)) return true;
1091
+ }
1092
+ }
1093
+ return false;
1094
+ }
1095
+
1096
+ /** `src/models/**\/*.ts` → a RegExp over a project-relative path. `**` spans
1097
+ * directories, `*` stays inside one; everything else is literal. */
1098
+ function globToRegExp(glob: string): RegExp {
1099
+ const DEEP = '__DEEP__';
1100
+ const escaped = glob
1101
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
1102
+ .replace(/\*\*\//g, DEEP)
1103
+ .replace(/\*/g, '[^/]*')
1104
+ .split(DEEP)
1105
+ .join('(?:.*/)?');
1106
+ return new RegExp(`^${escaped}$`);
1107
+ }
1108
+
1109
+ /**
1110
+ * Load the adapter once at editor init and again on every project switch —
1111
+ * the same lifecycle shape `project-story-discovery.ts` uses, for the same
1112
+ * reason: the adapter is a PROJECT capability, not a side effect of mounting
1113
+ * a particular root.
1114
+ */
1115
+ export function startProjectAdapterLoad(): () => void {
1116
+ void refreshProjectAdapter();
1117
+ const changed = (path: string) => {
1118
+ if (
1119
+ projectModuleChangeMatches(path, ADAPTER_MODULE_FILENAME) ||
1120
+ /(?:^|\/)src\/.+\.[jt]sx?$/.test(path)
1121
+ ) {
1122
+ void refreshProjectAdapter();
1123
+ }
1124
+ };
1125
+ const stopModules = subscribeProjectModuleChange(changed);
1126
+ const stopProject = onProjectChange(() => {
1127
+ if (_owner !== activeProjectKey()) publish(null);
1128
+ void refreshProjectAdapter();
1129
+ });
1130
+ // A finder's SOURCE changed on disk — a model module written, a page added.
1131
+ // Nothing imports those files, so this is the only signal the table gets.
1132
+ const stopSources = connectSourceFileEvents((path) => {
1133
+ if (path === '' || finderIncludesMatch(path)) void refreshProjectAdapter();
1134
+ });
1135
+ return () => {
1136
+ stopModules();
1137
+ stopProject();
1138
+ stopSources();
1139
+ };
1140
+ }