@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,830 @@
1
+ /**
2
+ * The editor session's console stream, as a STRUCTURED FILE an agent can read
3
+ * at any moment: `<project>/logs/editor-<sessionstamp>.jsonl`.
4
+ *
5
+ * WHY. Agents do not watch terminals; they read files. Every discipline signal
6
+ * the editor server already produces — a save that failed validation, a
7
+ * build-discipline tripwire crossing (`build-discipline.ts`), the session's own
8
+ * lifecycle — was delivered ONLY as a terminal print, which in detach mode goes
9
+ * to a raw capture nobody parses and in foreground scrolls past whoever was not
10
+ * looking. Same measured shape as the tripwires' own origin story: the
11
+ * mechanism was right and the delivery assumption was false.
12
+ *
13
+ * SO THE JOURNAL IS THE RECORD AND THE PRINTS ARE ITS RENDERERS. Every caller
14
+ * emits the event here first and renders second, from one emit point, so no
15
+ * line can reach a terminal without a durable twin on disk. Nothing new is
16
+ * measured for the journal's sake: each event is a fact one of those surfaces
17
+ * was about to print anyway.
18
+ *
19
+ * IT LIVES BESIDE `logs/play-*.jsonl`, in the project, deliberately: that is
20
+ * the established idiom for "durable evidence this session produced", the
21
+ * scaffold already gitignores `logs/`, and `build-discipline.ts` already skips
22
+ * `logs/` when dating source (so a journal line can never read as a source
23
+ * change and make a tripwire cry wolf on its own output).
24
+ *
25
+ * SCHEMA-LIGHT ON PURPOSE. One discriminated union, one writer, no zod: nothing
26
+ * reads a journal back through a validator — an agent reads it, and `JSON.parse`
27
+ * per line is the whole contract. Append-only, one object per line, no levels,
28
+ * no transports, no config.
29
+ *
30
+ * BOUNDS. Same rule the play logs follow (`/__editor/log-session`): prune to the
31
+ * newest `MAX_JOURNAL_FILES` when a new one is opened. Within a session the file
32
+ * grows unbounded, which is what an append-only record means.
33
+ */
34
+
35
+ import { appendFileSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
36
+ import { join } from 'node:path';
37
+ import type { TripwireTier } from './build-discipline';
38
+ import type { BlenderTabMetrics, RecordedTabCensus } from '@volter/editor-sdk/project/tab-census';
39
+
40
+ /** `editor-` + an ISO instant with `:`/`.` flattened + `.jsonl` — the same
41
+ * lexicographic-order-is-chronological-order shape `play-*.jsonl` uses. */
42
+ const PREFIX = 'editor-';
43
+ const SUFFIX = '.jsonl';
44
+
45
+ /** Journals kept per project. Matches the play logs' own `MAX_LOG_FILES`. */
46
+ const MAX_JOURNAL_FILES = 20;
47
+
48
+ /**
49
+ * One journal line's payload. `at` (ISO) is added by the writer, so a caller
50
+ * only ever describes WHAT happened.
51
+ *
52
+ * The tripwire arm reuses `build-discipline.ts`'s vocabulary verbatim — its
53
+ * `TripwireTier`, its tripwire names, and the same inputs its banners are
54
+ * computed from — so the JSONL and the printed banner can never disagree about
55
+ * what fired or how loud it was.
56
+ */
57
+ export type SessionJournalEvent =
58
+ /** This server began serving this project (the process's own start). */
59
+ | { readonly kind: 'session-started'; readonly project: string; readonly pid: number }
60
+ /** `POST /__editor/open-project` switched the live session's project. */
61
+ | {
62
+ readonly kind: 'project-opened';
63
+ readonly project: string;
64
+ readonly previousProject: string | null;
65
+ }
66
+ /** The session is going down (best-effort: a SIGKILL logs nothing). */
67
+ | { readonly kind: 'session-shutdown' }
68
+ /**
69
+ * ONE RESOURCE THE SHUTDOWN COULD NOT STOP, by name.
70
+ *
71
+ * The host's shutdown list runs each task independently and bounds each one
72
+ * (`packages/editor/server/process-shutdown.ts`); this is what a task writes
73
+ * when its bound expires or it throws. Measured 2026-09-21 under load average
74
+ * ~30: a `vgai close` died inside the tab notify before the workbench's
75
+ * children were stopped, and the ONLY evidence was a journal that stopped —
76
+ * no `session-shutdown` line, a Code-OSS server orphaned at PPID 1 on its
77
+ * reserved port, and the next `vgai edit` refusing by name. A silence names
78
+ * nothing; this line names the resource.
79
+ */
80
+ | {
81
+ readonly kind: 'session-shutdown-task';
82
+ readonly task: string;
83
+ readonly outcome: 'timeout' | 'failed';
84
+ readonly ms: number;
85
+ readonly detail?: string;
86
+ }
87
+ /**
88
+ * WHO HEARD THE SESSION END. Written by `tab-lifecycle.ts`'s
89
+ * `notifySessionEnded` after it broadcasts `tab-close` and WAITS (bounded at
90
+ * 2s) for every tab that was present at that moment to acknowledge it.
91
+ *
92
+ * Measured 2026-09-19/20: the broadcast used to be followed by a blind
93
+ * 150 ms sleep and the HTTP server coming down, so nothing ever recorded
94
+ * whether a page received it. A page that did not kept its Blender engine
95
+ * worker — one engine thread plus a 16-thread pthread pool — at 100%+ CPU
96
+ * for minutes after `vgai close`, and the box swapped. These two lines are
97
+ * the difference, and `vgai close` prints whichever one it finds.
98
+ */
99
+ | { readonly kind: 'session-end-acked'; readonly tabs: number; readonly ms: number }
100
+ /** …and the other outcome: these tabs never said they got it, so whatever
101
+ * their pages hold is still running. */
102
+ | {
103
+ readonly kind: 'session-end-unacked';
104
+ readonly tabId8s: readonly string[];
105
+ readonly ms: number;
106
+ }
107
+ /**
108
+ * WHERE EDITOR BOOT TIME WENT — written ONCE, when the first page proves it
109
+ * is RUNNING the editor document (its command listener attaches), which is
110
+ * the honest end of the wait `vgai edit` narrates as "Vite cold start".
111
+ *
112
+ * The gap between "Editor ready at …" and that line is the largest stage of
113
+ * opening a project and nothing used to record it: on an imported Unity port
114
+ * it measured 83–139s against a template's 5s, and finding out why took a
115
+ * hand-rolled request trace plus a V8 CPU profile of the dev server. Every
116
+ * field here is a fact the host already had.
117
+ *
118
+ * READ `loopDelayP99Ms` FIRST. It is what separates "the server was busy" —
119
+ * where every slow response in the window is queueing, not work — from "the
120
+ * server was idle and the browser was the wait". See
121
+ * `packages/editor/server/boot-timings.ts`.
122
+ */
123
+ | {
124
+ readonly kind: 'boot';
125
+ /** Process start → Vite's `createServer` resolved. */
126
+ readonly viteMs: number;
127
+ /** …→ the dependency scan settled. */
128
+ readonly depScanMs: number;
129
+ /** …→ HTTP listening (what "Editor ready at" announces). */
130
+ readonly listenMs: number;
131
+ /** Listening → the first page running the editor document. */
132
+ readonly boundMs: number;
133
+ /** Process start → the declared world warmup finished; `null` if none ran. */
134
+ readonly worldWarmupDoneMs: number | null;
135
+ readonly loopDelayP99Ms: number;
136
+ readonly loopDelayMaxMs: number;
137
+ }
138
+ /** A save-validation verdict — the same facts `runFileValidation` reports. */
139
+ | {
140
+ readonly kind: 'validation';
141
+ /** Project-relative, forward-slash. */
142
+ readonly path: string;
143
+ /** The validatable kind the server classified this file as. */
144
+ readonly fileKind: string;
145
+ readonly ok: boolean;
146
+ readonly errors?: readonly string[];
147
+ readonly warnings?: readonly string[];
148
+ }
149
+ /** A build-discipline tripwire CROSSED a step (never a repeat at one tier). */
150
+ | {
151
+ readonly kind: 'tripwire';
152
+ readonly tripwire: 'commit-cadence';
153
+ readonly tier: TripwireTier;
154
+ readonly ageMs: number;
155
+ readonly fileCount: number;
156
+ }
157
+ | {
158
+ readonly kind: 'tripwire';
159
+ readonly tripwire: 'unplayed-session';
160
+ readonly tier: TripwireTier;
161
+ readonly servingForMs: number;
162
+ }
163
+ /**
164
+ * A play-mode log session opened or closed.
165
+ *
166
+ * `name` is the run's OPTIONAL slug (`vgai play --name <text>`), `null` for
167
+ * an unnamed run — the same slug that goes in the log filename, so grepping
168
+ * the journal for a run and listing `logs/` for it are the same question.
169
+ */
170
+ | {
171
+ readonly kind: 'play';
172
+ readonly action: 'start' | 'stop';
173
+ readonly name: string | null;
174
+ readonly logFile: string | null;
175
+ }
176
+ /**
177
+ * A play run's VIDEO evidence opened or landed.
178
+ *
179
+ * Every `vgai play` records — there is no flag — so this row is the answer
180
+ * to "where is the footage of that run", and it has to be in the journal
181
+ * rather than only in a terminal ack: the runs whose footage matters most
182
+ * are the unattended ones, whose ack nobody read.
183
+ *
184
+ * `reason` is what ENDED the recording (`'idle-autostop'`, `'stop'`), null
185
+ * on the `started` row. `rotates` says whether the next play overwrites this
186
+ * file — the unnamed clip does, a `--record <name>` keepsake does not.
187
+ */
188
+ | {
189
+ readonly kind: 'play-recording';
190
+ readonly action: 'started' | 'finalized';
191
+ /** Basename under the project's `.vgai/recordings/`. */
192
+ readonly file: string;
193
+ readonly rotates: boolean;
194
+ readonly reason: string | null;
195
+ }
196
+ /**
197
+ * A relayed command ran out of budget, and WHICH PLAY-BOOT STEP the page was
198
+ * inside when it did.
199
+ *
200
+ * The row exists because its absence was the defect. Measured 2026-08-20 at
201
+ * N=20000 on the canvas lane: `play` (120s), `screenshot` (15s) and `stop`
202
+ * (30s) all expired against a tab whose WORKER heartbeat kept beating at
203
+ * 0.4s, and the journal's whole account of it was three `command-result`
204
+ * lines saying "the tab is present … and did not respond" — true, and about
205
+ * the tab rather than the page. `phase` is the page's own last word before it
206
+ * went quiet (`editor/src/play-boot-phase.ts`); `null` means no play boot was
207
+ * in flight, which is itself an answer.
208
+ */
209
+ | {
210
+ readonly kind: 'play-stall';
211
+ readonly command: string;
212
+ readonly requestId8: string;
213
+ readonly phase: string | null;
214
+ /** How long the page had been in that phase. `null` with a null phase. */
215
+ readonly phaseAgeMs: number | null;
216
+ /** The budget the command actually waited out. */
217
+ readonly waitedMs: number;
218
+ }
219
+ /**
220
+ * THE TRANSPORT ARM. Everything below is one line per transport or
221
+ * presence fact, and it exists because the journal was BLIND to all of
222
+ * them: on 2026-08-09 a session refused five `vgai play` commands against
223
+ * a healthy connected tab and the whole file it left behind was
224
+ * `session-started` plus validation lines. A relay that can refuse has to
225
+ * be able to say who it refused, to which tab, and on what evidence.
226
+ *
227
+ * Ids are truncated to 8 characters (`clientId8`, `tabId8`, `requestId8`)
228
+ * — enough to correlate lines within one session, short enough that a
229
+ * human can scan a column of them. No payload bodies, ever: a state
230
+ * snapshot or a command argument list would turn an append-only record
231
+ * into a memory dump.
232
+ */
233
+ /** A control connection opened (either transport). */
234
+ | {
235
+ readonly kind: 'client-connected';
236
+ readonly clientId8: string;
237
+ readonly transport: 'ws' | 'sse';
238
+ readonly participant: string | null;
239
+ }
240
+ /** A control connection closed, and how many pending commands it settled. */
241
+ | {
242
+ readonly kind: 'client-disconnected';
243
+ readonly clientId8: string;
244
+ readonly transport: 'ws' | 'sse';
245
+ /** WebSocket close code, or null for SSE (which has none). */
246
+ readonly code: number | null;
247
+ readonly reason: string | null;
248
+ readonly commandsSettled: number;
249
+ }
250
+ /** The server granted a socket the duplex control capability. */
251
+ | { readonly kind: 'duplex-granted'; readonly clientId8: string }
252
+ /** A page proved it is speaking the exact generation the server granted. */
253
+ | {
254
+ readonly kind: 'control-lifecycle-confirmed';
255
+ readonly clientId8: string;
256
+ readonly connectionGeneration8: string;
257
+ }
258
+ /** A stale or contradictory control frame was refused. */
259
+ | {
260
+ readonly kind: 'control-lifecycle-rejected';
261
+ readonly clientId8: string;
262
+ readonly mismatch:
263
+ | 'serverGeneration'
264
+ | 'connectionGeneration'
265
+ | 'clientId'
266
+ | 'tabId'
267
+ | 'pageGeneration';
268
+ }
269
+ /** A command left the relay for one tab's command channel. */
270
+ | {
271
+ readonly kind: 'command-relayed';
272
+ readonly command: string;
273
+ readonly requestId8: string;
274
+ readonly tabId8: string | null;
275
+ readonly clientId8: string | null;
276
+ }
277
+ /** The tab said its command listener PICKED THE COMMAND UP. */
278
+ | { readonly kind: 'command-receipt'; readonly requestId8: string }
279
+ /**
280
+ * The last tab's connection went away while UNRECEIPTED commands were still
281
+ * queued, and this many were swept. Receipted ones are deliberately not
282
+ * counted here — they keep their own work budget.
283
+ */
284
+ | { readonly kind: 'command-swept-on-last-tab-gone'; readonly settled: number }
285
+ /** The command settled — by the tab's own answer or by the relay refusing. */
286
+ | {
287
+ readonly kind: 'command-result';
288
+ readonly requestId8: string;
289
+ readonly ok: boolean;
290
+ /** Present only when `ok` is false; the refusal/failure text. */
291
+ readonly error?: string;
292
+ }
293
+ /**
294
+ * The relay HELD a command instead of refusing it: the target tab is
295
+ * present by heartbeat but its command channel is not carrying right now
296
+ * (mid-reload, or a channel that has not answered an echo).
297
+ */
298
+ | {
299
+ readonly kind: 'command-held';
300
+ readonly requestId8: string;
301
+ readonly tabId8: string;
302
+ readonly reason: 'no-channel' | 'unacknowledged';
303
+ }
304
+ /** The main-thread echo probe over a duplex socket. */
305
+ | {
306
+ readonly kind: 'echo-probe';
307
+ readonly clientId8: string;
308
+ readonly answered: boolean;
309
+ readonly waitedMs: number;
310
+ }
311
+ /** The blessed tab changed (or was chosen for the first time). */
312
+ | {
313
+ readonly kind: 'tab-blessed';
314
+ readonly tabId8: string;
315
+ readonly previousTabId8: string | null;
316
+ readonly reason: 'sticky' | 'oldest' | 'claimed';
317
+ }
318
+ /** An extra tab was told to yield. */
319
+ | { readonly kind: 'tab-yielded'; readonly tabId8: string }
320
+ /**
321
+ * PRESENCE, IN TAB VOCABULARY. These five say what the tab table saw, and
322
+ * they deliberately do NOT use socket words: a socket closing is not a tab
323
+ * leaving, and that conflation is what produced "editor tab lost —
324
+ * reopening" against a tab that had never gone anywhere.
325
+ */
326
+ /** A tabId beat for the first time. */
327
+ | {
328
+ readonly kind: 'tab-appeared';
329
+ readonly tabId8: string;
330
+ readonly visibility: 'visible' | 'hidden';
331
+ }
332
+ /** Beats stopped arriving for longer than the notice threshold. */
333
+ | { readonly kind: 'tab-heartbeat-gap'; readonly tabId8: string; readonly sinceMs: number }
334
+ /** A gap closed — the same tab resumed beating. `gapMs` is its full length. */
335
+ | { readonly kind: 'tab-gap-closed'; readonly tabId8: string; readonly gapMs: number }
336
+ /** Same tabId, new epoch: the page reloaded. The tab never left. */
337
+ | { readonly kind: 'tab-reloaded'; readonly tabId8: string; readonly epochCount: number }
338
+ /** The page announced main-thread work before starting it (a play-boot
339
+ * step, a model's `building src/models/x.ts`), or cleared it (`null`). */
340
+ | { readonly kind: 'page-phase'; readonly phase: string | null }
341
+ /**
342
+ * What the project's source watcher saw: one line per chokidar event under
343
+ * `src/` (`add`/`change`/`unlink`), with the file's mtime as the watcher
344
+ * found it (`null` when the path was gone). The instrument for "why did
345
+ * the page reload / the table refresh" — a reload on a file whose mtime
346
+ * never moved is a phantom event, and this is where it shows.
347
+ */
348
+ | {
349
+ readonly kind: 'src-watch';
350
+ readonly event: string;
351
+ readonly path: string;
352
+ readonly mtimeMs: number | null;
353
+ }
354
+ /** Absent past its grace: this tab is gone. */
355
+ | { readonly kind: 'tab-departed'; readonly tabId8: string; readonly absentMs: number }
356
+ /**
357
+ * WHAT THE TAB WAS HOLDING WHEN IT DIED WITHOUT A GOODBYE.
358
+ *
359
+ * Emitted beside `client-disconnected` on an ABNORMAL close (1006 — the
360
+ * socket ended with no close frame, which is what a killed renderer process
361
+ * leaves behind; an ordinary tab close sends one). Measured 2026-08-10: a
362
+ * game tab's renderer was killed repeatedly at Chrome's undocumented
363
+ * per-process ceiling and the whole session record said `1006` and nothing
364
+ * else — the recovery worked perfectly and the CAUSE was unrecorded.
365
+ *
366
+ * `census` is the tab's last resource profile and `censusAgeMs` how stale it
367
+ * was; both null for a tab that never reported one (an older page, a
368
+ * tunnelled tab). Numbers only — this stays a journal line, not a dump.
369
+ */
370
+ | {
371
+ readonly kind: 'tab-death-profile';
372
+ readonly tabId8: string;
373
+ /** The abnormal close code that triggered the line. */
374
+ readonly code: number;
375
+ readonly censusAgeMs: number | null;
376
+ /** {@link RecordedTabCensus} rather than `TabCensus`: the writer files a
377
+ * full census, but a line READ back may have been written before the
378
+ * mount census existed. */
379
+ readonly census: RecordedTabCensus | null;
380
+ }
381
+ /** Two epochs beating under one tabId — "Duplicate Tab" copied sessionStorage. */
382
+ | { readonly kind: 'tab-duplicated'; readonly tabId8: string }
383
+ /**
384
+ * The tab is present but its PAGE never came up this page-load. Presence
385
+ * proves the tab exists, not that the document works — a main thread that
386
+ * died after the inline bootstrap keeps beating and holding its control
387
+ * connection, and can run nothing. Such a tab is passed over for blessing
388
+ * and named in the refusal.
389
+ *
390
+ * `reason` says which stage it is stuck at: `'no-channel'` (the page never
391
+ * opened a control connection at all) or `'no-command-listener'` (it did,
392
+ * and the module graph behind it never produced a listener).
393
+ */
394
+ | {
395
+ readonly kind: 'tab-unresponsive';
396
+ readonly tabId8: string;
397
+ readonly reason: 'no-channel' | 'no-command-listener';
398
+ readonly unresponsiveForMs: number;
399
+ }
400
+ /**
401
+ * An uncaught error or unhandled rejection in a tab's PAGE, captured by
402
+ * `index.html`'s inline bootstrap — before the module graph, so it is
403
+ * recorded even when the boot that would have reported it is the thing that
404
+ * died. This is the line that says WHY a `tab-unresponsive` tab is
405
+ * unresponsive.
406
+ */
407
+ | {
408
+ readonly kind: 'page-error';
409
+ readonly tabId8: string;
410
+ readonly message: string;
411
+ }
412
+ /**
413
+ * One batch of occurrences of ONE console error/warning condition, as the
414
+ * server's unresolved-console ledger recorded it
415
+ * (`packages/editor/server/console-ledger.ts`).
416
+ *
417
+ * This is the DURABLE half of the loudness convention, and the reason it is
418
+ * a row per observation rather than a row per distinct message: `count` is
419
+ * the running total after this batch, so the file answers "how many times
420
+ * did this actually fire, and when did it stop" long after the ledger (and
421
+ * the tab, and the server) are gone. A repeat is never filtered out here —
422
+ * deduping repeats to silence is precisely the failure that made a session's
423
+ * eleven errors read as one line and then nothing.
424
+ */
425
+ | {
426
+ readonly kind: 'console-entry';
427
+ readonly id: string;
428
+ readonly severity: 'error' | 'warn';
429
+ readonly source: string | null;
430
+ /** Occurrences in THIS batch. */
431
+ readonly added: number;
432
+ /** Running total for this condition, across every page-load. */
433
+ readonly count: number;
434
+ readonly message: string;
435
+ }
436
+ /** A condition cleared by the honest re-test: the page reloaded and it did
437
+ * not recur. `count` is what it reached before it stopped. */
438
+ | {
439
+ readonly kind: 'console-retired';
440
+ readonly id: string;
441
+ readonly severity: 'error' | 'warn';
442
+ readonly count: number;
443
+ readonly message: string;
444
+ }
445
+ /** A condition waved through BY NAME (`vgai console ack`). The audit row is
446
+ * the whole point: an acknowledgment records who and why, and never erases
447
+ * what was acknowledged. */
448
+ | {
449
+ readonly kind: 'console-ack';
450
+ readonly id: string;
451
+ readonly severity: 'error' | 'warn';
452
+ readonly count: number;
453
+ readonly by: string;
454
+ readonly reason: string;
455
+ readonly message: string;
456
+ }
457
+ /** A condition cleared by its OWNER, which proved it gone by an event the
458
+ * reload rule cannot see — a play remount resolving its own
459
+ * `Restart required` warning. `by` names that owner, never a person. */
460
+ | {
461
+ readonly kind: 'console-resolved';
462
+ readonly id: string;
463
+ readonly severity: 'error' | 'warn';
464
+ readonly count: number;
465
+ readonly by: string;
466
+ readonly message: string;
467
+ };
468
+
469
+ /** A parsed journal line: the event plus when it was appended. */
470
+ export type SessionJournalLine = SessionJournalEvent & { readonly at: string };
471
+
472
+ /** An open journal. `append` never throws — a journal that breaks a save would
473
+ * be worse than a journal that misses a line. */
474
+ export interface SessionJournal {
475
+ /** Absolute path of the file being appended to. */
476
+ readonly path: string;
477
+ append(event: SessionJournalEvent): void;
478
+ }
479
+
480
+ /** `editor-2026-08-09T12-34-56-789Z.jsonl` for a given instant. */
481
+ export function sessionJournalFilename(startedAt: Date): string {
482
+ return `${PREFIX}${startedAt.toISOString().replace(/[:.]/g, '-')}${SUFFIX}`;
483
+ }
484
+
485
+ /**
486
+ * Open (create) this session's journal under `<projectRoot>/logs/`.
487
+ *
488
+ * `null` when the directory cannot be created or the first write fails —
489
+ * an unwritable project must degrade to "no journal", never to a crashed boot.
490
+ */
491
+ export function openSessionJournal(
492
+ projectRoot: string,
493
+ startedAt: Date = new Date(),
494
+ ): SessionJournal | null {
495
+ const logsDir = join(projectRoot, 'logs');
496
+ try {
497
+ mkdirSync(logsDir, { recursive: true });
498
+ } catch {
499
+ return null;
500
+ }
501
+ pruneJournals(logsDir);
502
+ const path = join(logsDir, sessionJournalFilename(startedAt));
503
+ const journal: SessionJournal = {
504
+ path,
505
+ append(event) {
506
+ const line: SessionJournalLine = { at: new Date().toISOString(), ...event };
507
+ try {
508
+ appendFileSync(path, `${JSON.stringify(line)}\n`, 'utf-8');
509
+ } catch {
510
+ /* a journal that breaks the caller is worse than a missing line */
511
+ }
512
+ },
513
+ };
514
+ try {
515
+ appendFileSync(path, '', 'utf-8');
516
+ } catch {
517
+ return null;
518
+ }
519
+ return journal;
520
+ }
521
+
522
+ /** Drop the oldest journals so opening one more stays at `MAX_JOURNAL_FILES`. */
523
+ function pruneJournals(logsDir: string): void {
524
+ try {
525
+ const files = readdirSync(logsDir)
526
+ .filter((f) => f.startsWith(PREFIX) && f.endsWith(SUFFIX))
527
+ .sort();
528
+ for (const f of files.slice(0, Math.max(0, files.length - MAX_JOURNAL_FILES + 1))) {
529
+ try {
530
+ unlinkSync(join(logsDir, f));
531
+ } catch {
532
+ /* already gone */
533
+ }
534
+ }
535
+ } catch {
536
+ /* no logs dir yet */
537
+ }
538
+ }
539
+
540
+ /**
541
+ * The newest journal in a project, or `null`.
542
+ *
543
+ * File-native on purpose (the same shape as the CLI's newest-play-log reader):
544
+ * the journal path is a fact about the PROJECT DIRECTORY, so `vgai status` and
545
+ * the boot pointer can name it without a server round-trip — and can still name
546
+ * it after the session that wrote it is gone.
547
+ */
548
+ export function newestSessionJournal(projectRoot: string): string | null {
549
+ try {
550
+ const files = readdirSync(join(projectRoot, 'logs'))
551
+ .filter((f) => f.startsWith(PREFIX) && f.endsWith(SUFFIX))
552
+ .sort();
553
+ const newest = files.at(-1);
554
+ return newest ? join(projectRoot, 'logs', newest) : null;
555
+ } catch {
556
+ return null;
557
+ }
558
+ }
559
+
560
+ /**
561
+ * The newest journal's last `limit` lines of the requested kinds, oldest first.
562
+ *
563
+ * WHY A READER EXISTS AT ALL. The journal was written as a file an agent
564
+ * "can read at any time", and the measured answer to that invitation was: it
565
+ * doesn't. The foundry probe's journal held the notice-tier tripwire crossing
566
+ * that predicted its own end-of-run batch commit, and nothing that agent ran
567
+ * ever rendered a line of it. So the record grows a RENDERER on the command
568
+ * agents already poll (`vgai status`) — same principle as the tripwires' own
569
+ * origin: the mechanism was right and the delivery assumption was false.
570
+ *
571
+ * Every failure is the same case — no project, no journal, unreadable file, a
572
+ * line that will not parse — and it is the empty list, never a throw. A status
573
+ * command that crashed on a malformed log line would be a worse outcome than
574
+ * one that says nothing about it.
575
+ *
576
+ * The whole file is read, which is what bounds this: a journal is one editor
577
+ * session's surface-worthy transitions (the happy-path silence rule keeps
578
+ * clean saves out), the pruner caps how many exist, and the alternative — a
579
+ * reverse streaming reader over a file measured in kilobytes — is machinery
580
+ * with nothing to buy.
581
+ */
582
+ export function readRecentJournalEvents(
583
+ projectRoot: string,
584
+ kinds: readonly SessionJournalEvent['kind'][],
585
+ limit: number,
586
+ ): SessionJournalLine[] {
587
+ const path = newestSessionJournal(projectRoot);
588
+ if (path === null || limit <= 0) return [];
589
+ let raw: string;
590
+ try {
591
+ raw = readFileSync(path, 'utf-8');
592
+ } catch {
593
+ return [];
594
+ }
595
+ const wanted = new Set<string>(kinds);
596
+ const matched: SessionJournalLine[] = [];
597
+ for (const line of raw.split('\n')) {
598
+ if (line === '') continue;
599
+ let parsed: SessionJournalLine;
600
+ try {
601
+ parsed = JSON.parse(line) as SessionJournalLine;
602
+ } catch {
603
+ continue; // a torn final line is not a reason to report nothing
604
+ }
605
+ if (parsed && typeof parsed === 'object' && wanted.has(parsed.kind)) matched.push(parsed);
606
+ }
607
+ return matched.slice(-limit);
608
+ }
609
+
610
+ /**
611
+ * One journal line as a scannable terminal line: `HH:MM:SS`, the kind, and
612
+ * the few facts that line is about.
613
+ *
614
+ * ONE OWNER of this rendering, for `sessionJournalPointerLine`'s reason — the
615
+ * events already have exactly one wording as data, and a second prose form per
616
+ * caller is how a reader ends up believing there are two records.
617
+ */
618
+ /** Split out of {@link formatJournalLine}'s switch for the same reason as
619
+ * {@link playRecordingLine} below. Seconds, because that is the unit the
620
+ * reader's complaint is in ("opening this project takes two minutes"), except
621
+ * the loop-delay pair — a p99 is a millisecond fact and rounding it to
622
+ * seconds would erase the only number that says WHO was waiting. */
623
+ function bootLine(line: Extract<SessionJournalEvent, { kind: 'boot' }>): string {
624
+ const s = (ms: number): string => `${Math.round(ms / 100) / 10}s`;
625
+ return (
626
+ `boot ready ${s(line.listenMs)} (vite ${s(line.viteMs)}, dep-scan ${s(line.depScanMs)}) ` +
627
+ `→ page bound +${s(line.boundMs)}` +
628
+ (line.worldWarmupDoneMs === null ? '' : `, world warmup done ${s(line.worldWarmupDoneMs)}`) +
629
+ ` — loop delay p99 ${line.loopDelayP99Ms}ms / max ${line.loopDelayMaxMs}ms`
630
+ );
631
+ }
632
+
633
+ /** Split out of {@link formatJournalLine}'s switch so this row's three
634
+ * conditionals do not push that already-long function further over the
635
+ * complexity bound. */
636
+ function playRecordingLine(line: Extract<SessionJournalEvent, { kind: 'play-recording' }>): string {
637
+ const why = line.reason === null ? '' : ` (${line.reason})`;
638
+ const life = line.rotates ? ' — replaced by the next play' : ' — kept';
639
+ return `play-recording ${line.action} .vgai/recordings/${line.file}${why}${life}`;
640
+ }
641
+
642
+ export function formatJournalLine(line: SessionJournalLine): string {
643
+ const at = line.at.slice(11, 19);
644
+ switch (line.kind) {
645
+ case 'tripwire':
646
+ return line.tripwire === 'commit-cadence'
647
+ ? `journal: ${at} tripwire commit-cadence ${line.tier} (${line.fileCount} files, ${Math.round(line.ageMs / 60_000)}m)`
648
+ : `journal: ${at} tripwire unplayed-session ${line.tier} (${Math.round(line.servingForMs / 60_000)}m)`;
649
+ case 'validation':
650
+ return `journal: ${at} validation ${line.ok ? 'ok' : 'FAILED'} ${line.path}`;
651
+ case 'play':
652
+ return `journal: ${at} play ${line.action}${line.name === null ? '' : ` [${line.name}]`}`;
653
+ case 'play-recording':
654
+ return `journal: ${at} ${playRecordingLine(line)}`;
655
+ case 'play-stall':
656
+ return (
657
+ `journal: ${at} play-stall ${line.command} ${line.requestId8} after ` +
658
+ `${Math.round(line.waitedMs / 1000)}s — ` +
659
+ (line.phase === null
660
+ ? 'no play boot was in flight'
661
+ : `stuck in "${line.phase}" for ${Math.round((line.phaseAgeMs ?? 0) / 1000)}s`)
662
+ );
663
+ case 'session-started':
664
+ return `journal: ${at} session-started pid ${line.pid}`;
665
+ case 'project-opened':
666
+ return `journal: ${at} project-opened ${line.project}`;
667
+ case 'session-shutdown':
668
+ return `journal: ${at} session-shutdown`;
669
+ case 'session-shutdown-task':
670
+ return (
671
+ `journal: ${at} session-shutdown-task ${line.task} ${line.outcome} after ${line.ms}ms` +
672
+ (line.detail === undefined ? '' : ` — ${line.detail}`)
673
+ );
674
+ case 'session-end-acked':
675
+ return line.tabs === 0
676
+ ? `journal: ${at} session-end-acked no tab was present`
677
+ : `journal: ${at} session-end-acked ${line.tabs} tab(s) in ${line.ms}ms`;
678
+ case 'session-end-unacked':
679
+ return (
680
+ `journal: ${at} session-end-unacked ${line.tabId8s.join(' ')} after ${line.ms}ms — ` +
681
+ 'their pages may still hold what the session started'
682
+ );
683
+ case 'boot':
684
+ return `journal: ${at} ${bootLine(line)}`;
685
+ // The TRANSPORT arm. One short line each, and every one of them names the
686
+ // TAB or the request it is about — a column of ids is how these lines get
687
+ // correlated, and a paragraph per line is how a reader stops reading.
688
+ case 'client-connected':
689
+ return `journal: ${at} client-connected ${line.clientId8} (${line.transport})`;
690
+ case 'client-disconnected':
691
+ return `journal: ${at} client-disconnected ${line.clientId8} code ${line.code ?? '-'} settled ${line.commandsSettled}`;
692
+ case 'duplex-granted':
693
+ return `journal: ${at} duplex-granted ${line.clientId8}`;
694
+ case 'control-lifecycle-confirmed':
695
+ return `journal: ${at} control-lifecycle-confirmed ${line.clientId8} connection ${line.connectionGeneration8}`;
696
+ case 'control-lifecycle-rejected':
697
+ return `journal: ${at} control-lifecycle-rejected ${line.clientId8} stale ${line.mismatch}`;
698
+ case 'command-relayed':
699
+ return `journal: ${at} command-relayed ${line.command} ${line.requestId8} -> tab ${line.tabId8 ?? 'none'}`;
700
+ case 'command-receipt':
701
+ return `journal: ${at} command-receipt ${line.requestId8}`;
702
+ case 'command-swept-on-last-tab-gone':
703
+ return `journal: ${at} command-swept-on-last-tab-gone ${line.settled} unreceipted`;
704
+ case 'command-result':
705
+ return `journal: ${at} command-result ${line.requestId8} ${line.ok ? 'ok' : `FAILED ${line.error ?? ''}`}`;
706
+ case 'command-held':
707
+ return `journal: ${at} command-held ${line.requestId8} tab ${line.tabId8} (${line.reason})`;
708
+ case 'echo-probe':
709
+ return `journal: ${at} echo-probe ${line.clientId8} ${line.answered ? 'answered' : 'UNANSWERED'} in ${line.waitedMs}ms`;
710
+ case 'tab-blessed':
711
+ return `journal: ${at} tab-blessed ${line.tabId8} (${line.reason})`;
712
+ case 'tab-yielded':
713
+ return `journal: ${at} tab-yielded ${line.tabId8}`;
714
+ case 'tab-appeared':
715
+ return `journal: ${at} tab-appeared ${line.tabId8} (${line.visibility})`;
716
+ case 'tab-heartbeat-gap':
717
+ return `journal: ${at} tab-heartbeat-gap ${line.tabId8} ${Math.round(line.sinceMs / 100) / 10}s`;
718
+ case 'tab-gap-closed':
719
+ return `journal: ${at} tab-gap-closed ${line.tabId8} after ${Math.round(line.gapMs / 100) / 10}s`;
720
+ case 'tab-reloaded':
721
+ return `journal: ${at} tab-reloaded ${line.tabId8} (page load ${line.epochCount})`;
722
+ case 'page-phase':
723
+ return `journal: ${at} page-phase ${line.phase ?? '(clear)'}`;
724
+ case 'src-watch':
725
+ return `journal: ${at} src-watch ${line.event} ${line.path}${line.mtimeMs === null ? ' (gone)' : ''}`;
726
+ case 'tab-departed':
727
+ return `journal: ${at} tab-departed ${line.tabId8} absent ${Math.round(line.absentMs / 100) / 10}s`;
728
+ case 'tab-duplicated':
729
+ return `journal: ${at} tab-duplicated ${line.tabId8}`;
730
+ case 'tab-unresponsive':
731
+ return `journal: ${at} tab-unresponsive ${line.tabId8} ${line.reason} for ${Math.round(line.unresponsiveForMs / 1000)}s`;
732
+ case 'page-error':
733
+ return `journal: ${at} page-error ${line.tabId8} ${line.message}`;
734
+ case 'tab-death-profile':
735
+ return `journal: ${at} tab-death-profile ${line.tabId8} code ${line.code} — ${tabDeathProfileBody(line)}`;
736
+ // The CONSOLE arm. `count` is the running total for that condition, so a
737
+ // reader scanning the column sees a repeat climbing rather than the same
738
+ // line over and over with nothing to distinguish the fiftieth from the
739
+ // first. Every line leads with the ack id, because that is what a reader
740
+ // types next.
741
+ case 'console-entry':
742
+ return `journal: ${at} console-${line.severity} ${line.id} +${line.added} (total ${line.count}) ${line.source === null ? '' : `[${line.source}] `}${line.message.split('\n')[0]}`;
743
+ case 'console-retired':
744
+ return `journal: ${at} console-retired ${line.id} after ${line.count} — did not recur after reload`;
745
+ case 'console-ack':
746
+ return `journal: ${at} console-ack ${line.id} (×${line.count}) by ${line.by}: ${line.reason}`;
747
+ case 'console-resolved':
748
+ return `journal: ${at} console-resolved ${line.id} after ${line.count} — ${line.by} cleared the condition it raised`;
749
+ }
750
+ }
751
+
752
+ /** The census half of a `tab-death-profile` line, split out so the switch above
753
+ * stays one expression per case. */
754
+ function tabDeathProfileBody(line: SessionJournalEvent & { kind: 'tab-death-profile' }): string {
755
+ const census = formatTabCensus(line.census);
756
+ if (line.censusAgeMs === null) return census;
757
+ return `${census} (sampled ${Math.round(line.censusAgeMs / 100) / 10}s before)`;
758
+ }
759
+
760
+ /** The resource profile a `tab-death-profile` line and a `vgai status` tab row
761
+ * both quote. ONE wording, for `sessionJournalPointerLine`'s reason: two
762
+ * phrasings of the same numbers is how a reader ends up believing there are
763
+ * two measurements. Renderer counts appear only when the page could read them
764
+ * (no mounted render-debug adapter = the words are absent, never a zero). */
765
+ /**
766
+ * The Blender block's words: what the Blender worker and this tab's main thread
767
+ * have been doing. Absent entirely in a tab with no Blender session.
768
+ *
769
+ * `in flight` is the field that speaks during a wedge — the 2026-09-16 call
770
+ * that held the worker 1,800s produced no other number anywhere in the product.
771
+ * A field the page could not measure is OMITTED rather than printed as 0: no
772
+ * call yet, or no Long Tasks API (Safari/Firefox), is not "never stalled".
773
+ */
774
+ function formatBlenderMetrics(blender: BlenderTabMetrics): string {
775
+ const s = (ms: number): string => `${Math.round(ms / 100) / 10}s`;
776
+ const parts = [
777
+ blender.inFlightMs === null ? 'idle' : `IN FLIGHT ${s(blender.inFlightMs)}`,
778
+ // First after the wedge field, because it is the tab's LARGEST number and
779
+ // the one the heap line above is routinely mistaken for.
780
+ ...(blender.wasmMemoryMB === null || blender.wasmMemoryMB === undefined
781
+ ? []
782
+ : [`engine memory ${blender.wasmMemoryMB}MB`]),
783
+ ...(blender.lastCallMs === null ? [] : [`last call ${s(blender.lastCallMs)}`]),
784
+ ...(blender.maxCallMs === null ? [] : [`max ${s(blender.maxCallMs)}`]),
785
+ `over 5s ${blender.callsOver5s}`,
786
+ `over 30s ${blender.callsOver30s}`,
787
+ ...(blender.longestTaskMs === null
788
+ ? []
789
+ : [
790
+ `longest main-thread task ${s(blender.longestTaskMs)} (${blender.tasksOver100ms} over 100ms)`,
791
+ ]),
792
+ ...(blender.lastCallLongestTaskMs === null
793
+ ? []
794
+ : [`${s(blender.lastCallLongestTaskMs)} of that during the last call`]),
795
+ ];
796
+ return `blender: ${parts.join(', ')}`;
797
+ }
798
+
799
+ export function formatTabCensus(census: RecordedTabCensus | null): string {
800
+ if (census === null) return 'no resource census';
801
+ const parts = [
802
+ census.heapUsedMB === null
803
+ ? 'heap n/a'
804
+ : `heap ${census.heapUsedMB}MB${census.heapLimitMB === null ? '' : `/${census.heapLimitMB}MB`}`,
805
+ `canvas ${census.canvasMB}MB in ${census.canvases}`,
806
+ ];
807
+ if (census.mountEpochs !== undefined) parts.splice(1, 0, `mount epochs ${census.mountEpochs}`);
808
+ if (census.textures !== undefined) parts.push(`tex ${census.textures}`);
809
+ if (census.geometries !== undefined) parts.push(`geo ${census.geometries}`);
810
+ if (census.programs !== undefined) parts.push(`prog ${census.programs}`);
811
+ const line = parts.join(', ');
812
+ // On its own line: the Blender block answers a different question from the
813
+ // resource profile (is the tab ANSWERING, not what is it holding), and
814
+ // running the two together is how a reader stops seeing either.
815
+ return census.blender === undefined
816
+ ? line
817
+ : `${line}\n ${formatBlenderMetrics(census.blender)}`;
818
+ }
819
+
820
+ /**
821
+ * The ONE wording for "here is the journal" — printed by the editor server's
822
+ * boot block, by `vgai edit`'s ready/detach lines, and by `vgai status`.
823
+ *
824
+ * One owner of the sentence, for `build-discipline.ts`'s reason: three
825
+ * processes point at this file, and a second phrasing of the same pointer is
826
+ * how a reader ends up believing there are two things.
827
+ */
828
+ export function sessionJournalPointerLine(journalPath: string): string {
829
+ return `Session journal: ${journalPath} — structured JSONL, read it any time`;
830
+ }