@volter/editor-core 0.5.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (719) hide show
  1. package/BUNDLED_NOTICES +1527 -0
  2. package/LICENSE +686 -0
  3. package/LICENSE-APACHE +202 -0
  4. package/NOTICE +18 -0
  5. package/README.md +24 -0
  6. package/dist/build/chunk-JS4D4XZ3.js +175 -0
  7. package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
  8. package/dist/build/vite-plugin-product-contributions.js +100 -0
  9. package/dist/build/vite-plugin-product-contributions.js.map +7 -0
  10. package/dist/build/vite-plugin-shared-react.js +25 -0
  11. package/dist/build/vite-plugin-shared-react.js.map +7 -0
  12. package/dist/build/vite-plugin-shared-three.js +86 -0
  13. package/dist/build/vite-plugin-shared-three.js.map +7 -0
  14. package/dist/server/chunk-KMJC7AGO.js +98 -0
  15. package/dist/server/chunk-KMJC7AGO.js.map +7 -0
  16. package/dist/server/chunk-PSILUMGS.js +46 -0
  17. package/dist/server/chunk-PSILUMGS.js.map +7 -0
  18. package/dist/server/frame-proxy.js +255 -0
  19. package/dist/server/frame-proxy.js.map +7 -0
  20. package/dist/server/open-browser.js +53 -0
  21. package/dist/server/open-browser.js.map +7 -0
  22. package/dist/server/process-shutdown.js +127 -0
  23. package/dist/server/process-shutdown.js.map +7 -0
  24. package/dist/server/session-registry.js +141 -0
  25. package/dist/server/session-registry.js.map +7 -0
  26. package/dist/server/spawn-opener.js +11 -0
  27. package/dist/server/spawn-opener.js.map +7 -0
  28. package/dist/server/worktree-identity.js +11 -0
  29. package/dist/server/worktree-identity.js.map +7 -0
  30. package/dist-server/packaged.mjs +88816 -0
  31. package/package.json +118 -0
  32. package/scripts/build-plugins.mjs +14 -0
  33. package/scripts/build-server.mjs +26 -0
  34. package/scripts/build-session.mjs +9 -0
  35. package/server/account-credentials.ts +60 -0
  36. package/server/account-service.ts +1434 -0
  37. package/server/adapter-region-includes.ts +47 -0
  38. package/server/asset-catalog-v2.ts +175 -0
  39. package/server/asset-history-snapshots.ts +196 -0
  40. package/server/asset-ledger-store.ts +195 -0
  41. package/server/asset-library-routes.ts +1595 -0
  42. package/server/blender-wasm-artifact.ts +430 -0
  43. package/server/boot-timings.ts +106 -0
  44. package/server/canonical-path.ts +50 -0
  45. package/server/checkout-workspace-preflight.ts +349 -0
  46. package/server/cloud-asset-catalog.ts +158 -0
  47. package/server/coding-inference-launch.ts +237 -0
  48. package/server/collaboration-account-client.ts +202 -0
  49. package/server/collaboration-attribution.ts +122 -0
  50. package/server/collaboration-session.ts +934 -0
  51. package/server/comfyui-bridge.ts +248 -0
  52. package/server/console-ledger.ts +499 -0
  53. package/server/creation-site-transform.ts +304 -0
  54. package/server/creation-site-write.ts +648 -0
  55. package/server/data-file-serialize.ts +158 -0
  56. package/server/editor-brand-html.ts +72 -0
  57. package/server/editor-control-socket.ts +366 -0
  58. package/server/editor-server-options.ts +190 -0
  59. package/server/editor-server.ts +1483 -0
  60. package/server/editor-sse.ts +352 -0
  61. package/server/engine-provenance.ts +169 -0
  62. package/server/engine-source-restart.ts +117 -0
  63. package/server/frame-bridge.ts +115 -0
  64. package/server/frame-proxy.ts +490 -0
  65. package/server/frame-workbench.ts +259 -0
  66. package/server/frontend-handoff.ts +229 -0
  67. package/server/game-globals-shadow.ts +113 -0
  68. package/server/gameplay-session-retention.ts +149 -0
  69. package/server/gameplay-sessions.ts +123 -0
  70. package/server/generation-jobs.ts +201 -0
  71. package/server/generation-reconciler.ts +137 -0
  72. package/server/generative-execution-context.ts +59 -0
  73. package/server/git-workflow.ts +612 -0
  74. package/server/harness-chat-caller.ts +36 -0
  75. package/server/harness-chat-service.ts +1681 -0
  76. package/server/idle-shutdown.ts +170 -0
  77. package/server/js-profiling-policy.ts +52 -0
  78. package/server/launcher-settings.ts +84 -0
  79. package/server/local-asset-catalog.ts +410 -0
  80. package/server/managed-account-defaults.ts +22 -0
  81. package/server/mock-control-plane.ts +540 -0
  82. package/server/model-import-conversion.ts +345 -0
  83. package/server/native-credential-store.ts +162 -0
  84. package/server/open-browser.ts +118 -0
  85. package/server/packaged.ts +1192 -0
  86. package/server/play-stall.ts +126 -0
  87. package/server/process-shutdown.ts +218 -0
  88. package/server/product-presets.ts +55 -0
  89. package/server/project-build-artifact.ts +95 -0
  90. package/server/project-components.ts +100 -0
  91. package/server/project-dependency-invalidation.ts +114 -0
  92. package/server/project-file-scan.ts +235 -0
  93. package/server/project-hmr-files.ts +215 -0
  94. package/server/project-install-roots.ts +210 -0
  95. package/server/project-kinds.ts +71 -0
  96. package/server/project-mcp-servers.ts +82 -0
  97. package/server/project-module-freshness.ts +109 -0
  98. package/server/project-module-instance.ts +375 -0
  99. package/server/project-optimize-deps-entries.ts +827 -0
  100. package/server/project-output-writer.ts +679 -0
  101. package/server/project-package-origin.ts +63 -0
  102. package/server/project-root-surface.ts +831 -0
  103. package/server/project-scratch-path.ts +47 -0
  104. package/server/project-script-hmr.ts +619 -0
  105. package/server/project-serving-plugins.ts +236 -0
  106. package/server/project-tools.ts +653 -0
  107. package/server/project-validation.ts +250 -0
  108. package/server/project-verbs.ts +127 -0
  109. package/server/project-view.ts +52 -0
  110. package/server/project-watch.ts +1134 -0
  111. package/server/project-work-coordinator.ts +288 -0
  112. package/server/provider-credentials.ts +331 -0
  113. package/server/recent-projects-store.ts +98 -0
  114. package/server/redact-secrets.ts +71 -0
  115. package/server/repository-presence.ts +267 -0
  116. package/server/routes/account.ts +357 -0
  117. package/server/routes/agents.ts +60 -0
  118. package/server/routes/assets.ts +154 -0
  119. package/server/routes/build.ts +13 -0
  120. package/server/routes/collaboration.ts +595 -0
  121. package/server/routes/configurations.ts +419 -0
  122. package/server/routes/context.ts +264 -0
  123. package/server/routes/control-plane.ts +1811 -0
  124. package/server/routes/logs.ts +272 -0
  125. package/server/routes/project-identity.ts +302 -0
  126. package/server/routes/project-open.ts +429 -0
  127. package/server/routes/project-source.ts +686 -0
  128. package/server/routes/project-state.ts +383 -0
  129. package/server/routes/relay.ts +1029 -0
  130. package/server/routes/route-helpers.ts +29 -0
  131. package/server/routes/served-modules.ts +138 -0
  132. package/server/routes/session-tabs.ts +196 -0
  133. package/server/routes/settings.ts +121 -0
  134. package/server/routes/share-control.ts +486 -0
  135. package/server/routes/themes.ts +116 -0
  136. package/server/routes/tools.ts +230 -0
  137. package/server/routes/worktrees.ts +595 -0
  138. package/server/scoped-game-css.ts +461 -0
  139. package/server/server-utils.ts +1194 -0
  140. package/server/session-product.ts +112 -0
  141. package/server/session-registry.ts +230 -0
  142. package/server/share-claims.ts +138 -0
  143. package/server/share-host.ts +526 -0
  144. package/server/share-session-gateway.ts +1362 -0
  145. package/server/share-tunnel.ts +123 -0
  146. package/server/spawn-opener.ts +74 -0
  147. package/server/support/play/log-format.ts +76 -0
  148. package/server/support/play/session-record.ts +199 -0
  149. package/server/support/project/build-discipline.ts +718 -0
  150. package/server/support/project/inspection-node.ts +119 -0
  151. package/server/support/project/log-naming.ts +54 -0
  152. package/server/support/project/provenance.ts +290 -0
  153. package/server/support/project/run-name.ts +18 -0
  154. package/server/support/project/session-journal.ts +830 -0
  155. package/server/support/project/shared.ts +327 -0
  156. package/server/tab-bootstrap.ts +62 -0
  157. package/server/tab-heartbeat.ts +477 -0
  158. package/server/tab-lifecycle.ts +550 -0
  159. package/server/tab-presence.ts +1508 -0
  160. package/server/team-agent-mirror.ts +95 -0
  161. package/server/twin-auth.ts +138 -0
  162. package/server/vendored-lock-recorder.ts +968 -0
  163. package/server/worktree-identity.ts +121 -0
  164. package/server/worktree-management.ts +398 -0
  165. package/server/worktree-retention.d.mts +13 -0
  166. package/server/worktree-retention.mjs +205 -0
  167. package/src/EditorContext.tsx +169 -0
  168. package/src/account.ts +333 -0
  169. package/src/action-registry.ts +532 -0
  170. package/src/active-product.ts +74 -0
  171. package/src/active-project.ts +155 -0
  172. package/src/adapter-editor-config.ts +25 -0
  173. package/src/adapter-observation.ts +49 -0
  174. package/src/animation/stage-transport.ts +379 -0
  175. package/src/animation/three-clips-subject.ts +172 -0
  176. package/src/api/asset-library-wire.ts +45 -0
  177. package/src/api/assets.ts +365 -0
  178. package/src/api/base.ts +10 -0
  179. package/src/api/build.ts +99 -0
  180. package/src/api/git-wire.ts +56 -0
  181. package/src/api/logs.ts +92 -0
  182. package/src/api/project-identity.ts +74 -0
  183. package/src/api/project-open.ts +355 -0
  184. package/src/api/project-source.ts +162 -0
  185. package/src/api/project-state.ts +107 -0
  186. package/src/api/relay.ts +268 -0
  187. package/src/api/settings.ts +36 -0
  188. package/src/api/themes.ts +45 -0
  189. package/src/api/worktrees.ts +257 -0
  190. package/src/asset-compare-core.ts +171 -0
  191. package/src/asset-compare.ts +294 -0
  192. package/src/asset-editor-context.tsx +100 -0
  193. package/src/asset-events.ts +97 -0
  194. package/src/asset-inspector-actions.ts +87 -0
  195. package/src/asset-preview-framing.ts +357 -0
  196. package/src/asset-preview.ts +2802 -0
  197. package/src/asset-selection-viewer-registry.ts +113 -0
  198. package/src/asset-selection.ts +133 -0
  199. package/src/asset-workflow/asset-capabilities.ts +344 -0
  200. package/src/asset-workflow/asset-import-jobs.ts +106 -0
  201. package/src/asset-workflow/asset-ledger-backend.ts +126 -0
  202. package/src/asset-workflow/asset-ledger.ts +156 -0
  203. package/src/asset-workflow/asset-materialization-report.ts +140 -0
  204. package/src/asset-workflow/asset-pack-manifest.ts +320 -0
  205. package/src/asset-workflow/asset-types.ts +142 -0
  206. package/src/asset-workflow/asset-workflow-quality.ts +61 -0
  207. package/src/asset-workflow/audio-preview-player.ts +193 -0
  208. package/src/asset-workflow/audio-waveform.ts +22 -0
  209. package/src/asset-workflow/cloud-asset-client.ts +263 -0
  210. package/src/asset-workflow/folder-preview.ts +345 -0
  211. package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
  212. package/src/asset-workflow/image-view-scale.ts +32 -0
  213. package/src/asset-workflow/import-contract.ts +124 -0
  214. package/src/asset-workflow/ledger-write-lock.ts +244 -0
  215. package/src/asset-workflow/model-inspection.ts +818 -0
  216. package/src/asset-workflow/pixi-spritesheet.ts +197 -0
  217. package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
  218. package/src/asset-workflow/project-asset-commands.ts +23 -0
  219. package/src/asset-workflow/project-asset-health.ts +139 -0
  220. package/src/asset-workflow/project-asset-operations.ts +154 -0
  221. package/src/asset-workflow/project-asset-roots.ts +68 -0
  222. package/src/asset-workflow/project-content.ts +288 -0
  223. package/src/asset-workflow/project-source-index.ts +550 -0
  224. package/src/asset-workflow/thumbnail-system.ts +256 -0
  225. package/src/authoring/active-adapter.ts +199 -0
  226. package/src/authoring/active-systems.ts +422 -0
  227. package/src/authoring/adapter-key.ts +18 -0
  228. package/src/authoring/authoring-asset-url.ts +27 -0
  229. package/src/authoring/bootstrap-state.ts +49 -0
  230. package/src/authoring/boundary-authoring-adapter.ts +184 -0
  231. package/src/authoring/breakpoint-state.ts +43 -0
  232. package/src/authoring/canvas-scene-guides.ts +76 -0
  233. package/src/authoring/component-instance-root.ts +171 -0
  234. package/src/authoring/composite-authoring-adapter.ts +2110 -0
  235. package/src/authoring/consumer-actions.ts +520 -0
  236. package/src/authoring/css-numeric-style.ts +97 -0
  237. package/src/authoring/design-time-layers.ts +849 -0
  238. package/src/authoring/design-time-mount-registry.ts +235 -0
  239. package/src/authoring/design-time-settle.ts +343 -0
  240. package/src/authoring/edit-mode-authoring.ts +619 -0
  241. package/src/authoring/eyedropper-session.ts +60 -0
  242. package/src/authoring/instance-source-menu-register.ts +13 -0
  243. package/src/authoring/instance-source-menu.ts +135 -0
  244. package/src/authoring/layered-pick.ts +183 -0
  245. package/src/authoring/live-gesture-lock.ts +50 -0
  246. package/src/authoring/live-object-transform.ts +62 -0
  247. package/src/authoring/mount-failure-report.ts +154 -0
  248. package/src/authoring/mounted-root-subjects.ts +144 -0
  249. package/src/authoring/no-authoring-adapter.ts +55 -0
  250. package/src/authoring/null-inspection-subjects.tsx +75 -0
  251. package/src/authoring/object3d-document-persistence.ts +106 -0
  252. package/src/authoring/object3d-document-session-registry.ts +120 -0
  253. package/src/authoring/object3d-document-session.ts +1326 -0
  254. package/src/authoring/object3d-gesture-controller.ts +113 -0
  255. package/src/authoring/panel-authoring.ts +121 -0
  256. package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
  257. package/src/authoring/prefab-instance-section-model.ts +55 -0
  258. package/src/authoring/project-authoring-session.ts +105 -0
  259. package/src/authoring/provenance.ts +99 -0
  260. package/src/authoring/quarks-particle-systems.ts +19 -0
  261. package/src/authoring/react-canvas-navigation.ts +255 -0
  262. package/src/authoring/react-design-canvas-style.ts +20 -0
  263. package/src/authoring/react-story-board.ts +937 -0
  264. package/src/authoring/selection-scope.ts +195 -0
  265. package/src/authoring/shell-document-ops.ts +169 -0
  266. package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
  267. package/src/authoring/shell-viewport-policy.ts +48 -0
  268. package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
  269. package/src/authoring/stories-scope.ts +35 -0
  270. package/src/authoring/story-board-chrome-fit.ts +107 -0
  271. package/src/authoring/story-board-presentation.ts +111 -0
  272. package/src/authoring/three-projection-core.ts +226 -0
  273. package/src/authoring/viewport-pick-context.ts +40 -0
  274. package/src/authoring/viewport-raycast.ts +240 -0
  275. package/src/authoring/viewport-tool-context.ts +73 -0
  276. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  277. package/src/authoring/world-hidden-viewport.ts +152 -0
  278. package/src/authoring/world-pan-state.ts +198 -0
  279. package/src/authoring/world-session-state.ts +101 -0
  280. package/src/authoring/write-pipe.ts +173 -0
  281. package/src/availability-tick.ts +66 -0
  282. package/src/bitmap-label.ts +120 -0
  283. package/src/blender-tab-metrics.ts +161 -0
  284. package/src/board-open-actions.ts +20 -0
  285. package/src/boot-routing.ts +391 -0
  286. package/src/build-identity.ts +15 -0
  287. package/src/bytes-codec.ts +62 -0
  288. package/src/camera-authoring.ts +175 -0
  289. package/src/cancellation-reason.ts +58 -0
  290. package/src/canvas-preview-frames.ts +482 -0
  291. package/src/capture-camera-pose.ts +77 -0
  292. package/src/chrome-registry.ts +159 -0
  293. package/src/chrome-slot-registry.ts +91 -0
  294. package/src/collaboration-client.ts +264 -0
  295. package/src/collaboration-presence.ts +41 -0
  296. package/src/command-dispatch.ts +19 -0
  297. package/src/command-listener.ts +2743 -0
  298. package/src/command-registry.ts +70 -0
  299. package/src/component-board-registry.ts +205 -0
  300. package/src/component-states-registry.ts +200 -0
  301. package/src/components/AgentPresentationNotice.tsx +75 -0
  302. package/src/components/AlignToolbar.tsx +204 -0
  303. package/src/components/AppRoot.tsx +394 -0
  304. package/src/components/ApplicationMenus.tsx +415 -0
  305. package/src/components/AssetBrowser.tsx +2843 -0
  306. package/src/components/AssetEditorShell.tsx +216 -0
  307. package/src/components/AssetInspectorToolSection.tsx +124 -0
  308. package/src/components/BoardRulers.tsx +354 -0
  309. package/src/components/CameraInfo.tsx +81 -0
  310. package/src/components/CanvasSceneViewport.tsx +556 -0
  311. package/src/components/CapabilityCoverageSection.tsx +185 -0
  312. package/src/components/CenterDocuments.tsx +22 -0
  313. package/src/components/ChromeSlot.tsx +20 -0
  314. package/src/components/CodeView.tsx +470 -0
  315. package/src/components/CollaborationHeaderControl.css +74 -0
  316. package/src/components/CompactInspectorCard.tsx +190 -0
  317. package/src/components/CompactInspectorShell.tsx +39 -0
  318. package/src/components/ConsolePanel.css +55 -0
  319. package/src/components/ConsolePanel.tsx +270 -0
  320. package/src/components/DefaultEditorLayout.tsx +106 -0
  321. package/src/components/DocumentHeaderStrip.tsx +96 -0
  322. package/src/components/DocumentShelfRail.tsx +45 -0
  323. package/src/components/DocumentThumbnail.tsx +345 -0
  324. package/src/components/EditorLeaseGuard.tsx +372 -0
  325. package/src/components/ErrorBanner.tsx +123 -0
  326. package/src/components/FolderPreviewTile.tsx +159 -0
  327. package/src/components/GameHierarchy.tsx +3842 -0
  328. package/src/components/GameplaySessionTimeline.tsx +295 -0
  329. package/src/components/InspectionProjection.tsx +968 -0
  330. package/src/components/Inspector.tsx +233 -0
  331. package/src/components/InspectorCanvasPreview.tsx +35 -0
  332. package/src/components/InspectorFieldsSection.tsx +290 -0
  333. package/src/components/InspectorObjectPreview.tsx +57 -0
  334. package/src/components/InspectorStoriesSection.tsx +92 -0
  335. package/src/components/InspectorToolSection.tsx +96 -0
  336. package/src/components/InspectorTransformSection.tsx +245 -0
  337. package/src/components/LightExplorerPanel.tsx +433 -0
  338. package/src/components/MediaProperties.tsx +145 -0
  339. package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
  340. package/src/components/Object3DDocumentToolbar.css +100 -0
  341. package/src/components/Object3DDocumentToolbar.tsx +454 -0
  342. package/src/components/Object3DDocumentViewport.tsx +58 -0
  343. package/src/components/ProjectHeader.css +617 -0
  344. package/src/components/ProjectHeader.tsx +328 -0
  345. package/src/components/ProjectLayout.tsx +249 -0
  346. package/src/components/ReactCanvasControls.tsx +284 -0
  347. package/src/components/RootSelectionOverlay.tsx +3162 -0
  348. package/src/components/RootTextEditor.tsx +79 -0
  349. package/src/components/SaveStatus.tsx +70 -0
  350. package/src/components/StageHost.tsx +1954 -0
  351. package/src/components/StageOverlays.tsx +21 -0
  352. package/src/components/StartupErrorScreen.tsx +76 -0
  353. package/src/components/StartupLoadingScreen.tsx +65 -0
  354. package/src/components/StatsOverlay.tsx +78 -0
  355. package/src/components/SurfaceStateOverlay.tsx +24 -0
  356. package/src/components/ToolContributionSurfaces.tsx +65 -0
  357. package/src/components/ToolHost.tsx +370 -0
  358. package/src/components/ToolObject3DPreview.tsx +39 -0
  359. package/src/components/Toolbar.tsx +595 -0
  360. package/src/components/TransientHint.tsx +44 -0
  361. package/src/components/TransportStrip.tsx +174 -0
  362. package/src/components/VersionControlSection.tsx +470 -0
  363. package/src/components/VgaiLogo.css +83 -0
  364. package/src/components/VgaiLogo.tsx +35 -0
  365. package/src/components/ViewportControlsHint.tsx +60 -0
  366. package/src/components/ViewportFurniture.tsx +401 -0
  367. package/src/components/ViewportOverlay.tsx +145 -0
  368. package/src/components/ViewportOverlaysMenu.tsx +153 -0
  369. package/src/components/ViewportShadingMenu.tsx +300 -0
  370. package/src/components/ViewportViewMenu.tsx +101 -0
  371. package/src/components/WorkspaceDocumentSurface.tsx +204 -0
  372. package/src/components/WorkspaceUtilitySurface.tsx +22 -0
  373. package/src/components/WorktreeSwitcher.css +239 -0
  374. package/src/components/WorktreeSwitcher.tsx +890 -0
  375. package/src/components/account-documents.tsx +1162 -0
  376. package/src/components/asset-documents.tsx +812 -0
  377. package/src/components/asset-editor-persistence.ts +216 -0
  378. package/src/components/asset-selection-section.tsx +535 -0
  379. package/src/components/asset-thumbnails.tsx +312 -0
  380. package/src/components/asset-viewers/AudioViewer.tsx +201 -0
  381. package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
  382. package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  383. package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  384. package/src/components/asset-viewers/ImageViewer.tsx +300 -0
  385. package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
  386. package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
  387. package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
  388. package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  389. package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
  390. package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
  391. package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
  392. package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
  393. package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  394. package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
  395. package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  396. package/src/components/asset-viewers/VideoViewer.tsx +101 -0
  397. package/src/components/asset-viewers/shader-source.ts +144 -0
  398. package/src/components/asset-workflow.css +731 -0
  399. package/src/components/board-guides.ts +150 -0
  400. package/src/components/compact-inspector.css +539 -0
  401. package/src/components/core-utilities.tsx +90 -0
  402. package/src/components/editor-notifications.css +40 -0
  403. package/src/components/engine-workspace.css +274 -0
  404. package/src/components/inspector-preview-section.tsx +238 -0
  405. package/src/components/inspector-property-grouping.ts +64 -0
  406. package/src/components/inspector-revert-label.ts +20 -0
  407. package/src/components/inspector-selection.ts +42 -0
  408. package/src/components/inspector-stories-gating.ts +171 -0
  409. package/src/components/inspector-transform-subject.ts +11 -0
  410. package/src/components/inspector-transform.ts +75 -0
  411. package/src/components/kind-documents.tsx +500 -0
  412. package/src/components/palette-action-publisher.tsx +140 -0
  413. package/src/components/primitives/DraftColorInput.tsx +74 -0
  414. package/src/components/product-shell.css +1829 -0
  415. package/src/components/project-tool-documents.tsx +410 -0
  416. package/src/components/scene-documents.tsx +223 -0
  417. package/src/components/stage-keyboard.tsx +37 -0
  418. package/src/components/stage-overlay-set.tsx +107 -0
  419. package/src/components/stage-presence-markers.ts +478 -0
  420. package/src/components/standard-viewport-dressing.ts +417 -0
  421. package/src/components/status-contributions.tsx +403 -0
  422. package/src/components/tool-documents.tsx +309 -0
  423. package/src/components/tool-schema-form.tsx +262 -0
  424. package/src/components/use-after-paint.ts +41 -0
  425. package/src/components/use-project-image-assets.ts +86 -0
  426. package/src/components/viewport-header-controls.css +90 -0
  427. package/src/components/viewport-surface-status.tsx +55 -0
  428. package/src/components/workspace-history.ts +32 -0
  429. package/src/components/workspace-static-panel-registry.tsx +152 -0
  430. package/src/components/workspace-surfaces.css +621 -0
  431. package/src/components/world-documents.tsx +586 -0
  432. package/src/components/world-overlay-gestures.ts +1694 -0
  433. package/src/composite-screenshot.ts +1641 -0
  434. package/src/console-sync.ts +131 -0
  435. package/src/constraint-helper.ts +338 -0
  436. package/src/content-entry-source-registry.ts +184 -0
  437. package/src/coverage/authoring-seam-evidence.ts +300 -0
  438. package/src/coverage/canvas-reveal.ts +192 -0
  439. package/src/coverage/design-time-surfaces.ts +101 -0
  440. package/src/coverage/live-seam-evidence.ts +11 -0
  441. package/src/coverage/ontology-invariants.ts +466 -0
  442. package/src/coverage/session-vitals.ts +501 -0
  443. package/src/coverage/system-seam-evidence.ts +72 -0
  444. package/src/crash-null-boundary.ts +36 -0
  445. package/src/creation-site-edit.ts +1479 -0
  446. package/src/creation-site-registry.ts +160 -0
  447. package/src/delegate-harness-registry.ts +143 -0
  448. package/src/document-context-registry.ts +177 -0
  449. package/src/document-open-registry.ts +200 -0
  450. package/src/document-preview-source.ts +20 -0
  451. package/src/document-renderer-session.ts +138 -0
  452. package/src/editor-api.ts +46 -0
  453. package/src/editor-chrome-capture.ts +139 -0
  454. package/src/editor-commands.ts +164 -0
  455. package/src/editor-console.ts +563 -0
  456. package/src/editor-current-view.ts +82 -0
  457. package/src/editor-document-probe.ts +881 -0
  458. package/src/editor-git-client.ts +115 -0
  459. package/src/editor-host-door.ts +462 -0
  460. package/src/editor-hotkeys.ts +842 -0
  461. package/src/editor-lease-view.ts +39 -0
  462. package/src/editor-lease.ts +415 -0
  463. package/src/editor-mode.ts +19 -0
  464. package/src/editor-notifications.ts +140 -0
  465. package/src/editor-presence.ts +563 -0
  466. package/src/editor-presentation-activity.ts +50 -0
  467. package/src/editor-presentation-notice.ts +42 -0
  468. package/src/editor-runtime.tsx +145 -0
  469. package/src/editor-server-response.ts +86 -0
  470. package/src/editor-session-attribution.ts +75 -0
  471. package/src/editor-session-mode.ts +54 -0
  472. package/src/editor-shell-store.ts +1304 -0
  473. package/src/editor-state-facets.ts +74 -0
  474. package/src/editor-styles.css +33 -0
  475. package/src/editor-view-presentation.ts +810 -0
  476. package/src/editor-viewport.ts +5302 -0
  477. package/src/entity-lod.ts +31 -0
  478. package/src/entity-object.ts +91 -0
  479. package/src/files/file-provider.ts +62 -0
  480. package/src/files/project-files.ts +264 -0
  481. package/src/finders/index.ts +136 -0
  482. package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
  483. package/src/frame/bridge.tsx +1408 -0
  484. package/src/frame/product.ts +77 -0
  485. package/src/gameplay-dom-recording.ts +319 -0
  486. package/src/gameplay-export-state.ts +14 -0
  487. package/src/gameplay-replay.ts +417 -0
  488. package/src/gameplay-session-time.ts +9 -0
  489. package/src/gameplay-sessions.ts +204 -0
  490. package/src/harness-chat-types.ts +253 -0
  491. package/src/hierarchy-component-marks.ts +298 -0
  492. package/src/hierarchy-drop.ts +91 -0
  493. package/src/hierarchy-expansion-state.ts +80 -0
  494. package/src/hierarchy-header-slot.ts +52 -0
  495. package/src/hierarchy-internals.ts +197 -0
  496. package/src/hierarchy-kind-icon.ts +217 -0
  497. package/src/hierarchy-mark-reader.ts +73 -0
  498. package/src/hierarchy-menu-registry.ts +67 -0
  499. package/src/hierarchy-node-rows.ts +307 -0
  500. package/src/hierarchy-panel-view.ts +280 -0
  501. package/src/hierarchy-projection.ts +76 -0
  502. package/src/hierarchy-row-cache.ts +243 -0
  503. package/src/hierarchy-row-model.ts +308 -0
  504. package/src/hierarchy-rows.ts +11 -0
  505. package/src/hierarchy-walk.ts +86 -0
  506. package/src/history/editor-session.ts +25 -0
  507. package/src/history/history-commands.ts +147 -0
  508. package/src/history/history-delegate.ts +163 -0
  509. package/src/history/history-limit-notices.ts +43 -0
  510. package/src/history/history-service.ts +1173 -0
  511. package/src/history/persistence-coordinator.ts +35 -0
  512. package/src/history/project-file-history.ts +386 -0
  513. package/src/history/project-root-history-backends.ts +139 -0
  514. package/src/history/resource-registry.ts +209 -0
  515. package/src/history/snapshot-store.ts +103 -0
  516. package/src/history/source-history-backend.ts +546 -0
  517. package/src/history/types.ts +124 -0
  518. package/src/hmr-registration-group.ts +67 -0
  519. package/src/hmr-stable-react-context.ts +23 -0
  520. package/src/hotkeys.ts +188 -0
  521. package/src/inference-diagnostics.ts +69 -0
  522. package/src/initial-project.ts +80 -0
  523. package/src/inspection/active-subject.ts +578 -0
  524. package/src/inspection/active-surface.ts +142 -0
  525. package/src/inspection/compose.ts +1064 -0
  526. package/src/inspection/display.ts +170 -0
  527. package/src/inspection/document-subject.ts +109 -0
  528. package/src/inspection/game-subject.ts +85 -0
  529. package/src/inspection/model.ts +542 -0
  530. package/src/inspection/null-subject.ts +115 -0
  531. package/src/inspection/serialize.ts +357 -0
  532. package/src/inspection/use-active-inspection.ts +180 -0
  533. package/src/inspector-presentation.ts +201 -0
  534. package/src/inspector-section-registry.ts +221 -0
  535. package/src/instance-source-actions.ts +163 -0
  536. package/src/instanced-presentation.ts +164 -0
  537. package/src/js-heap.ts +71 -0
  538. package/src/key-actions.ts +91 -0
  539. package/src/keymap-presets.ts +401 -0
  540. package/src/layout-policy.ts +31 -0
  541. package/src/learn-links.ts +73 -0
  542. package/src/light-explorer-model.ts +134 -0
  543. package/src/live-canvas-frame.ts +55 -0
  544. package/src/live-document.ts +279 -0
  545. package/src/live-module-source.ts +230 -0
  546. package/src/live-session-registry.ts +220 -0
  547. package/src/live-transition.ts +633 -0
  548. package/src/manifest-project.ts +107 -0
  549. package/src/model-thumbnail.ts +523 -0
  550. package/src/native-selection-style.ts +202 -0
  551. package/src/object3d-document-write-policy.ts +137 -0
  552. package/src/packaged-runtime.ts +108 -0
  553. package/src/palettes/maya.palette.json +57 -0
  554. package/src/palettes/substance.palette.json +57 -0
  555. package/src/pasteboard-module.ts +81 -0
  556. package/src/performance-profiler.ts +367 -0
  557. package/src/performance-sources.ts +69 -0
  558. package/src/play-boot-phase.ts +145 -0
  559. package/src/presentation-surface.ts +248 -0
  560. package/src/project-adapter.ts +1143 -0
  561. package/src/project-asset-refresh.ts +26 -0
  562. package/src/project-declaration-refresh.ts +31 -0
  563. package/src/project-local-state.ts +118 -0
  564. package/src/project-manager.ts +243 -0
  565. package/src/project-module-changes.ts +163 -0
  566. package/src/project-module-split.ts +266 -0
  567. package/src/project-provenance.ts +115 -0
  568. package/src/project-ready.ts +42 -0
  569. package/src/project-session-reset.ts +67 -0
  570. package/src/project-shape.ts +68 -0
  571. package/src/project-tool-discovery.ts +66 -0
  572. package/src/project-tools.ts +107 -0
  573. package/src/project-work-types.ts +149 -0
  574. package/src/projection/three.ts +898 -0
  575. package/src/projection/types.ts +44 -0
  576. package/src/readiness.ts +113 -0
  577. package/src/reflection-probe-helper.ts +142 -0
  578. package/src/reported-play-state.ts +90 -0
  579. package/src/resolve-relative-specifier.ts +33 -0
  580. package/src/scene-document-plan.ts +315 -0
  581. package/src/scene-framing.ts +315 -0
  582. package/src/scene-live-open.ts +210 -0
  583. package/src/scene-view-fog.ts +89 -0
  584. package/src/scoped-game-css.ts +152 -0
  585. package/src/session-orphan-record.ts +193 -0
  586. package/src/session-tombstone.ts +126 -0
  587. package/src/settings/settings-provider.ts +82 -0
  588. package/src/settings-store.ts +345 -0
  589. package/src/shared-view-restore.ts +42 -0
  590. package/src/shell-store-door.ts +45 -0
  591. package/src/source-conflict.ts +122 -0
  592. package/src/spatial-handle-visuals.ts +332 -0
  593. package/src/stage-context.ts +341 -0
  594. package/src/stage-store-registry.ts +63 -0
  595. package/src/stale-chunk-recovery.ts +34 -0
  596. package/src/state-report-deferral.ts +73 -0
  597. package/src/storage/host-files-storage.ts +97 -0
  598. package/src/storage/http-storage.ts +174 -0
  599. package/src/storage/index.ts +75 -0
  600. package/src/storage/mem-storage.ts +149 -0
  601. package/src/storage/path-lock.ts +44 -0
  602. package/src/storage/paths.ts +26 -0
  603. package/src/storage/types.ts +116 -0
  604. package/src/stories/StoryComponentThumbnail.tsx +184 -0
  605. package/src/stories/StoryPreviewMount.tsx +306 -0
  606. package/src/stories/component-content-source.tsx +106 -0
  607. package/src/stories/component-states-source.ts +78 -0
  608. package/src/stories/compose-project-stories.ts +255 -0
  609. package/src/stories/pixi-story-model.ts +30 -0
  610. package/src/stories/prefabs-finder.ts +54 -0
  611. package/src/stories/prefabs-from-stories.ts +182 -0
  612. package/src/stories/project-story-discovery.ts +78 -0
  613. package/src/stories/project-story-regions.ts +24 -0
  614. package/src/stories/story-actions.ts +54 -0
  615. package/src/stories/story-capture-command.ts +124 -0
  616. package/src/stories/story-capture.ts +584 -0
  617. package/src/stories/story-declared-medium.ts +126 -0
  618. package/src/stories/story-discovery.ts +176 -0
  619. package/src/stories/story-document-openers.ts +36 -0
  620. package/src/stories/story-dom-runtime.ts +78 -0
  621. package/src/stories/story-grouping.ts +111 -0
  622. package/src/stories/story-lane.ts +88 -0
  623. package/src/stories/story-mount-turn.ts +27 -0
  624. package/src/stories/story-opener.ts +260 -0
  625. package/src/stories/story-pixi-preview.ts +408 -0
  626. package/src/stories/story-presentation.ts +215 -0
  627. package/src/stories/story-registry.ts +517 -0
  628. package/src/stories/story-three-preview.ts +807 -0
  629. package/src/stories/three-story-model.ts +96 -0
  630. package/src/story-three-preview-runtime.ts +56 -0
  631. package/src/surface-keyboard.ts +112 -0
  632. package/src/surface-state.ts +124 -0
  633. package/src/tab-bootstrap.js +628 -0
  634. package/src/tab-census.ts +198 -0
  635. package/src/tab-lifecycle-client.ts +196 -0
  636. package/src/theme-library.ts +844 -0
  637. package/src/theme-preference.ts +378 -0
  638. package/src/theme.css +3437 -0
  639. package/src/three-viewport/bone-selection-highlight.ts +119 -0
  640. package/src/three-viewport/camera-fit.ts +41 -0
  641. package/src/three-viewport/interactive-renderer.ts +132 -0
  642. package/src/three-viewport/selection-brackets.ts +310 -0
  643. package/src/three-viewport/selection-outline.ts +183 -0
  644. package/src/three-viewport/skeleton-helper.ts +61 -0
  645. package/src/three-viewport/source-color.ts +167 -0
  646. package/src/three-viewport/studio-environment.ts +96 -0
  647. package/src/three-viewport-presentation.ts +23 -0
  648. package/src/tool-contribution-play.ts +74 -0
  649. package/src/tool-loader.ts +1798 -0
  650. package/src/transform-mode-request.ts +57 -0
  651. package/src/transient-hint.ts +78 -0
  652. package/src/trigger-volume-helper.ts +116 -0
  653. package/src/ui-source/adapter-region-includes.ts +241 -0
  654. package/src/ui-source/ensure-import.ts +132 -0
  655. package/src/ui-source/file-region-resolver.ts +302 -0
  656. package/src/ui-source/inspect.ts +775 -0
  657. package/src/ui-source/oid-transform.ts +1845 -0
  658. package/src/ui-source/plan-csf-story.ts +196 -0
  659. package/src/ui-source/plan-extract-component.ts +421 -0
  660. package/src/ui-source/plan-fork-component.ts +621 -0
  661. package/src/ui-source/plan-named-style.ts +95 -0
  662. package/src/ui-source/plan-source-edit.ts +353 -0
  663. package/src/ui-source/r3f-contract-resolver.ts +327 -0
  664. package/src/ui-source/r3f-diagnostic-index.ts +165 -0
  665. package/src/ui-source/r3f-environment-binding.ts +133 -0
  666. package/src/ui-source/r3f-joint-binding.ts +251 -0
  667. package/src/ui-source/r3f-lod-binding.ts +91 -0
  668. package/src/ui-source/r3f-particle-binding.ts +235 -0
  669. package/src/ui-source/r3f-physics-binding.ts +483 -0
  670. package/src/ui-source/r3f-project-contracts.ts +444 -0
  671. package/src/ui-source/relative-import-specifier.ts +34 -0
  672. package/src/ui-source/reparent-guard.ts +544 -0
  673. package/src/ui-source/source-edit-request.ts +97 -0
  674. package/src/ui-source/source-write-backend.ts +618 -0
  675. package/src/ui-source/syntactic-prop-specs.ts +296 -0
  676. package/src/ui-source/tier-source-write-backend.ts +279 -0
  677. package/src/ui-source/ts-ast.ts +171 -0
  678. package/src/ui-source/utility-class-support.ts +169 -0
  679. package/src/ui-source/write-component-default.ts +133 -0
  680. package/src/ui-source/writer.ts +1989 -0
  681. package/src/viewport-activation-timings.ts +840 -0
  682. package/src/viewport-authoring-policy.ts +186 -0
  683. package/src/viewport-controls-hint.ts +57 -0
  684. package/src/viewport-door.ts +237 -0
  685. package/src/viewport-shading-boundary.ts +12 -0
  686. package/src/vite-error-surface.ts +141 -0
  687. package/src/wait-until.ts +37 -0
  688. package/src/workspace-areas.ts +156 -0
  689. package/src/workspace-aux-commands.ts +11 -0
  690. package/src/workspace-available-documents.ts +127 -0
  691. package/src/workspace-core-utilities.ts +31 -0
  692. package/src/workspace-document-ids.ts +59 -0
  693. package/src/workspace-document-registry.ts +612 -0
  694. package/src/workspace-document-restore.ts +145 -0
  695. package/src/workspace-host-commands.ts +141 -0
  696. package/src/workspace-persistence-gate.ts +40 -0
  697. package/src/workspace-play-utilities.ts +44 -0
  698. package/src/workspace-presets.ts +425 -0
  699. package/src/workspace-regions.ts +294 -0
  700. package/src/workspace-state-persistence.ts +536 -0
  701. package/src/workspace-static-panels.ts +73 -0
  702. package/src/workspace-status-registry.ts +121 -0
  703. package/src/workspace-style.ts +239 -0
  704. package/src/workspace-utility-commands.ts +74 -0
  705. package/src/workspace-utility-registry.ts +263 -0
  706. package/src/workspace-viewport-rect.ts +97 -0
  707. package/src/world-adoption.ts +115 -0
  708. package/src/world-document-routing.ts +104 -0
  709. package/vite-plugin-creation-site-write.ts +184 -0
  710. package/vite-plugin-creation-site.ts +80 -0
  711. package/vite-plugin-game-static.ts +303 -0
  712. package/vite-plugin-module-doorways.ts +336 -0
  713. package/vite-plugin-product-contributions.ts +197 -0
  714. package/vite-plugin-project-game-static.ts +125 -0
  715. package/vite-plugin-project-jsx-js.ts +65 -0
  716. package/vite-plugin-project-root-absolute-assets.ts +359 -0
  717. package/vite-plugin-shared-react.ts +420 -0
  718. package/vite-plugin-shared-three.ts +203 -0
  719. package/vite-plugin-ui-oid.ts +2106 -0
