@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,550 @@
1
+ /**
2
+ * Tab lifecycle — the stateful shell around the tab table (`tab-presence.ts`).
3
+ *
4
+ * The owner invariant is unchanged: per edited game there is ONE and only one
5
+ * browser tab. What changed on 2026-08-09 is how the server knows a tab
6
+ * exists. It used to know by socket: an identified connection meant "here", a
7
+ * close meant "lost — reopening". Both readings were wrong often enough to
8
+ * ship bugs — a blocked main thread reads as dead, a reload reads as a
9
+ * departure — and the relay compounded it by demanding an application-level
10
+ * state report before it would route a command at all. Now a tab proves
11
+ * itself by HEARTBEAT (a dedicated Worker, `tab-heartbeat.ts`), the table is
12
+ * the only truth, and this file is a thin shell:
13
+ *
14
+ * - Beats and socket events go IN as facts. Socket events are hints only:
15
+ * they update one boolean and schedule a reconcile. Nothing here opens,
16
+ * closes or blesses in a connect/disconnect handler.
17
+ * - ONE reconcile function runs on a tick and on those hints. It picks the
18
+ * single blessed tab (sticky while present, else oldest), tells extras to
19
+ * yield, and probes tabs whose beats have gapped. It NEVER opens one.
20
+ * - Everything it decides is journaled in tab vocabulary — appeared,
21
+ * heartbeat-gap, gap-closed, reloaded, departed, duplicated, blessed. The
22
+ * words "lost" and "reopening" are gone: neither was ever a fact about a
23
+ * tab.
24
+ *
25
+ * NOTHING IN THE BACKGROUND OPENS A TAB (owner, 2026-08-10). `vgai edit` opens
26
+ * one; that is the whole list. The reconciler used to open whenever the table
27
+ * went empty, on a budget a tab's own appearance RESET — so "open → tab
28
+ * appears → you close it → open" ran forever, and it spent a night reopening
29
+ * windows on two ports the owner was not using. The budget's doc comment
30
+ * already said a returning tab must never refill it; the refill at the other
31
+ * end of the file quietly re-enabled the loop it was written to stop. Closing
32
+ * a tab is an instruction, not a fault to repair.
33
+ */
34
+
35
+ import type { SessionJournalEvent } from './support/project/session-journal';
36
+ import {
37
+ DEFAULT_TAB_PRESENCE_CONFIG,
38
+ departedTabReport,
39
+ initialTabPresenceState,
40
+ latestCensus,
41
+ noteTabCommandOutcome,
42
+ presentTabs,
43
+ reconcile,
44
+ recordBeat,
45
+ recordTabClose,
46
+ setTabConnected,
47
+ setTabListener,
48
+ setTabRoute,
49
+ setTabSurface,
50
+ type TabBeat,
51
+ type TabCensus,
52
+ type TabCloseBeacon,
53
+ type TabPresenceConfig,
54
+ type TabPresenceReport,
55
+ type TabPresenceState,
56
+ type TabRecord,
57
+ type TabRoute,
58
+ type TabSurface,
59
+ tabArriving,
60
+ tabEndAcknowledged,
61
+ tabNeedsProbe,
62
+ tabPresenceReport,
63
+ tabState,
64
+ tabUnresponsive,
65
+ } from './tab-presence';
66
+
67
+ export type { TabRoute, TabSurface } from './tab-presence';
68
+
69
+ export type TabEnsureOutcome = 'focused' | 'adopt' | 'reloading' | 'arriving' | 'opening' | 'noop';
70
+
71
+ /** How long session end waits for the tabs to acknowledge it before going
72
+ * down anyway. A page that cannot answer must never hold a shutdown open. */
73
+ const SESSION_END_ACK_BUDGET_MS = 2_000;
74
+
75
+ /** How often the wait re-reads the table. The acks arrive over HTTP into this
76
+ * same process, so this is a poll of local state, not of a network. */
77
+ const SESSION_END_ACK_POLL_MS = 25;
78
+
79
+ export interface TabLifecycleController {
80
+ /**
81
+ * A control connection for this tab opened. A HINT — see the header.
82
+ *
83
+ * `surface` is what kind of PAGE opened it ({@link TabSurface}); it rides
84
+ * this hint because a connection is the first and only moment the server
85
+ * learns it, and it must be known for a page that never beats.
86
+ */
87
+ onTabChannelOpen(tabId: string, surface?: TabSurface): void;
88
+ /** A control connection for this tab closed. A HINT. */
89
+ onTabChannelClose(tabId: string): void;
90
+ /** One heartbeat; `'re-mint'` tells a duplicated tab to take a new identity. */
91
+ onBeat(beat: TabBeat): 're-mint' | null;
92
+ /**
93
+ * A page said goodbye (`pagehide` → `navigator.sendBeacon`). The ONE page
94
+ * signal this file gained on 2026-09-17, and the only thing that tells a
95
+ * closed/reloading tab from a crashed one — see `tab-presence.ts`'s
96
+ * {@link TabCloseBeacon}.
97
+ */
98
+ onTabClose(beacon: TabCloseBeacon): void;
99
+ /**
100
+ * How a relayed command against this tab ENDED. The server's own evidence
101
+ * for `hung`: a command that expired while the beats stayed fresh is the
102
+ * page not running, and the next answered one is proof it is.
103
+ */
104
+ onCommandOutcome(tabId: string, outcome: 'timed-out' | 'answered'): void;
105
+ /**
106
+ * This tab's page reported its COMMAND LISTENER attached — the only
107
+ * evidence the server ever gets that the document behind the channel is
108
+ * actually running. A fact, like a beat: recorded, then reconciled on.
109
+ */
110
+ onTabListener(tabId: string, pageEpoch: string): void;
111
+ /** The tab said which surface it is showing. */
112
+ onTabRoute(tabId: string, route: TabRoute): void;
113
+ /** Yield-page claim: bless the claimant's tab instead of the current one. */
114
+ claim(tabId?: string): void;
115
+ /** Converge for an edit/create invocation; opens the browser on 'opening'. */
116
+ ensure(open: boolean): TabEnsureOutcome;
117
+ /** Successor incoming on this port — do NOT close the tab at shutdown. */
118
+ expectRestart(): void;
119
+ /**
120
+ * `tab-close` to every tab, then WAIT for the tabs that were present to
121
+ * acknowledge it — see the implementation for what the wait is worth.
122
+ */
123
+ notifySessionEnded(): Promise<void>;
124
+ /** One reconcile pass (interval-driven; exposed for tests). */
125
+ tick(): void;
126
+ stop(): void;
127
+ /** The one tab commands address, or null when none is present. */
128
+ blessedTabId(): string | null;
129
+ /** Present tabs, oldest first. */
130
+ present(): TabRecord[];
131
+ /** Present tabs whose PAGE never came up this page-load — no control channel
132
+ * at all, or a channel with no command listener behind it. See
133
+ * `tab-presence.ts`'s `tabUnresponsive` for the two stages and their budgets. */
134
+ unresponsive(): TabRecord[];
135
+ /** One row per present tab — what `vgai status` prints. */
136
+ report(): TabPresenceReport[];
137
+ /** One row per RECENTLY DEPARTED tab — the only place `closed` and
138
+ * `crashed` can be said, because both are verdicts about a tab that is no
139
+ * longer present. Bounded memory, not a log. */
140
+ departedReport(): TabPresenceReport[];
141
+ /** This tab's record, present or not (a refusal quotes it). */
142
+ tab(tabId: string): TabRecord | undefined;
143
+ /**
144
+ * This tab's newest resource census and how stale it is, on the lifecycle's
145
+ * OWN clock — the one that stamped it, so the age is a subtraction and not a
146
+ * guess across two clocks. Null when the tab is unknown or never sent one.
147
+ *
148
+ * Asked at exactly the moment a tab stops being normal (an abnormal socket
149
+ * close), which is why it does not require the tab to be present.
150
+ */
151
+ censusOf(tabId: string): { census: TabCensus; ageMs: number } | null;
152
+ /** Read-only snapshot (tests/diagnostics). */
153
+ state(): Readonly<TabPresenceState>;
154
+ }
155
+
156
+ export interface TabLifecycleOptions {
157
+ /** The URL the one tab must show (and the one auto-open opens). */
158
+ editorUrl: string;
159
+ /** Platform browser hand-off (same helper family the CLI uses). */
160
+ openUrl: (url: string) => void;
161
+ /** Targeted send to every live connection of one TAB; false when it has none. */
162
+ sendToTab: (tabId: string, event: string, data: unknown) => boolean;
163
+ /**
164
+ * Send to EVERY connection in this lifecycle's scope. Session end is the
165
+ * only caller and the reason it exists: `tab-close` used to reach the
166
+ * blessed tab alone, so every OTHER connected tab (an extra on the yield
167
+ * page, a launcher tab, a second window) learned about the shutdown only by
168
+ * its socket dying and fell through to the generic "Editor disconnected"
169
+ * overlay. Returns how many connections took it.
170
+ */
171
+ broadcastToClients: (event: string, data: unknown) => number;
172
+ /** Ask these tabs' workers to beat NOW (see `tab-heartbeat.ts`). */
173
+ probeTabs?: ((tabIds: readonly string[]) => void) | undefined;
174
+ /** Live view of the server's last index-page request stamp. */
175
+ lastIndexRequestAt: () => number | null;
176
+ /** One journal line per decision. */
177
+ journal?: ((event: SessionJournalEvent) => void) | undefined;
178
+ /** A tab's page reloaded under the same tabId (its `epochCount` advanced).
179
+ * The page that held everything addressed to the old page-load is gone;
180
+ * the control plane settles those commands on this. */
181
+ onTabReloaded?: ((tabId: string, epochCount: number) => void) | undefined;
182
+ now?: () => number;
183
+ config?: Partial<TabPresenceConfig>;
184
+ /** Reconcile cadence; 0 disables the interval (tests drive `tick()`). */
185
+ tickIntervalMs?: number;
186
+ /** False for a remote participant: enforce one tab, never open one here. */
187
+ maintain?: boolean;
188
+ }
189
+
190
+ export function createTabLifecycle(options: TabLifecycleOptions): TabLifecycleController {
191
+ const config: TabPresenceConfig = { ...DEFAULT_TAB_PRESENCE_CONFIG, ...options.config };
192
+ const now = options.now ?? Date.now;
193
+ const journal = options.journal ?? ((): void => {});
194
+ const maintain = options.maintain ?? true;
195
+ let state = initialTabPresenceState();
196
+ let restartExpected = false;
197
+ /** Tabs already told to yield for the CURRENT blessing — one nudge each. */
198
+ let yielded = new Set<string>();
199
+ /** Page-loads already told to reload themselves — one attempt each, so a
200
+ * document that cannot boot is never put in a loop. */
201
+ const reloaded = new Set<string>();
202
+
203
+ function emit(events: readonly SessionJournalEvent[]): void {
204
+ for (const event of events) journal(event);
205
+ }
206
+
207
+ /**
208
+ * The single decision point. Every caller below either records a fact and
209
+ * lands here, or reads the table — nothing decides on its own.
210
+ */
211
+ function run(): void {
212
+ const at = now();
213
+ const previousBlessed = state.blessedTabId;
214
+ const result = reconcile(
215
+ state,
216
+ { now: at, lastIndexRequestAt: options.lastIndexRequestAt(), mayOpen: maintain },
217
+ config,
218
+ );
219
+ state = result.state;
220
+ emit(result.events);
221
+
222
+ if (result.blessedTabId !== previousBlessed) yielded = new Set();
223
+ for (const tabId of result.yieldTabIds) {
224
+ if (yielded.has(tabId)) continue;
225
+ // A worker beat can arrive before its page's control channel. Retry on
226
+ // the next reconcile when there was no channel to take this nudge.
227
+ if (!options.sendToTab(tabId, 'tab-yield', { reason: 'duplicate' })) continue;
228
+ yielded.add(tabId);
229
+ journal({ kind: 'tab-yielded', tabId8: tabId.slice(0, 8) });
230
+ }
231
+
232
+ // Probing is a question the TABLE asks: only tabs whose beats have
233
+ // gapped, and only while they are still inside their grace. A hidden tab
234
+ // answers from its worker's message handler even when its timers are
235
+ // throttled, which is what keeps it present indefinitely.
236
+ if (options.probeTabs) {
237
+ const stale = [...state.tabs.values()]
238
+ .filter((tab) => tabNeedsProbe(tab, at, config))
239
+ .map((tab) => tab.tabId);
240
+ if (stale.length > 0) options.probeTabs(stale);
241
+ }
242
+ }
243
+
244
+ const intervalMs = options.tickIntervalMs ?? 1_000;
245
+ let timer: ReturnType<typeof setInterval> | null = null;
246
+
247
+ const controller: TabLifecycleController = {
248
+ onTabChannelOpen(tabId, surface) {
249
+ state = setTabConnected(state, tabId, true, now());
250
+ if (surface !== undefined) state = setTabSurface(state, tabId, surface);
251
+ run();
252
+ },
253
+ onTabChannelClose(tabId) {
254
+ state = setTabConnected(state, tabId, false, now());
255
+ run();
256
+ },
257
+ onBeat(beat) {
258
+ const outcome = recordBeat(state, beat, now(), config);
259
+ state = outcome.state;
260
+ emit(outcome.events);
261
+ for (const event of outcome.events)
262
+ if (event.kind === 'tab-reloaded') options.onTabReloaded?.(beat.tabId, event.epochCount);
263
+ // A beat that CLOSES a gap is a hint worth reconciling on immediately —
264
+ // it is the moment a tab stops being a question.
265
+ if (outcome.events.length > 0) run();
266
+ return outcome.reply;
267
+ },
268
+ onTabClose(beacon) {
269
+ const before = state;
270
+ state = recordTabClose(state, beacon, now());
271
+ // A goodbye is the moment a tab stops being a question — reconcile on it
272
+ // for the same reason a gap-closing beat does.
273
+ if (state !== before) run();
274
+ },
275
+ onCommandOutcome(tabId, outcome) {
276
+ state = noteTabCommandOutcome(state, tabId, now(), outcome);
277
+ },
278
+ onTabListener(tabId, pageEpoch) {
279
+ const before = state;
280
+ state = setTabListener(state, tabId, now(), pageEpoch);
281
+ // Only the transition is worth a reconcile: this arrives once per
282
+ // page-load, and it is the moment a tab stops being a question.
283
+ if (state !== before) run();
284
+ },
285
+ onTabRoute(tabId, route) {
286
+ state = setTabRoute(state, tabId, route);
287
+ },
288
+ claim(tabId) {
289
+ // "Use here instead": drop the blessing so the next reconcile picks the
290
+ // claimant. The claimant's own tab is preferred when it names itself.
291
+ const current = state.blessedTabId;
292
+ if (current !== null && current !== tabId) {
293
+ options.sendToTab(current, 'tab-yield', { reason: 'claimed' });
294
+ }
295
+ state = { ...state, blessedTabId: tabId ?? null, blessedAt: now() };
296
+ if (tabId !== undefined) {
297
+ journal({
298
+ kind: 'tab-blessed',
299
+ tabId8: tabId.slice(0, 8),
300
+ previousTabId8: current === null ? null : current.slice(0, 8),
301
+ reason: 'claimed',
302
+ });
303
+ }
304
+ yielded = new Set();
305
+ run();
306
+ },
307
+ ensure(open) {
308
+ run();
309
+ const at = now();
310
+ const blessed = state.blessedTabId;
311
+ // HEAL A DEAD DOCUMENT, and only when nothing can take a command.
312
+ //
313
+ // A page that connected and never attached a command listener cannot act
314
+ // on `tab-refocus` or `tab-adopt`: it buffers them for a consumer that is
315
+ // never coming (`index.html`'s bootstrap), so adopting it again leaves
316
+ // the session holding a tab where, as `vgai status` says, "no command
317
+ // will complete". `tab-reload` is the one message the bootstrap acts on
318
+ // without the module graph.
319
+ //
320
+ // The condition is about the SESSION, not about which tab is blessed. A
321
+ // dead tab that is merely present keeps `ensure` from opening a fresh one
322
+ // while never being able to run anything itself, and only checking the
323
+ // blessed tab left exactly that state unhealable. So: if NO tab has a
324
+ // command listener, reload every tab that has given up on getting one.
325
+ //
326
+ // And never while a healthy tab exists. Reloading a dead tab beside a
327
+ // working one produces two healthy tabs, and the bijection then tells one
328
+ // to yield — which closed the tab fourteen minutes into a replay and lost
329
+ // the run (measured 2026-09-15: `client-disconnected` code 1001, a
330
+ // deliberate unload, with the model's command still in flight).
331
+ //
332
+ // Once per page-load, and only from an explicit `vgai edit`, so a
333
+ // document that cannot boot is never put in a reload loop.
334
+ const anyHealthy = [...state.tabs.values()].some(
335
+ (tab) => tab.listenerThisEpoch && tab.connected,
336
+ );
337
+ if (!anyHealthy) {
338
+ for (const tab of state.tabs.values()) {
339
+ if (!tabUnresponsive(tab, at, config)) continue;
340
+ const epoch = tab.epoch ?? tab.tabId;
341
+ if (reloaded.has(epoch)) continue;
342
+ if (!options.sendToTab(tab.tabId, 'tab-reload', {})) continue;
343
+ reloaded.add(epoch);
344
+ // Deliberately UNJOURNALED. This used to write `tab-yielded`, which
345
+ // is a different decision with a different consequence — a yield
346
+ // tells a page to go away — and a reader diagnosing the wedge sees
347
+ // one word for two acts. The pair that already brackets this is
348
+ // honest and complete: `tab-unresponsive` (the cause, journaled by
349
+ // the reconciler) and `tab-reloaded` (the outcome, journaled when the
350
+ // successor page-load beats). A send that produced neither is a lost
351
+ // message, which is exactly what should read as nothing happening.
352
+ return 'reloading';
353
+ }
354
+ }
355
+ // A BLESSED TAB THAT SAID GOODBYE IS NOT A TAB.
356
+ //
357
+ // Blessing is not released by a close: the record stays, `connected`
358
+ // goes false, and `listenerThisEpoch` is still latched from the epoch
359
+ // that just ended — so the branch below answered `focused` for a page
360
+ // that is gone, and the CLI printed "Editor already open on this
361
+ // project" and exited 0 having opened nothing. MEASURED twice on walk 5,
362
+ // each time by quitting the browser and running `vgai edit .`
363
+ // immediately: `vgai status` in the same breath read
364
+ //
365
+ // tab 7b08ccd5: CLOSED 7.4s — its page sent a close beacon 7.4s ago
366
+ // and no new page-load is beating
367
+ //
368
+ // and `vgai edit` said the editor was already open. A second run ~30 s
369
+ // later, once the record had aged out of `presentTabs`, opened the tab
370
+ // normally — so the defect was a WINDOW, which is exactly the window a
371
+ // person or an agent reaches for `vgai edit` in.
372
+ //
373
+ // The verdict this asks is the SAME ONE the status line prints
374
+ // (`tabState`), so the two can no longer disagree about whether there is
375
+ // a tab. A `reloading` verdict is deliberately not here: that page said
376
+ // goodbye and a successor is already beating, which the branches below
377
+ // handle as the arriving tab it is.
378
+ const saidGoodbye = (tab: TabRecord): boolean => {
379
+ const verdict = tabState(tab, at, config).state;
380
+ return verdict === 'closed' || verdict === 'ended';
381
+ };
382
+ const blessedRecord = blessed === null ? undefined : state.tabs.get(blessed);
383
+ if (blessed !== null && !(blessedRecord !== undefined && saidGoodbye(blessedRecord))) {
384
+ const record = blessedRecord;
385
+ if (record?.route === 'no-project') {
386
+ // The tab is live but on the launcher: retarget THAT tab instead of
387
+ // opening one beside it.
388
+ options.sendToTab(blessed, 'tab-adopt', { url: options.editorUrl });
389
+ return 'adopt';
390
+ }
391
+ // A PAGE THAT HAS NOT ATTACHED A COMMAND LISTENER THIS EPOCH IS NOT
392
+ // `focused`. `focused` is this endpoint's word for "converged — a tab
393
+ // is on this session and it answers", and the CLI reads it as exactly
394
+ // that (`tab-attach.ts`'s `planTabAttach`: `focused` -> `attached` ->
395
+ // "Editor already open on this project", exit 0).
396
+ //
397
+ // MEASURED 2026-09-19 against a page stalled before React mounts (the
398
+ // wedge in WORK.md §"The editor tab can wedge in a state `vgai edit`
399
+ // cannot self-heal"): for the WHOLE `listenerBudgetMs` — two minutes —
400
+ // this branch answered
401
+ //
402
+ // POST /__editor/tab/ensure -> {"status":"focused"}
403
+ // tab=025db401 connected=true channelThisEpoch=true
404
+ // listenerThisEpoch=false route=unknown epochAge=95349ms
405
+ //
406
+ // for a tab that could never run a command, and `tab-refocus` went to
407
+ // a page that buffers it for a consumer that is never coming (see the
408
+ // heal note above). That is the window the blind contributor probe hit
409
+ // three times in one session.
410
+ //
411
+ // `arriving` is the truthful word for it: the page-load is on its way
412
+ // in and the caller must WAIT for the document rather than be told it
413
+ // already has one. Past the budget the heal above turns it into
414
+ // `reloading`; both map to `wait-arriving`, and the CLI's wait now
415
+ // keys on the listener (`editor-sessions.ts`), so a page that never
416
+ // comes up ends in a named, non-zero failure instead of a lie.
417
+ if (record !== undefined && !record.listenerThisEpoch) return 'arriving';
418
+ options.sendToTab(blessed, 'tab-refocus', { url: options.editorUrl });
419
+ return 'focused';
420
+ }
421
+ if (tabArriving(state, at, options.lastIndexRequestAt(), config)) return 'arriving';
422
+ // A PRESENT TAB IS A TAB, blessed or not. Blessing is released the moment
423
+ // a page stops being eligible (`tab-presence.ts`'s `partitionEligible`),
424
+ // and reading "nothing blessed" as "no tab" is the same mistake the yield
425
+ // filter made — there it closed the session's only tab, here it would
426
+ // open a second one on top of it, which is the duplicate the bijection
427
+ // exists to forbid. The heal above is the door for a present tab that
428
+ // cannot run; waiting is the answer once it has been used.
429
+ // …and a tab that said goodbye is not present either, for the reason the
430
+ // blessed branch above states: `tabPresent` keeps a closed tab for its
431
+ // whole grace, which is the same window that made `vgai edit` report a
432
+ // tab it did not have.
433
+ if (presentTabs(state, at, config).some((tab) => !saidGoodbye(tab))) return 'arriving';
434
+ if (!open || !maintain) return 'noop';
435
+ // An explicit `vgai edit` is the ONLY thing that opens a tab. Nothing in
436
+ // the reconcile loop opens one: a tab you closed stays closed.
437
+ options.openUrl(options.editorUrl);
438
+ return 'opening';
439
+ },
440
+ expectRestart() {
441
+ restartExpected = true;
442
+ },
443
+ /**
444
+ * THE END OF THE SESSION IS A FACT THE PAGE HAS TO CONFIRM.
445
+ *
446
+ * This used to broadcast `tab-close`, sleep 150 ms blind and return, and
447
+ * the caller then tore the HTTP server down (`dev.ts`). Nothing waited for
448
+ * the message and nothing recorded whether it landed — and everything the
449
+ * page tears down on the session's end hangs off it: `tab-close` →
450
+ * `markSessionEnded` → every `editorHost().session.onEnded` terminator,
451
+ * `terminateBlenderRuntime` among them. Measured 2026-09-19/20: when the
452
+ * socket died before the event did, the page instead fell to the lease
453
+ * guard's `server-gone` path — main-thread timers Chrome throttles to once
454
+ * a minute in a long-hidden tab — and the tab's Blender engine worker (one
455
+ * engine thread plus a 16-thread pthread pool) stayed at 100%+ CPU for
456
+ * minutes after `vgai close`, with the box swapping.
457
+ *
458
+ * So the sleep is gone and a real wait takes its place: every tab PRESENT
459
+ * when the end was announced has to send a `session-ended` goodbye
460
+ * (`src/tab-lifecycle-client.ts`, the same beacon route `pagehide` uses),
461
+ * bounded at {@link SESSION_END_ACK_BUDGET_MS}. The bound is the point —
462
+ * a page that cannot answer must not hold the shutdown open — and the
463
+ * journal says which of the two happened, so `vgai close` can print it
464
+ * instead of the operator having to find the CPU themselves.
465
+ */
466
+ async notifySessionEnded() {
467
+ controller.stop();
468
+ if (restartExpected) return;
469
+ const awaiting = presentTabs(state, now(), config).map((tab) => tab.tabId);
470
+ const startedAt = now();
471
+ const unacked = (): string[] => awaiting.filter((id) => !tabEndAcknowledged(state, id));
472
+ const journalUnacked = (missing: readonly string[]): void => {
473
+ journal({
474
+ kind: 'session-end-unacked',
475
+ tabId8s: missing.map((id) => id.slice(0, 8)),
476
+ ms: now() - startedAt,
477
+ });
478
+ };
479
+ if (options.broadcastToClients('tab-close', {}) === 0) {
480
+ // Nobody took the message, so no ack can ever come — waiting the
481
+ // budget out would only delay the exit. Say so at once when there was
482
+ // a tab to tell; a session with no tabs has nothing to record.
483
+ const missing = unacked();
484
+ if (missing.length > 0) journalUnacked(missing);
485
+ return;
486
+ }
487
+ for (;;) {
488
+ const missing = unacked();
489
+ if (missing.length === 0) {
490
+ journal({
491
+ kind: 'session-end-acked',
492
+ tabs: awaiting.length,
493
+ ms: now() - startedAt,
494
+ });
495
+ return;
496
+ }
497
+ if (now() - startedAt >= SESSION_END_ACK_BUDGET_MS) {
498
+ journalUnacked(missing);
499
+ return;
500
+ }
501
+ await new Promise((resolve) => setTimeout(resolve, SESSION_END_ACK_POLL_MS));
502
+ }
503
+ },
504
+ tick() {
505
+ run();
506
+ },
507
+ stop() {
508
+ if (timer !== null) {
509
+ clearInterval(timer);
510
+ timer = null;
511
+ }
512
+ },
513
+ blessedTabId() {
514
+ return state.blessedTabId;
515
+ },
516
+ present() {
517
+ return presentTabs(state, now(), config);
518
+ },
519
+ unresponsive() {
520
+ const at = now();
521
+ return presentTabs(state, at, config).filter((tab) => tabUnresponsive(tab, at, config));
522
+ },
523
+ report() {
524
+ return tabPresenceReport(state, now(), config);
525
+ },
526
+ departedReport() {
527
+ return departedTabReport(state, now(), config);
528
+ },
529
+ tab(tabId) {
530
+ return state.tabs.get(tabId);
531
+ },
532
+ censusOf(tabId) {
533
+ const record = state.tabs.get(tabId);
534
+ const newest = record === undefined ? null : latestCensus(record);
535
+ if (newest === null) return null;
536
+ const { at, ...census } = newest;
537
+ return { census, ageMs: now() - at };
538
+ },
539
+ state() {
540
+ return state;
541
+ },
542
+ };
543
+
544
+ if (intervalMs > 0) {
545
+ timer = setInterval(() => controller.tick(), intervalMs);
546
+ timer.unref?.();
547
+ }
548
+
549
+ return controller;
550
+ }