@@ -0,0 +1,686 @@
1
+ /**
2
+ * The project's OWN FILES — reading and writing them, and serving them to the
3
+ * running game.
4
+ *
5
+ * `/__editor/save-file`, `/__editor/vgai-file`, `/__editor/project-resource`,
6
+ * `/__editor/data-file(s)`, `/__editor/story-files`,
7
+ * `/__editor/scoped-game-css`, `/__editor/source-conflict` and the `/@fs`
8
+ * module server.
9
+ *
10
+ * Every WRITE here goes through `ctx.commitProjectMutation` — the one
11
+ * attributed, conflict-checked transaction — and answers failures through
12
+ * `ctx.projectMutationError`, including the structured revision-conflict
13
+ * shape. There is deliberately no second write path.
14
+ */
15
+
16
+ import { type Dirent, existsSync, readFileSync } from 'node:fs';
17
+ import { readdir, readFile, stat } from 'node:fs/promises';
18
+ import { join, relative, resolve, sep } from 'node:path';
19
+ import { isContainedRelativePath } from '@volter/editor-sdk/session/relative-path-guard';
20
+ import { globToRegExp } from '@volter/editor-sdk/session/source-glob';
21
+ import type { Request, Response } from 'express';
22
+ import { resolveManifestPath } from '@volter/editor-project/manifest/locate';
23
+ import { projectRegionEntriesFromSources } from '../../src/asset-workflow/project-source-index';
24
+ import { ADAPTER_MODULE_FILENAME } from '../../src/ui-source/adapter-region-includes';
25
+ import { foldDataFileText } from '../data-file-serialize';
26
+ import type { EditorServerRouter } from '../editor-server';
27
+ import { buildScopedGameStyles } from '../scoped-game-css';
28
+ import {
29
+ isCanonicalPathInside,
30
+ isPathInside,
31
+ isReadableVgaiPath,
32
+ isServableFsExtension,
33
+ isServableFsPath,
34
+ isViteDepCachePath,
35
+ isWritableProjectResourcePath,
36
+ isWritableVgaiEditorPath,
37
+ projectStorySourceDirs,
38
+ } from '../server-utils';
39
+ import type { RouteContext } from './context';
40
+ import { shareControlError } from './route-helpers';
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Recursive file scanning
44
+ // ---------------------------------------------------------------------------
45
+
46
+ async function findFiles(dir: string, ext: string): Promise<string[]> {
47
+ const results: string[] = [];
48
+ let entries: Dirent[];
49
+ try {
50
+ entries = await readdir(dir, { withFileTypes: true });
51
+ } catch {
52
+ return results;
53
+ }
54
+ for (const entry of entries) {
55
+ if (entry.name.startsWith('.')) continue;
56
+ const full = join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ results.push(...(await findFiles(full, ext)));
59
+ } else if (entry.isFile() && entry.name.endsWith(ext)) {
60
+ results.push(full);
61
+ }
62
+ }
63
+ return results;
64
+ }
65
+
66
+ export function registerProjectSourceRoutes(router: EditorServerRouter, ctx: RouteContext): void {
67
+ const {
68
+ commitProjectMutation,
69
+ engineRoot,
70
+ projectMutationError,
71
+ readProjectManifest,
72
+ sourceConflictTickets,
73
+ trustedShareIdentity,
74
+ } = ctx;
75
+
76
+ router.get('/__editor/source-conflict', async (req: Request, res: Response) => {
77
+ const path =
78
+ typeof req.query['path'] === 'string' ? req.query['path'].split('\\').join('/') : '';
79
+ const absolute = resolve(ctx.projectRoot, path);
80
+ if (!isContainedRelativePath(path) || !isPathInside(ctx.projectRoot, absolute)) {
81
+ res.status(400).json({ error: 'Conflict path is outside the project.' });
82
+ return;
83
+ }
84
+ try {
85
+ if (trustedShareIdentity(req) && !(await isCanonicalPathInside(ctx.projectRoot, absolute))) {
86
+ res.status(403).json({ error: 'Shared conflict path leaves the project root.' });
87
+ return;
88
+ }
89
+ const info = await stat(absolute);
90
+ if (!info.isFile() || info.size > 2 * 1024 * 1024) {
91
+ res.status(413).json({ error: 'Conflict inspection is limited to 2 MiB text files.' });
92
+ return;
93
+ }
94
+ res.json({ path, content: await readFile(absolute, 'utf8') });
95
+ } catch (error) {
96
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
97
+ res.json({ path, content: null });
98
+ return;
99
+ }
100
+ shareControlError(res, error);
101
+ }
102
+ });
103
+
104
+ router.post('/__editor/source-conflict/resolve', async (req: Request, res: Response) => {
105
+ const body = (req.body ?? {}) as Record<string, unknown>;
106
+ const ticket =
107
+ typeof body['conflictId'] === 'string'
108
+ ? sourceConflictTickets.get(body['conflictId'])
109
+ : undefined;
110
+ if (!ticket || ticket.expiresAt < Date.now()) {
111
+ if (typeof body['conflictId'] === 'string') sourceConflictTickets.delete(body['conflictId']);
112
+ res.status(409).json({ error: 'This source-conflict resolution expired. Compare again.' });
113
+ return;
114
+ }
115
+ if (!Array.isArray(body['resources']) || body['resources'].length === 0) {
116
+ res.status(400).json({ error: 'Expected at least one resolved source resource.' });
117
+ return;
118
+ }
119
+ const resources: Array<{ path: string; content: string | null }> = [];
120
+ for (const value of body['resources']) {
121
+ if (!value || typeof value !== 'object') {
122
+ res.status(400).json({ error: 'Invalid resolved source resource.' });
123
+ return;
124
+ }
125
+ const resource = value as Record<string, unknown>;
126
+ if (
127
+ typeof resource['path'] !== 'string' ||
128
+ !ticket.paths.has(resource['path']) ||
129
+ (resource['content'] !== null && typeof resource['content'] !== 'string') ||
130
+ (typeof resource['content'] === 'string' && resource['content'].length > 2 * 1024 * 1024)
131
+ ) {
132
+ res.status(400).json({ error: 'Resolved content must match this conflict ticket.' });
133
+ return;
134
+ }
135
+ resources.push({ path: resource['path'], content: resource['content'] });
136
+ }
137
+ try {
138
+ const revision = await commitProjectMutation(req, resources);
139
+ sourceConflictTickets.delete(body['conflictId'] as string);
140
+ res.json({ ok: true, revision: revision?.revision ?? null });
141
+ } catch (error) {
142
+ projectMutationError(res, error);
143
+ }
144
+ });
145
+ // ---- Save file ----
146
+ router.post('/__editor/save-file', async (req: Request, res: Response) => {
147
+ const body = req.body as {
148
+ path: string;
149
+ content?: string;
150
+ encoding?: 'base64';
151
+ delete?: boolean;
152
+ };
153
+ const filePath = body.path ?? '';
154
+
155
+ // Both halves are load-bearing, and the pair used to be
156
+ // `includes('..')` + `startsWith(publicRoot)`, which let an ABSOLUTE
157
+ // `body.path` through: `resolve(root, 'public', '/root/publicX/x.js')`
158
+ // returns its last argument verbatim, so no `..` ever appeared and the
159
+ // separator-less prefix test accepted the sibling directory. The write
160
+ // then landed outside `public/`.
161
+ if (!isContainedRelativePath(filePath)) {
162
+ res.status(400).json({ error: 'Invalid path.' });
163
+ return;
164
+ }
165
+
166
+ const absPath = resolve(ctx.projectRoot, 'public', filePath);
167
+ if (!isPathInside(ctx.publicRoot, absPath)) {
168
+ res.status(400).json({ error: 'Path outside public directory.' });
169
+ return;
170
+ }
171
+
172
+ try {
173
+ if (body.delete) {
174
+ const revision = await commitProjectMutation(req, [
175
+ { path: `public/${filePath}`, content: null },
176
+ ]);
177
+ res.json({ ok: true, path: filePath, revision: revision?.revision ?? null });
178
+ return;
179
+ }
180
+ if (typeof body.content !== 'string') {
181
+ res.status(400).json({ error: 'Missing content.' });
182
+ return;
183
+ }
184
+ const data = body.encoding === 'base64' ? Buffer.from(body.content, 'base64') : body.content;
185
+ const revision = await commitProjectMutation(req, [
186
+ { path: `public/${filePath}`, content: data },
187
+ ]);
188
+ res.json({ ok: true, path: filePath, revision: revision?.revision ?? null });
189
+ } catch (error) {
190
+ projectMutationError(res, error);
191
+ }
192
+ });
193
+ // ---- Project-root `.vgai/` file access ----
194
+ //
195
+ // The editor-state/thumbnail routes above already read/write
196
+ // `<projectRoot>/.vgai/...` directly via `node:fs` — but `StorageBackend`
197
+ // (`HttpStorage`/`/__editor/save-file` above) is hard-rooted at
198
+ // `<projectRoot>/public/`, so anything a client writes through that seam
199
+ // under a `.vgai/` path actually lands at `<projectRoot>/public/.vgai/...`
200
+ // (the two-`.vgai`-dirs finding recorded in T3.3's close-out).
201
+ // This route pair is the fix: project-root-relative, scoped to `.vgai/` only
202
+ // (defense in depth — it is not a general project-file API), mirroring the
203
+ // editor-state route's mkdir-on-write / read-or-empty shape. Today's readers
204
+ // are the provenance ledger (`src/project-provenance.ts`) and the
205
+ // asset-budget optimize pass (`@vgai/game/src/asset-budget/`).
206
+ router.get('/__editor/vgai-file', async (req: Request, res: Response) => {
207
+ const relPath = (req.query['path'] as string) ?? '';
208
+ if (!isReadableVgaiPath(relPath)) {
209
+ res.status(400).json({ error: 'Invalid path.' });
210
+ return;
211
+ }
212
+ if (ctx.projectRoot === engineRoot) {
213
+ res.status(404).json({ error: 'No project open.' });
214
+ return;
215
+ }
216
+ try {
217
+ const absolute = join(ctx.projectRoot, relPath);
218
+ if (trustedShareIdentity(req) && !(await isCanonicalPathInside(ctx.projectRoot, absolute))) {
219
+ res.status(403).json({ error: 'Shared path leaves the project root.' });
220
+ return;
221
+ }
222
+ const raw = await readFile(absolute);
223
+ res.type(relPath.endsWith('.png') ? 'image/png' : 'text/plain').send(raw);
224
+ } catch {
225
+ res.status(404).json({ error: 'Not found.' });
226
+ }
227
+ });
228
+
229
+ router.post('/__editor/vgai-file', async (req: Request, res: Response) => {
230
+ const body = req.body as { path?: string; content?: string; encoding?: string };
231
+ const relPath = body.path ?? '';
232
+ if (!isWritableVgaiEditorPath(relPath)) {
233
+ res.status(400).json({ error: 'Invalid path.' });
234
+ return;
235
+ }
236
+ if (ctx.projectRoot === engineRoot) {
237
+ res.status(400).json({ error: 'No project open.' });
238
+ return;
239
+ }
240
+ try {
241
+ const content =
242
+ body.encoding === 'base64'
243
+ ? Buffer.from(body.content ?? '', 'base64')
244
+ : (body.content ?? '');
245
+ const revision = await commitProjectMutation(req, [{ path: relPath, content }]);
246
+ res.json({ ok: true, revision: revision?.revision ?? null });
247
+ } catch (error) {
248
+ projectMutationError(res, error);
249
+ }
250
+ });
251
+
252
+ // ---- Project-owned authoring resources ----
253
+ //
254
+ // Asset Lab documents can serialize several native resources as one logical
255
+ // history step. The browser-side history driver reaches each exact byte
256
+ // snapshot through this project-root route; executable source remains owned
257
+ // by `/__ui-source/*`, and project control files remain unreachable.
258
+ // Source files matching `include` globs — what a finder's `include` reads
259
+ // through (ARCHITECTURE-CORE §The project model). Bounded to `src/`.
260
+ router.get('/__editor/source-files', async (req: Request, res: Response) => {
261
+ if (ctx.projectRoot === engineRoot) {
262
+ res.json({ files: [] });
263
+ return;
264
+ }
265
+ const raw = req.query['include'];
266
+ const globs = (Array.isArray(raw) ? raw : [raw]).filter(
267
+ (g): g is string => typeof g === 'string' && g.length > 0 && !g.includes('..'),
268
+ );
269
+ const matchers = globs.map(globToRegExp);
270
+ // `order=mtime` answers newest first: the adapter table takes this order,
271
+ // and a fresh checkout with several documents and no declared default
272
+ // opens on the one most recently written — the one being worked on.
273
+ const byMtime = req.query['order'] === 'mtime';
274
+ const files: { path: string; mtimeMs: number }[] = [];
275
+ const walk = async (absolute: string, rel: string): Promise<void> => {
276
+ let entries: Dirent[];
277
+ try {
278
+ entries = await readdir(absolute, { withFileTypes: true });
279
+ } catch {
280
+ return;
281
+ }
282
+ for (const entry of entries) {
283
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
284
+ const path = rel ? `${rel}/${entry.name}` : entry.name;
285
+ if (entry.isDirectory()) await walk(join(absolute, entry.name), path);
286
+ else if (matchers.some((m) => m.test(path))) {
287
+ let mtimeMs = 0;
288
+ if (byMtime) {
289
+ try {
290
+ mtimeMs = (await stat(join(absolute, entry.name))).mtimeMs;
291
+ } catch {
292
+ /* vanished between readdir and stat: sorts last */
293
+ }
294
+ }
295
+ files.push({ path, mtimeMs });
296
+ }
297
+ }
298
+ };
299
+ await walk(join(ctx.projectRoot, 'src'), 'src');
300
+ files.sort((a, b) =>
301
+ byMtime && a.mtimeMs !== b.mtimeMs ? b.mtimeMs - a.mtimeMs : a.path.localeCompare(b.path),
302
+ );
303
+ res.json({ files: files.map((file) => file.path) });
304
+ });
305
+ router.get('/__editor/project-resource', async (req: Request, res: Response) => {
306
+ const relPath = (req.query['path'] as string) ?? '';
307
+ if (!isWritableProjectResourcePath(relPath)) {
308
+ res.status(400).json({ error: 'Invalid project resource path.' });
309
+ return;
310
+ }
311
+ if (ctx.projectRoot === engineRoot) {
312
+ res.status(404).json({ error: 'No project open.' });
313
+ return;
314
+ }
315
+ try {
316
+ const absolute = join(ctx.projectRoot, relPath);
317
+ if (trustedShareIdentity(req) && !(await isCanonicalPathInside(ctx.projectRoot, absolute))) {
318
+ res.status(403).json({ error: 'Shared path leaves the project root.' });
319
+ return;
320
+ }
321
+ res.type('application/octet-stream').send(await readFile(absolute));
322
+ } catch {
323
+ res.status(404).json({ error: 'Not found.' });
324
+ }
325
+ });
326
+
327
+ router.post('/__editor/project-resource', async (req: Request, res: Response) => {
328
+ const body = req.body as { path?: string; content?: string; encoding?: string };
329
+ const relPath = body.path ?? '';
330
+ if (!isWritableProjectResourcePath(relPath)) {
331
+ res.status(400).json({ error: 'Invalid project resource path.' });
332
+ return;
333
+ }
334
+ if (ctx.projectRoot === engineRoot) {
335
+ res.status(400).json({ error: 'No project open.' });
336
+ return;
337
+ }
338
+ if (body.encoding !== 'base64' || typeof body.content !== 'string') {
339
+ res.status(400).json({ error: 'Project resource content must be base64.' });
340
+ return;
341
+ }
342
+ const content = Buffer.from(body.content, 'base64');
343
+ if (content.toString('base64') !== body.content) {
344
+ res.status(400).json({ error: 'Project resource content is not valid base64.' });
345
+ return;
346
+ }
347
+ try {
348
+ const revision = await commitProjectMutation(req, [{ path: relPath, content }]);
349
+ res.json({ ok: true, revision: revision?.revision ?? null });
350
+ } catch (error) {
351
+ projectMutationError(res, error);
352
+ }
353
+ });
354
+
355
+ router.delete('/__editor/project-resource', async (req: Request, res: Response) => {
356
+ const relPath = (req.body as { path?: string }).path ?? '';
357
+ if (!isWritableProjectResourcePath(relPath)) {
358
+ res.status(400).json({ error: 'Invalid project resource path.' });
359
+ return;
360
+ }
361
+ if (ctx.projectRoot === engineRoot) {
362
+ res.status(400).json({ error: 'No project open.' });
363
+ return;
364
+ }
365
+ try {
366
+ const revision = await commitProjectMutation(req, [{ path: relPath, content: null }]);
367
+ res.json({ ok: true, revision: revision?.revision ?? null });
368
+ } catch (error) {
369
+ projectMutationError(res, error);
370
+ }
371
+ });
372
+ // ---- Data assets (W2) ----
373
+ //
374
+ // The Data dock tab's server half. Discovery is the folder scan, per D3 (no
375
+ // asset database): every `src/data/*.data.json` of the open project, plus
376
+ // the sibling EMITTED `*.schema.json` when present — the editor consumes the
377
+ // JSON Schema interchange artifact and never executes the project's
378
+ // `.schema.ts` (§2.1). Raw file text is returned (not parsed JSON) so the
379
+ // client owns parse errors and can report a malformed file instead of this
380
+ // route 500ing on it.
381
+ //
382
+ // Why not `/__editor/save-file` + `/@fs/` reads: that seam is HARD-ROOTED at
383
+ // `<projectRoot>/public/` (see `HttpStorage`'s doc comment and the
384
+ // two-`.vgai`-dirs finding above) — data assets live under the project
385
+ // root's `src/data/`, outside its reach. Same fix as the `vgai-file` pair:
386
+ // a purpose-scoped route rooted at the project root, deliberately NOT a
387
+ // general project-file API (defense in depth — the write half accepts only
388
+ // `src/data/**/*.data.json`).
389
+ router.get('/__editor/data-files', async (_req: Request, res: Response) => {
390
+ if (ctx.projectRoot === engineRoot) {
391
+ res.json({ files: [] });
392
+ return;
393
+ }
394
+ const dataDir = join(ctx.projectRoot, 'src', 'data');
395
+ const found = await findFiles(dataDir, '.data.json');
396
+ const files: Array<{ path: string; content: string; schema: string | null }> = [];
397
+ for (const abs of found.sort()) {
398
+ let content: string;
399
+ try {
400
+ content = await readFile(abs, 'utf-8');
401
+ } catch {
402
+ continue; // deleted between scan and read — just omit it
403
+ }
404
+ // Sibling emitted schema (D4: generated + committed by default). Absent
405
+ // (project hasn't run `npm run emit-schemas`) → null; the client shows
406
+ // a teaching message rather than guessing widget types.
407
+ let schema: string | null = null;
408
+ try {
409
+ schema = await readFile(abs.replace(/\.data\.json$/, '.schema.json'), 'utf-8');
410
+ } catch {
411
+ /* no emitted schema */
412
+ }
413
+ files.push({ path: relative(ctx.projectRoot, abs).split('\\').join('/'), content, schema });
414
+ }
415
+ res.json({ files });
416
+ });
417
+
418
+ // D1 (spec §2.5): a Data-panel edit writes the `.data.json` IMMEDIATELY. A
419
+ // running game picks the write up
420
+ // through the already-shipped HMR path (`dev.ts`'s `.data.json` exemption →
421
+ // the owning module's `import.meta.hot.accept` → `DataHandle.hotSwap`).
422
+ // Undo for a data write is git, so there is no byte-exact replay path and
423
+ // therefore no bytes-in body variant — see the fold note in the handler.
424
+ router.post('/__editor/data-file', async (req: Request, res: Response) => {
425
+ const body = req.body as { path?: string; content?: unknown; values?: unknown };
426
+ const relPath = body.path ?? '';
427
+ if (
428
+ !isContainedRelativePath(relPath) ||
429
+ !relPath.startsWith('src/data/') ||
430
+ !relPath.endsWith('.data.json')
431
+ ) {
432
+ res.status(400).json({ error: 'Invalid path — expected src/data/**/*.data.json.' });
433
+ return;
434
+ }
435
+ if (ctx.projectRoot === engineRoot) {
436
+ res.status(400).json({ error: 'No project open.' });
437
+ return;
438
+ }
439
+ // ONE accepted body: `values`, a parsed next-state object. THIS route folds
440
+ // it against the file's current key order and serializes it
441
+ // (`data-file-serialize.ts`). The data-asset UI ships as a copied
442
+ // capability, so the fold cannot live there: a project that reorganizes its
443
+ // own table UI must not be able to reorganize the file's bytes with it.
444
+ // Raw bytes are deliberately NOT accepted — a caller that could hand over
445
+ // exact text would be a second serializer, and the fold would no longer be
446
+ // the one place the diff contract lives.
447
+ if (body.content !== undefined) {
448
+ res.status(400).json({
449
+ error:
450
+ 'This route writes PARSED VALUES, not bytes — post `values` (a JSON object) and the ' +
451
+ "dev server's fold owns key order and the $schema-first serialization.",
452
+ });
453
+ return;
454
+ }
455
+ if (body.values === undefined) {
456
+ res
457
+ .status(400)
458
+ .json({ error: "Missing values — post the file's parsed next state as `values`." });
459
+ return;
460
+ }
461
+ if (!body.values || typeof body.values !== 'object' || Array.isArray(body.values)) {
462
+ res.status(400).json({ error: 'values must be a JSON object.' });
463
+ return;
464
+ }
465
+ let currentText: string | null = null;
466
+ try {
467
+ currentText = await readFile(join(ctx.projectRoot, relPath), 'utf-8');
468
+ } catch {
469
+ /* new file — the posted values define the order */
470
+ }
471
+ const content = foldDataFileText(currentText, body.values as Record<string, unknown>);
472
+ try {
473
+ const revision = await commitProjectMutation(req, [{ path: relPath, content }]);
474
+ res.json({ ok: true, path: relPath, revision: revision?.revision ?? null });
475
+ } catch (error) {
476
+ projectMutationError(res, error);
477
+ }
478
+ });
479
+
480
+ // There is deliberately no GET/DELETE on `/__editor/data-file`: reading a
481
+ // data asset is the folder scan (`GET /__editor/data-files`, which returns
482
+ // every file's text), and nothing in the product deletes one through the
483
+ // server. This route is the WRITE seam only.
484
+ // ---- Story discovery (C3, spec §9 — "Add CSF Discovery and Portable
485
+ // Story Mounting") ----
486
+ //
487
+ // Conventional `*.stories.tsx` / `*.stories.ts` files, colocated anywhere
488
+ // under the project's `src/` (Storybook's own convention — unlike
489
+ // `src/contributions/`/`src/data/`, stories are not confined to one folder). Same
490
+ // D3 physics as every other editor list here: a live folder scan, never a
491
+ // cached manifest — add/remove a `*.stories.tsx` and the next refresh sees
492
+ // it. Paths only: the CSF MODULE itself is loaded client-side through
493
+ // Vite's `/@fs/` dynamic-import path (`story-discovery.ts`, mirroring
494
+ // `tool-loader.ts`), so it arrives transpiled with HMR, and the editor
495
+ // never stores a duplicate story definition — the CSF module stays the one
496
+ // source of truth (spec §9 C3 "Story source remains the CSF module").
497
+ //
498
+ // `previewModulePath` rides along in the same response: the project's
499
+ // OPTIONAL `.storybook/preview.tsx`/`.ts` (checked with `existsSync`, same
500
+ // "server tells the truth about disk, client never guesses" contract as
501
+ // the rest of this route), or `null` when the project has no `.storybook/`
502
+ // folder — most projects don't. `story-discovery.ts`'s
503
+ // `loadProjectPreviewAnnotations` used to blind-probe both candidate paths
504
+ // client-side via `/@fs/` and swallow the import failure; that still
505
+ // logged a real failed network request for every project without a
506
+ // preview config (the common case), which is indistinguishable from a
507
+ // genuine broken-editor error to anything watching the page's network/
508
+ // console (e.g. `check-vgai-generated-project-p2p.ts`'s browser-proof).
509
+ // Reporting existence here lets the client only ever import a path that is
510
+ // actually there.
511
+ router.get('/__editor/story-files', async (_req: Request, res: Response) => {
512
+ if (ctx.projectRoot === engineRoot) {
513
+ res.json({ files: [], previewModulePath: null, regions: [] });
514
+ return;
515
+ }
516
+ // NOT `<projectRoot>/src` — the project's own manifest says where its
517
+ // authored source lives (`projectStorySourceDirs`, server-utils.ts). For
518
+ // every scaffolded project and every `examples/<id>` that IS
519
+ // `<projectRoot>/src`; for a project whose root `entry` resolves outside
520
+ // its folder (a repo-vendored game: manifest + host shim under
521
+ // `packages/editor/src/ingest/games/<id>/`, source under
522
+ // `vendor/games/<id>/src/`) it is that game's source tree as well, so a
523
+ // story colocated with the component it declares is DISCOVERED there.
524
+ const scanDirs = projectStorySourceDirs(ctx.projectRoot, readProjectManifest());
525
+ const scans = await Promise.all(
526
+ scanDirs.flatMap((dir) => [findFiles(dir, '.stories.tsx'), findFiles(dir, '.stories.ts')]),
527
+ );
528
+ const found = scans.flat().filter((abs) => !abs.includes(`${sep}node_modules${sep}`));
529
+ const previewCandidates = ['.storybook/preview.tsx', '.storybook/preview.ts'];
530
+ const previewModulePath =
531
+ previewCandidates.find((candidate) => existsSync(join(ctx.projectRoot, candidate))) ?? null;
532
+ let manifestSource = '';
533
+ let adapterSource = '';
534
+ try {
535
+ manifestSource = readFileSync(resolveManifestPath(ctx.projectRoot), 'utf8');
536
+ } catch {
537
+ /* no manifest — regions stay empty */
538
+ }
539
+ try {
540
+ adapterSource = readFileSync(join(ctx.projectRoot, ADAPTER_MODULE_FILENAME), 'utf8');
541
+ } catch {
542
+ /* no adapter — native default, regions still derive from roots */
543
+ }
544
+ const { regions } = projectRegionEntriesFromSources(manifestSource, adapterSource);
545
+ res.json({
546
+ files: found.sort().map((abs) => ({
547
+ path: relative(ctx.projectRoot, abs).split('\\').join('/'),
548
+ })),
549
+ previewModulePath,
550
+ regions,
551
+ });
552
+ });
553
+
554
+ // ---- Scoped game CSS (the game's page-level stylesheet, contained) ----
555
+ // A foreign game keeps its HUD layout in a page stylesheet whose `html`/
556
+ // `body`/`*` rules would restyle the EDITOR, so no surface could load it and
557
+ // every surface showing that game's DOM rendered unstyled. Serve it rewritten
558
+ // into `@scope ([data-vgai-game-styles])` instead: the client attaches it once
559
+ // and marks each container that owns game DOM (a story card's content, the
560
+ // ingest surface). The transform is SERVE-TIME — the bytes under
561
+ // `vendor/games/` are read and never written, which is what keeps
562
+ // `vendor/games/verify-unaltered.mjs` green.
563
+ router.get('/__editor/scoped-game-css', (_req: Request, res: Response) => {
564
+ const result = buildScopedGameStyles({
565
+ manifest: readProjectManifest(),
566
+ readStyleSheet: (relPath) => {
567
+ // Same containment every project read here has: a declared path is
568
+ // resolved against the project root and must stay on disk. A vendored
569
+ // game's sheet legitimately sits OUTSIDE the project folder (the
570
+ // manifest lives under `packages/editor/src/ingest/games/<id>/`, the
571
+ // game's source under `vendor/games/<id>/src/`), which is exactly the
572
+ // shape `projectStorySourceDirs` already serves stories from — so the
573
+ // bound is "readable file", not "inside ctx.projectRoot".
574
+ const absolute = resolve(ctx.projectRoot, relPath);
575
+ try {
576
+ return {
577
+ css: readFileSync(absolute, 'utf8'),
578
+ // Vite serves an out-of-root file at `/@fs/<absolute>`; relative
579
+ // `url()`s in the sheet rebase onto that, so a game asset request
580
+ // goes to the game's own folder instead of an editor route.
581
+ servedPath: `/@fs/${absolute.split(sep).join('/')}`,
582
+ };
583
+ } catch {
584
+ return null;
585
+ }
586
+ },
587
+ });
588
+ res.json(result);
589
+ });
590
+ }
591
+
592
+ /**
593
+ * The module transport's missing-module answer, registered SEPARATELY because
594
+ * its POSITION is load-bearing: it must sit after the packaged host's
595
+ * transpiling `/@fs` route so that route's extension resolution runs first.
596
+ * The server calls this immediately after that block, exactly where the
597
+ * middleware used to be written inline.
598
+ */
599
+ export function registerModuleTransportNotFound(
600
+ router: EditorServerRouter,
601
+ ctx: RouteContext,
602
+ ): void {
603
+ const { engineRoot } = ctx;
604
+
605
+ // ---- A DELETED project module must 404, not become the editor's HTML ----
606
+ //
607
+ // Measured live (2026-08-19): delete a file the mounted world imports, and
608
+ // `GET /@fs/<abs>/src/scenes/MainScene.tsx` answered `200 text/html` with the
609
+ // editor's own `index.html` (27 KB) — the SPA fallback, which Vite's
610
+ // `htmlFallbackMiddleware` applies to any unmatched GET whose `Accept`
611
+ // includes `*/*`, and a browser's ES-module fetch sends exactly that. The
612
+ // browser then refuses to execute a module served as HTML and reports
613
+ //
614
+ // TypeError: Failed to fetch dynamically imported module:
615
+ // http://…/src/world.tsx?vgai-mount=19
616
+ //
617
+ // which names the ENTRY (a file that exists), never the deleted file, and
618
+ // carries a `?vgai-mount=N` that grows per attempt. `vgai play` then refuses
619
+ // four times in a row with a message that reads like the server serving a
620
+ // stale mount graph — which is where a probe went looking, and there is no
621
+ // such bug: the unlink watcher fires, `project-script-hmr.ts` stamps the
622
+ // graph, and restoring the file recovers without a restart. The whole defect
623
+ // was this response.
624
+ //
625
+ // Line 3121's `hasUiRegistry` comment already recorded the behavior ("a dev
626
+ // server answers 200 (SPA fallback) for missing files") and worked around it
627
+ // at one call site. This closes the class instead: `/@fs/` is a MODULE
628
+ // TRANSPORT, and a module that does not exist has exactly one honest answer.
629
+ //
630
+ // Deliberately narrow — anything not provably a missing module falls through
631
+ // untouched: only GET/HEAD, only paths contained in the project or engine
632
+ // root, only the `/@fs/` extensions this server already agrees to serve
633
+ // (`isServableFsExtension`), never the private estates (`isServableFsPath`),
634
+ // never Vite's own dependency-optimizer cache (`isViteDepCachePath` — those
635
+ // files are written asynchronously and absence there means "not bundled
636
+ // YET", which is Vite's middleware to answer, not ours), and only when the
637
+ // path genuinely is not on disk. Registered after the production transpiling
638
+ // route above so its extension-resolution runs first.
639
+ router.use('/@fs', async (req: Request, res: Response, next) => {
640
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
641
+ next();
642
+ return;
643
+ }
644
+ const absPath = resolve(req.path);
645
+ if (!isPathInside(ctx.projectRoot, absPath) && !isPathInside(engineRoot, absPath)) {
646
+ next();
647
+ return;
648
+ }
649
+ const ext = absPath.split('.').pop() ?? '';
650
+ // An extensionless path may still resolve (index files, appended
651
+ // extensions), and a private path must not have its existence confirmed
652
+ // or denied here — both keep their existing behavior. The `stat` is async
653
+ // (like the transpiling route's above) because this sits on the module
654
+ // transport a cold boot drives thousands of requests through.
655
+ if (!isServableFsExtension(ext) || !isServableFsPath(absPath)) {
656
+ next();
657
+ return;
658
+ }
659
+ // The optimizer's own output is never "a module the game imports is
660
+ // missing" — mid-run it is simply not written yet. See `isViteDepCachePath`.
661
+ if (isViteDepCachePath(absPath)) {
662
+ next();
663
+ return;
664
+ }
665
+ const present = await stat(absPath).then(
666
+ () => true,
667
+ () => false,
668
+ );
669
+ if (present) {
670
+ next();
671
+ return;
672
+ }
673
+ res
674
+ .status(404)
675
+ .type('text/plain')
676
+ .send(
677
+ `vgai: ${absPath} does not exist.\n\n` +
678
+ 'A module the running game imports is missing from disk — deleted or renamed, ' +
679
+ 'and the import that names it was not updated. This is a 404 rather than the ' +
680
+ "editor's index.html so the failure names the missing file instead of surfacing " +
681
+ 'as an opaque "Failed to fetch dynamically imported module" against the entry.\n\n' +
682
+ 'Restore the file, or fix the import that still names it; the editor picks the ' +
683
+ 'change up without a restart.\n',
684
+ );
685
+ });
686
+ }