@volter/sdk 0.0.0-stage → 0.5.203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (523) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +20 -0
  3. package/README.md +38 -3
  4. package/package.json +510 -4
  5. package/src/account.ts +210 -0
  6. package/src/chrome.ts +88 -0
  7. package/src/client.ts +1646 -0
  8. package/src/commands.ts +66 -0
  9. package/src/contributions.ts +619 -0
  10. package/src/css-numeric-style.ts +97 -0
  11. package/src/document-probe.ts +282 -0
  12. package/src/editor-view.ts +225 -0
  13. package/src/extension.ts +40 -0
  14. package/src/generations.ts +178 -0
  15. package/src/host.ts +1157 -0
  16. package/src/http-transport.browser.ts +14 -0
  17. package/src/http-transport.node.ts +19 -0
  18. package/src/index.ts +131 -0
  19. package/src/kit/CapabilityCoverageSection.tsx +185 -0
  20. package/src/kit/account-client.ts +333 -0
  21. package/src/kit/action-registry.ts +317 -0
  22. package/src/kit/active-product.ts +76 -0
  23. package/src/kit/active-project.ts +155 -0
  24. package/src/kit/adapter-editor-config.ts +25 -0
  25. package/src/kit/adapter-module.ts +7 -0
  26. package/src/kit/adapter-observation.ts +49 -0
  27. package/src/kit/animation/animation-clock.ts +479 -0
  28. package/src/kit/animation/stage-transport.ts +385 -0
  29. package/src/kit/api/assets.ts +365 -0
  30. package/src/kit/api/project-open.ts +355 -0
  31. package/src/kit/api/project-source.ts +180 -0
  32. package/src/kit/api/project-state.ts +110 -0
  33. package/src/kit/api/relay.ts +270 -0
  34. package/src/kit/api/themes.ts +45 -0
  35. package/src/kit/api-asset-library-wire.ts +45 -0
  36. package/src/kit/api-base.ts +10 -0
  37. package/src/kit/api-build.ts +99 -0
  38. package/src/kit/api-git-wire.ts +56 -0
  39. package/src/kit/api-logs.ts +92 -0
  40. package/src/kit/api-project-identity.ts +74 -0
  41. package/src/kit/api-settings.ts +36 -0
  42. package/src/kit/api-worktrees.ts +205 -0
  43. package/src/kit/asset-capabilities.ts +344 -0
  44. package/src/kit/asset-compare-core.ts +171 -0
  45. package/src/kit/asset-editor-context.tsx +101 -0
  46. package/src/kit/asset-events.ts +96 -0
  47. package/src/kit/asset-inspector-actions.ts +87 -0
  48. package/src/kit/asset-selection-viewer-registry.ts +113 -0
  49. package/src/kit/asset-selection.ts +146 -0
  50. package/src/kit/asset-thumbnails.ts +25 -0
  51. package/src/kit/asset-viewers.ts +115 -0
  52. package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
  53. package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
  54. package/src/kit/asset-workflow/asset-ledger.ts +156 -0
  55. package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
  56. package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
  57. package/src/kit/asset-workflow/asset-types.ts +142 -0
  58. package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
  59. package/src/kit/asset-workflow/audio-waveform.ts +22 -0
  60. package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
  61. package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
  62. package/src/kit/asset-workflow/image-view-scale.ts +31 -0
  63. package/src/kit/asset-workflow/import-contract.ts +124 -0
  64. package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
  65. package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
  66. package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
  67. package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
  68. package/src/kit/asset-workflow/project-content.ts +288 -0
  69. package/src/kit/asset-workflow/project-source-index.ts +545 -0
  70. package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
  71. package/src/kit/authoring/active-adapter.ts +200 -0
  72. package/src/kit/authoring/active-systems.ts +422 -0
  73. package/src/kit/authoring/adapter-key.ts +18 -0
  74. package/src/kit/authoring/authoring-asset-url.ts +27 -0
  75. package/src/kit/authoring/bootstrap-state.ts +49 -0
  76. package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
  77. package/src/kit/authoring/canvas-scene-guides.ts +84 -0
  78. package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
  79. package/src/kit/authoring/consumer-actions.ts +531 -0
  80. package/src/kit/authoring/design-time-layers.ts +852 -0
  81. package/src/kit/authoring/design-time-mount-registry.ts +244 -0
  82. package/src/kit/authoring/edit-mode-authoring.ts +637 -0
  83. package/src/kit/authoring/empty-project-authoring.ts +22 -0
  84. package/src/kit/authoring/instance-source-menu.ts +135 -0
  85. package/src/kit/authoring/layered-pick.ts +185 -0
  86. package/src/kit/authoring/mounted-root-subjects.ts +146 -0
  87. package/src/kit/authoring/no-authoring-adapter.ts +59 -0
  88. package/src/kit/authoring/object3d-document-persistence.ts +122 -0
  89. package/src/kit/authoring/panel-authoring.ts +121 -0
  90. package/src/kit/authoring/project-authoring-session.ts +105 -0
  91. package/src/kit/authoring/provenance.ts +99 -0
  92. package/src/kit/authoring/react-canvas-navigation.ts +259 -0
  93. package/src/kit/authoring/react-design-canvas-style.ts +20 -0
  94. package/src/kit/authoring/react-story-board.ts +917 -0
  95. package/src/kit/authoring/selection-scope.ts +195 -0
  96. package/src/kit/authoring/shell-document-ops.ts +169 -0
  97. package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
  98. package/src/kit/authoring/story-board-presentation.ts +111 -0
  99. package/src/kit/authoring/three-root.ts +67 -0
  100. package/src/kit/authoring/viewport-tool-context.ts +73 -0
  101. package/src/kit/authoring/viewport-tool-owner.ts +38 -0
  102. package/src/kit/authoring/world-session-state.ts +101 -0
  103. package/src/kit/authoring-seam-evidence.ts +300 -0
  104. package/src/kit/availability-tick.ts +66 -0
  105. package/src/kit/bitmap-label.ts +120 -0
  106. package/src/kit/boot-routing.ts +392 -0
  107. package/src/kit/breakpoint-state.ts +43 -0
  108. package/src/kit/build-identity.ts +16 -0
  109. package/src/kit/bytes-codec.ts +62 -0
  110. package/src/kit/cancellation-reason.ts +58 -0
  111. package/src/kit/canvas-frames.ts +88 -0
  112. package/src/kit/capture-camera-pose.ts +77 -0
  113. package/src/kit/capture-size.ts +88 -0
  114. package/src/kit/chrome-registry.ts +159 -0
  115. package/src/kit/chrome-slot-registry.ts +91 -0
  116. package/src/kit/collaboration-client.ts +264 -0
  117. package/src/kit/collaboration-presence.ts +41 -0
  118. package/src/kit/command-dispatch.ts +19 -0
  119. package/src/kit/command-listener.ts +2182 -0
  120. package/src/kit/command-registry.ts +71 -0
  121. package/src/kit/component-board-registry.ts +205 -0
  122. package/src/kit/component-states-registry.ts +199 -0
  123. package/src/kit/components/AlignToolbar.tsx +204 -0
  124. package/src/kit/components/ApplicationMenus.tsx +372 -0
  125. package/src/kit/components/AssetEditorShell.tsx +216 -0
  126. package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
  127. package/src/kit/components/BoardRulers.tsx +354 -0
  128. package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
  129. package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
  130. package/src/kit/components/ChromeSlot.tsx +20 -0
  131. package/src/kit/components/CodeView.tsx +470 -0
  132. package/src/kit/components/CompactInspectorShell.tsx +39 -0
  133. package/src/kit/components/ConsolePanel.tsx +273 -0
  134. package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
  135. package/src/kit/components/InspectionProjection.tsx +932 -0
  136. package/src/kit/components/Inspector.tsx +270 -0
  137. package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
  138. package/src/kit/components/InspectorFieldsSection.tsx +290 -0
  139. package/src/kit/components/InspectorStoriesSection.tsx +92 -0
  140. package/src/kit/components/InspectorToolSection.tsx +96 -0
  141. package/src/kit/components/InspectorTransformSection.tsx +245 -0
  142. package/src/kit/components/LightExplorerPanel.tsx +433 -0
  143. package/src/kit/components/MediaProperties.tsx +145 -0
  144. package/src/kit/components/ProjectHeader.tsx +328 -0
  145. package/src/kit/components/ReactCanvasControls.tsx +358 -0
  146. package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
  147. package/src/kit/components/RootTextEditor.tsx +79 -0
  148. package/src/kit/components/SaveStatus.tsx +70 -0
  149. package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
  150. package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
  151. package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
  152. package/src/kit/components/ToolHost.tsx +380 -0
  153. package/src/kit/components/Toolbar.tsx +811 -0
  154. package/src/kit/components/TransientHint.tsx +44 -0
  155. package/src/kit/components/VersionControlSection.tsx +470 -0
  156. package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
  157. package/src/kit/components/VolterLogo.tsx +18 -0
  158. package/src/kit/components/WorktreeSwitcher.tsx +712 -0
  159. package/src/kit/components/account-documents.tsx +1162 -0
  160. package/src/kit/components/asset-documents.tsx +794 -0
  161. package/src/kit/components/asset-editor-persistence.ts +216 -0
  162. package/src/kit/components/asset-selection-section.tsx +545 -0
  163. package/src/kit/components/asset-thumbnails.tsx +307 -0
  164. package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
  165. package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  166. package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
  167. package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
  168. package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
  169. package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
  170. package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  171. package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
  172. package/src/kit/components/asset-viewers/shader-source.ts +144 -0
  173. package/src/kit/components/board-guides.ts +150 -0
  174. package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
  175. package/src/kit/components/canvas-temporary-pivot.ts +34 -0
  176. package/src/kit/components/core-utilities.tsx +94 -0
  177. package/src/kit/components/inspector-preview-section.tsx +223 -0
  178. package/src/kit/components/inspector-revert-label.ts +20 -0
  179. package/src/kit/components/inspector-selection.ts +42 -0
  180. package/src/kit/components/inspector-stories-gating.ts +171 -0
  181. package/src/kit/components/inspector-transform-subject.ts +11 -0
  182. package/src/kit/components/inspector-transform.ts +88 -0
  183. package/src/kit/components/kind-documents.tsx +544 -0
  184. package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
  185. package/src/kit/components/project-tool-documents.tsx +402 -0
  186. package/src/kit/components/scene-documents.tsx +221 -0
  187. package/src/kit/components/status-contributions.tsx +407 -0
  188. package/src/kit/components/tool-documents.tsx +302 -0
  189. package/src/kit/components/tool-schema-form.tsx +262 -0
  190. package/src/kit/components/use-after-paint.ts +41 -0
  191. package/src/kit/components/use-project-image-assets.ts +86 -0
  192. package/src/kit/components/workspace-history.ts +32 -0
  193. package/src/kit/components/world-documents.tsx +570 -0
  194. package/src/kit/components/world-overlay-gestures.ts +1939 -0
  195. package/src/kit/composite-screenshot.ts +2238 -0
  196. package/src/kit/content-entry-source-registry.ts +184 -0
  197. package/src/kit/contribution-surfaces.ts +48 -0
  198. package/src/kit/coverage/canvas-reveal.ts +192 -0
  199. package/src/kit/coverage/design-time-surfaces.ts +101 -0
  200. package/src/kit/coverage/ontology-invariants.ts +466 -0
  201. package/src/kit/coverage/session-vitals.ts +503 -0
  202. package/src/kit/crash-null-boundary.ts +36 -0
  203. package/src/kit/creation-site-edit.ts +1491 -0
  204. package/src/kit/creation-site-registry.ts +160 -0
  205. package/src/kit/delegate-harness-registry.ts +134 -0
  206. package/src/kit/document-areas.ts +70 -0
  207. package/src/kit/document-context-registry.ts +193 -0
  208. package/src/kit/document-open-registry.ts +200 -0
  209. package/src/kit/document-play-extension.ts +221 -0
  210. package/src/kit/document-preview-source.ts +20 -0
  211. package/src/kit/document-renderer-session.ts +138 -0
  212. package/src/kit/document-stage-sessions.ts +26 -0
  213. package/src/kit/document-viewports.ts +120 -0
  214. package/src/kit/editor-api.ts +46 -0
  215. package/src/kit/editor-chrome-capture.ts +136 -0
  216. package/src/kit/editor-commands.ts +176 -0
  217. package/src/kit/editor-console.ts +580 -0
  218. package/src/kit/editor-current-view.ts +56 -0
  219. package/src/kit/editor-document-probe.ts +1166 -0
  220. package/src/kit/editor-git-client.ts +115 -0
  221. package/src/kit/editor-hotkeys.ts +728 -0
  222. package/src/kit/editor-lease-view.ts +39 -0
  223. package/src/kit/editor-lease.ts +415 -0
  224. package/src/kit/editor-mode.ts +19 -0
  225. package/src/kit/editor-notifications.ts +140 -0
  226. package/src/kit/editor-presence.ts +563 -0
  227. package/src/kit/editor-presentation-activity.ts +58 -0
  228. package/src/kit/editor-presentation-notice.ts +42 -0
  229. package/src/kit/editor-runtime.tsx +147 -0
  230. package/src/kit/editor-server-response.ts +86 -0
  231. package/src/kit/editor-session-attribution.ts +85 -0
  232. package/src/kit/editor-session-mode.ts +54 -0
  233. package/src/kit/editor-state-facets.ts +74 -0
  234. package/src/kit/editor-view-presentation.ts +777 -0
  235. package/src/kit/environment-images.ts +58 -0
  236. package/src/kit/eyedropper-session.ts +60 -0
  237. package/src/kit/files/file-provider.ts +62 -0
  238. package/src/kit/files/project-files.ts +270 -0
  239. package/src/kit/finders/index.ts +137 -0
  240. package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
  241. package/src/kit/frame/frame-parts.ts +30 -0
  242. package/src/kit/framed-document-capture.ts +34 -0
  243. package/src/kit/game-globals-prelude.ts +143 -0
  244. package/src/kit/game-surface-defaults.ts +33 -0
  245. package/src/kit/gameplay-dom-recording.ts +318 -0
  246. package/src/kit/gameplay-export-state.ts +14 -0
  247. package/src/kit/gameplay-replay.ts +417 -0
  248. package/src/kit/gameplay-session-time.ts +9 -0
  249. package/src/kit/gameplay-sessions.ts +204 -0
  250. package/src/kit/hierarchy-component-marks.ts +298 -0
  251. package/src/kit/hierarchy-internals.ts +197 -0
  252. package/src/kit/hierarchy-kind-icon.ts +217 -0
  253. package/src/kit/hierarchy-menu-registry.ts +67 -0
  254. package/src/kit/hierarchy-node-rows.ts +307 -0
  255. package/src/kit/hierarchy-panel-view.ts +280 -0
  256. package/src/kit/hierarchy-projection.ts +76 -0
  257. package/src/kit/hierarchy-row-media.ts +45 -0
  258. package/src/kit/hierarchy-row-model.ts +308 -0
  259. package/src/kit/hierarchy-rows.ts +11 -0
  260. package/src/kit/hierarchy-walk.ts +86 -0
  261. package/src/kit/history/editor-session.ts +25 -0
  262. package/src/kit/history/history-commands.ts +147 -0
  263. package/src/kit/history/history-delegate.ts +187 -0
  264. package/src/kit/history/history-limit-notices.ts +43 -0
  265. package/src/kit/history/history-service.ts +1189 -0
  266. package/src/kit/history/persistence-coordinator.ts +35 -0
  267. package/src/kit/history/project-file-history.ts +386 -0
  268. package/src/kit/history/project-root-history-backends.ts +139 -0
  269. package/src/kit/history/resource-registry.ts +209 -0
  270. package/src/kit/history/snapshot-store.ts +103 -0
  271. package/src/kit/history/source-history-backend.ts +546 -0
  272. package/src/kit/history-types.ts +124 -0
  273. package/src/kit/hmr-registration-group.ts +67 -0
  274. package/src/kit/hmr-stable-react-context.ts +23 -0
  275. package/src/kit/hotkeys.ts +190 -0
  276. package/src/kit/inference-diagnostics.ts +69 -0
  277. package/src/kit/initial-project.ts +80 -0
  278. package/src/kit/inspection/active-subject.ts +571 -0
  279. package/src/kit/inspection/active-surface.ts +142 -0
  280. package/src/kit/inspection/compose-subject.ts +1055 -0
  281. package/src/kit/inspection/compose.ts +7 -0
  282. package/src/kit/inspection/display.ts +171 -0
  283. package/src/kit/inspection/document-subject.ts +109 -0
  284. package/src/kit/inspection/game-subject.ts +85 -0
  285. package/src/kit/inspection/null-subject.ts +119 -0
  286. package/src/kit/inspection/serialize.ts +357 -0
  287. package/src/kit/inspection/use-active-inspection.ts +180 -0
  288. package/src/kit/inspection-model.ts +542 -0
  289. package/src/kit/inspection-node-media.ts +58 -0
  290. package/src/kit/inspector-presentation.ts +203 -0
  291. package/src/kit/inspector-property-grouping.ts +64 -0
  292. package/src/kit/inspector-section-registry.ts +221 -0
  293. package/src/kit/instance-source-actions.ts +163 -0
  294. package/src/kit/js-heap.ts +71 -0
  295. package/src/kit/key-actions.ts +91 -0
  296. package/src/kit/keymap-presets.ts +428 -0
  297. package/src/kit/layout-policy.ts +31 -0
  298. package/src/kit/light-explorer-model.ts +134 -0
  299. package/src/kit/live-canvas-frame.ts +55 -0
  300. package/src/kit/live-document.ts +296 -0
  301. package/src/kit/live-gesture-lock.ts +50 -0
  302. package/src/kit/live-seam-evidence.ts +11 -0
  303. package/src/kit/live-session-registry.ts +220 -0
  304. package/src/kit/live-transition.ts +391 -0
  305. package/src/kit/manifest-project.ts +107 -0
  306. package/src/kit/module-fetch-diagnosis.ts +192 -0
  307. package/src/kit/mount-failure-report.ts +154 -0
  308. package/src/kit/native-selection-style.ts +497 -0
  309. package/src/kit/object3d-document-write-policy.ts +137 -0
  310. package/src/kit/packaged-runtime.ts +108 -0
  311. package/src/kit/palettes/maya.palette.json +57 -0
  312. package/src/kit/palettes/substance.palette.json +57 -0
  313. package/src/kit/performance-profiler.ts +367 -0
  314. package/src/kit/performance-sources.ts +69 -0
  315. package/src/kit/photograph-notice.ts +141 -0
  316. package/src/kit/play-boot-phase.ts +166 -0
  317. package/src/kit/play-camera-flight.ts +35 -0
  318. package/src/kit/png-encode.worker.ts +26 -0
  319. package/src/kit/presentation-surface.ts +248 -0
  320. package/src/kit/product-command.ts +90 -0
  321. package/src/kit/project-adapter.ts +1140 -0
  322. package/src/kit/project-asset-refresh.ts +23 -0
  323. package/src/kit/project-asset-roots.ts +68 -0
  324. package/src/kit/project-local-state.ts +151 -0
  325. package/src/kit/project-manager.ts +243 -0
  326. package/src/kit/project-module-changes.ts +201 -0
  327. package/src/kit/project-module-split.ts +270 -0
  328. package/src/kit/project-play-layers.ts +25 -0
  329. package/src/kit/project-provenance.ts +115 -0
  330. package/src/kit/project-ready.ts +42 -0
  331. package/src/kit/project-shape.ts +68 -0
  332. package/src/kit/project-tools.ts +107 -0
  333. package/src/kit/projection-types.ts +44 -0
  334. package/src/kit/readiness.ts +113 -0
  335. package/src/kit/renderer-resource-counts.ts +27 -0
  336. package/src/kit/reported-play-state.ts +90 -0
  337. package/src/kit/resolve-contributed-command.ts +14 -0
  338. package/src/kit/resolve-relative-specifier.ts +33 -0
  339. package/src/kit/retained-document-states.ts +91 -0
  340. package/src/kit/scene-document-plan.ts +320 -0
  341. package/src/kit/scene-live-open.ts +210 -0
  342. package/src/kit/scoped-game-css.ts +152 -0
  343. package/src/kit/served-url.ts +5 -0
  344. package/src/kit/session-close.ts +17 -0
  345. package/src/kit/session-tombstone.ts +127 -0
  346. package/src/kit/settings/settings-provider.ts +82 -0
  347. package/src/kit/settings-store.ts +348 -0
  348. package/src/kit/shell-document-state.ts +27 -0
  349. package/src/kit/shell-store-door.ts +45 -0
  350. package/src/kit/shell-store.ts +722 -0
  351. package/src/kit/source-conflict.ts +122 -0
  352. package/src/kit/stage-context.ts +377 -0
  353. package/src/kit/stage-invalidation.ts +25 -0
  354. package/src/kit/stage-store-registry.ts +69 -0
  355. package/src/kit/startup-failure.ts +80 -0
  356. package/src/kit/state-report-deferral.ts +73 -0
  357. package/src/kit/storage/host-files-storage.ts +97 -0
  358. package/src/kit/storage/http-storage.ts +174 -0
  359. package/src/kit/storage/index.ts +75 -0
  360. package/src/kit/storage/mem-storage.ts +158 -0
  361. package/src/kit/storage/path-lock.ts +44 -0
  362. package/src/kit/storage/paths.ts +26 -0
  363. package/src/kit/storage-types.ts +127 -0
  364. package/src/kit/stories/StoryPreviewMount.tsx +306 -0
  365. package/src/kit/stories/compose-project-stories.ts +255 -0
  366. package/src/kit/stories/prefabs-finder.ts +54 -0
  367. package/src/kit/stories/prefabs-from-stories.ts +182 -0
  368. package/src/kit/stories/project-story-regions.ts +24 -0
  369. package/src/kit/stories/story-capture.ts +579 -0
  370. package/src/kit/stories/story-declared-medium.ts +126 -0
  371. package/src/kit/stories/story-discovery.ts +176 -0
  372. package/src/kit/stories/story-dom-runtime.ts +78 -0
  373. package/src/kit/stories/story-grouping.ts +111 -0
  374. package/src/kit/stories/story-mount-turn.ts +27 -0
  375. package/src/kit/stories/story-presentation.ts +215 -0
  376. package/src/kit/stories/story-preview-component.ts +7 -0
  377. package/src/kit/stories/story-registry.ts +530 -0
  378. package/src/kit/stories-scope.ts +35 -0
  379. package/src/kit/story-document-openers.ts +36 -0
  380. package/src/kit/story-thumbnails.ts +47 -0
  381. package/src/kit/surface-keyboard.ts +101 -0
  382. package/src/kit/surface-state.ts +135 -0
  383. package/src/kit/system-seam-evidence.ts +72 -0
  384. package/src/kit/tab-census.ts +202 -0
  385. package/src/kit/tab-lifecycle-client.ts +227 -0
  386. package/src/kit/theme-library.ts +897 -0
  387. package/src/kit/theme-preference.ts +429 -0
  388. package/src/kit/three-viewport-presentation.ts +23 -0
  389. package/src/kit/tool-contribution-play.ts +74 -0
  390. package/src/kit/tool-loader.ts +1918 -0
  391. package/src/kit/transform-mode-request.ts +66 -0
  392. package/src/kit/transient-hint.ts +78 -0
  393. package/src/kit/transport-strip.tsx +174 -0
  394. package/src/kit/ui-source/adapter-region-includes.ts +238 -0
  395. package/src/kit/ui-source/file-region-resolver.ts +302 -0
  396. package/src/kit/ui-source/inspect.ts +775 -0
  397. package/src/kit/ui-source/source-write-backend.ts +605 -0
  398. package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
  399. package/src/kit/user-local-state.ts +105 -0
  400. package/src/kit/viewport-activation-timings.ts +840 -0
  401. package/src/kit/viewport-editor-controls.ts +22 -0
  402. package/src/kit/viewport-presentation.ts +668 -0
  403. package/src/kit/viewport-surface-status.tsx +55 -0
  404. package/src/kit/wait-until.ts +37 -0
  405. package/src/kit/worker-call-metrics.ts +166 -0
  406. package/src/kit/workspace-areas.ts +191 -0
  407. package/src/kit/workspace-aux-commands.ts +11 -0
  408. package/src/kit/workspace-available-documents.ts +142 -0
  409. package/src/kit/workspace-core-utilities.ts +31 -0
  410. package/src/kit/workspace-document-ids.ts +59 -0
  411. package/src/kit/workspace-document-registry.ts +624 -0
  412. package/src/kit/workspace-document-restore.ts +146 -0
  413. package/src/kit/workspace-host-commands.ts +141 -0
  414. package/src/kit/workspace-persistence-gate.ts +40 -0
  415. package/src/kit/workspace-play-utilities.ts +44 -0
  416. package/src/kit/workspace-presets.ts +446 -0
  417. package/src/kit/workspace-regions.ts +276 -0
  418. package/src/kit/workspace-static-panels.ts +73 -0
  419. package/src/kit/workspace-status-registry.ts +121 -0
  420. package/src/kit/workspace-storage.ts +35 -0
  421. package/src/kit/workspace-style.ts +226 -0
  422. package/src/kit/workspace-utility-commands.ts +74 -0
  423. package/src/kit/workspace-utility-registry.ts +263 -0
  424. package/src/kit/world-adoption-event.ts +23 -0
  425. package/src/kit/world-adoption.ts +115 -0
  426. package/src/kit/world-canvas-viewport-state.ts +35 -0
  427. package/src/kit/world-document-routing.ts +104 -0
  428. package/src/kit/world-pan-state.ts +198 -0
  429. package/src/kit/write-pipe.ts +173 -0
  430. package/src/layout-arrangements.ts +5 -0
  431. package/src/layouts.tsx +108 -0
  432. package/src/looks.ts +16 -0
  433. package/src/project/output-roots.ts +73 -0
  434. package/src/project/tab-census.ts +155 -0
  435. package/src/project-tool-catalog.ts +104 -0
  436. package/src/selection.tsx +107 -0
  437. package/src/services.ts +18 -0
  438. package/src/session/build-report.ts +22 -0
  439. package/src/session/collaboration-types.ts +262 -0
  440. package/src/session/command-table.ts +327 -0
  441. package/src/session/discovery.ts +100 -0
  442. package/src/session/editor-brand.ts +48 -0
  443. package/src/session/editor-compatibility.ts +317 -0
  444. package/src/session/editor-control-lifecycle.ts +68 -0
  445. package/src/session/editor-control-protocol.ts +5 -0
  446. package/src/session/entrypoint-selection-readers.ts +66 -0
  447. package/src/session/entrypoint-selection-source.ts +120 -0
  448. package/src/session/game-css-scope.ts +30 -0
  449. package/src/session/hosted-attachment.ts +225 -0
  450. package/src/session/limited-view.ts +82 -0
  451. package/src/session/product-create.ts +24 -0
  452. package/src/session/product-locator.ts +478 -0
  453. package/src/session/project-module-url.ts +242 -0
  454. package/src/session/project-serving.ts +164 -0
  455. package/src/session/project-upgrade.ts +403 -0
  456. package/src/session/registry-format.ts +210 -0
  457. package/src/session/relative-path-guard.ts +56 -0
  458. package/src/session/scoped-game-css.ts +461 -0
  459. package/src/session/source-glob.ts +15 -0
  460. package/src/session/tool-contribution-convention.ts +123 -0
  461. package/src/session/workbench-locator.ts +712 -0
  462. package/src/session.ts +41 -0
  463. package/src/share.ts +160 -0
  464. package/src/source-analysis.ts +28 -0
  465. package/src/source-authoring.ts +439 -0
  466. package/src/tools/errors.ts +91 -0
  467. package/src/tools/provider-execution.ts +70 -0
  468. package/src/tools/registry.ts +341 -0
  469. package/src/tools/types.ts +159 -0
  470. package/src/transport.ts +100 -0
  471. package/src/types.ts +1693 -0
  472. package/src/views.ts +164 -0
  473. package/src/widgets/design-system.ts +93 -0
  474. package/src/widgets/editor-appearance.ts +151 -0
  475. package/src/widgets/editor-material.ts +83 -0
  476. package/src/widgets/icon-set-registry.ts +105 -0
  477. package/src/widgets/index.ts +71 -0
  478. package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
  479. package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
  480. package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
  481. package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
  482. package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
  483. package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
  484. package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
  485. package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
  486. package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
  487. package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
  488. package/src/widgets/inspector-widgets/color-utils.ts +201 -0
  489. package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
  490. package/src/widgets/inspector-widgets/index.ts +25 -0
  491. package/src/widgets/inspector-widgets/shared.tsx +140 -0
  492. package/src/widgets/interactive-edit-scope.ts +33 -0
  493. package/src/widgets/patterns/Dialog.tsx +140 -0
  494. package/src/widgets/patterns/Fields.tsx +44 -0
  495. package/src/widgets/patterns/List.tsx +25 -0
  496. package/src/widgets/patterns/StateSurface.tsx +40 -0
  497. package/src/widgets/patterns/Surfaces.tsx +122 -0
  498. package/src/widgets/patterns/Tabs.tsx +80 -0
  499. package/src/widgets/patterns/Toolbar.tsx +72 -0
  500. package/src/widgets/patterns/Tree.tsx +72 -0
  501. package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
  502. package/src/widgets/primitives/Button.tsx +62 -0
  503. package/src/widgets/primitives/ColorInput.tsx +78 -0
  504. package/src/widgets/primitives/DraftTextInput.tsx +63 -0
  505. package/src/widgets/primitives/EditorIcon.tsx +157 -0
  506. package/src/widgets/primitives/FormControls.tsx +88 -0
  507. package/src/widgets/primitives/HoverPreview.tsx +96 -0
  508. package/src/widgets/primitives/JsonInput.tsx +113 -0
  509. package/src/widgets/primitives/Layout.tsx +100 -0
  510. package/src/widgets/primitives/Menu.tsx +161 -0
  511. package/src/widgets/primitives/NumberInput.tsx +169 -0
  512. package/src/widgets/primitives/Panel.tsx +80 -0
  513. package/src/widgets/primitives/SectionHeader.tsx +77 -0
  514. package/src/widgets/primitives/Text.tsx +54 -0
  515. package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
  516. package/src/widgets/primitives/Tooltip.tsx +204 -0
  517. package/src/widgets/primitives/Vec3Input.tsx +70 -0
  518. package/src/widgets/primitives/banner-tones.ts +32 -0
  519. package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
  520. package/src/widgets/primitives/editor-icons.ts +254 -0
  521. package/src/widgets/primitives/panel-header-styles.ts +42 -0
  522. package/src/widgets/theme.ts +2841 -0
  523. package/src/widgets/z-index.ts +25 -0
@@ -0,0 +1,403 @@
1
+ /**
2
+ * `upgrade [version]` — MOVE A PROJECT TO ONE RELEASE: its @volter packages AND its engine pin,
3
+ * together. Any product's CLI exposes it by passing who it is (`UpgradingProduct`) and declaring
4
+ * `volter.product.upgrade: true`, which is what lets the pinned-engine refusal name this verb
5
+ * (`./editor-compatibility`). It lives in the SDK's session half, beside the product and
6
+ * workbench locators, because a product CLI may import that and not editor-core's internals
7
+ * (`scripts/check-boundaries.mjs`).
8
+ *
9
+ * Why this exists (owner's stream, 2026-10-06): a project pinned to 0.5.185 had its packages
10
+ * upgraded to 0.5.189 and then would not open — the editor refuses a project whose
11
+ * `engine.version` differs from the engine it runs, and nothing that upgrades packages moves that
12
+ * pin. So every upgrade ended at the same refusal, and there was no command that did both.
13
+ *
14
+ * WHAT A RELEASE IS: the product package at `version`, and the versions it depends on. Its
15
+ * `@volter/editor-core` is the engine a project pins (`create.ts` stamps the same one), its
16
+ * `@volter/project` is the kit version the lockstep kit packages (`play`,
17
+ * `editor-ui`, …) install at. The running product answers for its own version with no network;
18
+ * any other version, and `latest`, is asked of the registry (`npm view`).
19
+ *
20
+ * IT EDITS TWO FILES AND NOTHING ELSE — `package.json` and `volter.project.json` — and in each
21
+ * only the rows it moves, in the file's own layout (add-play's rule, #133). It does not install:
22
+ * like `add-play`, it says `npm install` when a version changed, because the install is the
23
+ * person's and a checkout-linked project has nothing to install at all.
24
+ *
25
+ * A PRODUCT THAT WAS RENAMED (`UpgradingProduct.replaces`) is the one exception, because a
26
+ * project on the old name has no other way across: the old package is no longer published, and
27
+ * its own `upgrade` only knows its own name. So the NEW package's `upgrade` is the door — run as
28
+ * `npx <new package> upgrade` from inside the old project (`upgradeLine`) — and it moves the
29
+ * dependency to the new name, the `package.json` scripts that call the old command, and the
30
+ * files `create` wrote that name the old package or command (`.mcp.json`, `.codex/config.toml`,
31
+ * `AGENTS.md`, `CLAUDE.md`), so the project's agent servers and instructions keep working.
32
+ */
33
+ import { spawn } from 'node:child_process';
34
+ import { existsSync, lstatSync, readFileSync } from 'node:fs';
35
+ import { readFile, rename, rm, writeFile } from 'node:fs/promises';
36
+ import { dirname, join, resolve } from 'node:path';
37
+ import { MANIFEST_FILENAME } from '@volter/project/manifest/filename';
38
+ import { hasManifest } from '@volter/project/manifest/locate';
39
+ import { compareSemver } from './editor-compatibility';
40
+
41
+ export const UPGRADE_USAGE = "upgrade [version] # move this project's @volter packages and its engine pin to one release (default: latest)";
42
+
43
+ /** A product name that is no longer published, as a project still spells it. */
44
+ export interface RetiredProduct {
45
+ readonly packageName: string;
46
+ readonly command: string;
47
+ /** The names a project's own files use for the retired product, longest first, each with the
48
+ * name that replaces it. Each is replaced only as a whole word. */
49
+ readonly names: readonly (readonly [retired: string, current: string])[];
50
+ }
51
+
52
+ /** Who is upgrading: the product package a project declares, its command, and where it is installed. */
53
+ export interface UpgradingProduct {
54
+ readonly packageName: string;
55
+ readonly command: string;
56
+ /** The running product's package root — it answers for its own version without the registry. */
57
+ readonly dir: string;
58
+ /** The names this product replaced: a project declaring one is moved onto this product. */
59
+ readonly replaces?: readonly RetiredProduct[];
60
+ }
61
+
62
+ /** The ONE line that moves a project onto `product`, from any version — including a project on a
63
+ * name `product` replaced, whose own installed command does not know `product` exists. */
64
+ export function upgradeLine(product: Pick<UpgradingProduct, 'packageName'>): string {
65
+ return `npx ${product.packageName} upgrade`;
66
+ }
67
+
68
+ /** The retired product a project's `package.json` still declares, or null. */
69
+ export function declaredRetiredProduct(product: Pick<UpgradingProduct, 'replaces'>, project: string): RetiredProduct | null {
70
+ let pkg: PackageJson;
71
+ try {
72
+ pkg = JSON.parse(readFileSync(join(project, 'package.json'), 'utf8')) as PackageJson;
73
+ } catch {
74
+ return null;
75
+ }
76
+ return product.replaces?.find(retired =>
77
+ pkg.dependencies?.[retired.packageName] !== undefined || pkg.devDependencies?.[retired.packageName] !== undefined) ?? null;
78
+ }
79
+
80
+ /** The refusal every verb but `upgrade` gives a project still on a retired name. */
81
+ export function retiredProjectError(product: Pick<UpgradingProduct, 'packageName' | 'command'>, retired: RetiredProduct, project: string): Error {
82
+ return new Error(
83
+ `${project} is a ${retired.packageName} project, and ${retired.packageName} is now ${product.packageName} (command: ${product.command}). ` +
84
+ `Move the project onto it from the project folder, then run ${product.command} again:\n ${upgradeLine(product)}`,
85
+ );
86
+ }
87
+
88
+ /** The files `create` writes that name the product's package or command, project-relative. */
89
+ const RENAMED_PRODUCT_FILES = ['.mcp.json', '.codex/config.toml', 'AGENTS.md', 'CLAUDE.md'] as const;
90
+
91
+ /** `text` as a whole word — not part of a longer command, package or name it is the start or the
92
+ * end of (`<command>-x`, a name's plural). A path after it (`<package>/package.json`) or
93
+ * punctuation (a name's `'s`) still ends the word. */
94
+ function wholeWord(text: string): RegExp {
95
+ return new RegExp(`(?<![\\w-])${text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w-])`, 'g');
96
+ }
97
+
98
+ /** `entries` with the key `from` renamed to `to` where it stands, so a rewrite keeps its order. */
99
+ function renameKey<T>(entries: Record<string, T>, from: string, to: string, value: T): Record<string, T> {
100
+ return Object.fromEntries(Object.entries(entries).map(([key, current]) => key === from ? [to, value] : [key, current]));
101
+ }
102
+
103
+ interface Release {
104
+ readonly version: string;
105
+ readonly dependencies: Readonly<Record<string, string>>;
106
+ }
107
+
108
+ interface PackageJson {
109
+ dependencies?: Record<string, string>;
110
+ devDependencies?: Record<string, string>;
111
+ peerDependencies?: Record<string, string>;
112
+ optionalDependencies?: Record<string, string>;
113
+ workspaces?: unknown;
114
+ [key: string]: unknown;
115
+ }
116
+
117
+ /** The exact version a declared spec names (`0.5.185`, `^0.5.185`, `=0.5.185`), or null for a
118
+ * range, a tag, a path or a link — nothing a direction can be read from. */
119
+ function declaredVersion(spec: string | undefined): string | null {
120
+ const bare = spec?.replace(/^[\^~=v]+/, '');
121
+ return bare !== undefined && EXACT_VERSION.test(bare) ? bare : null;
122
+ }
123
+
124
+ /**
125
+ * Write every planned file, or none: each to a temp file beside it first, then all renamed into
126
+ * place. A failure while writing leaves the project as it was; a failure between the renames
127
+ * puts back what was already moved. Until 2026-10-06 (#146 review) the two files were written
128
+ * one after the other, so a failure on the second left a project with new packages and the old
129
+ * pin — exactly the mismatch this verb exists to end.
130
+ */
131
+ async function writeAll(files: readonly { readonly path: string; readonly content: string; readonly original: string }[]): Promise<void> {
132
+ const temp = (path: string) => `${path}.upgrade-${process.pid}.tmp`;
133
+ try {
134
+ for (const file of files) await writeFile(temp(file.path), file.content);
135
+ } catch (error) {
136
+ await Promise.all(files.map(file => rm(temp(file.path), { force: true })));
137
+ throw new Error(`The upgrade was not written (nothing changed): ${error instanceof Error ? error.message : String(error)}`);
138
+ }
139
+ const moved: typeof files[number][] = [];
140
+ try {
141
+ for (const file of files) { await rename(temp(file.path), file.path); moved.push(file); }
142
+ } catch (error) {
143
+ for (const file of moved) await writeFile(file.path, file.original).catch(() => {});
144
+ await Promise.all(files.map(file => rm(temp(file.path), { force: true })));
145
+ throw new Error(`The upgrade could not be put in place, and what had moved was put back: ${error instanceof Error ? error.message : String(error)}`);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * THE LOCK FORGETS THE OLD @volter FAMILY (2026-10-08). Its packages pin one another exactly
151
+ * (`editor-blender` has a peer `@volter/sdk@<its release>`), and npm checks a new member's pins
152
+ * against the old members the lock still holds before it replaces them, so the `npm install`
153
+ * this verb prints refused every installed project with ERESOLVE. Dropping the `@volter/` rows
154
+ * from `package-lock.json` and npm's hidden `node_modules/.package-lock.json` lets npm resolve
155
+ * the family again from package.json; every other package keeps its locked version.
156
+ */
157
+ async function lockWithoutVolter(path: string): Promise<{ path: string; content: string; original: string } | null> {
158
+ let raw: string;
159
+ let lock: { packages?: Record<string, unknown>; dependencies?: Record<string, unknown> };
160
+ try { raw = await readFile(path, 'utf8'); lock = JSON.parse(raw); } catch { return null; }
161
+ let dropped = 0;
162
+ for (const key of Object.keys(lock.packages ?? {}))
163
+ if (/(^|\/)node_modules\/@volter\//.test(key)) { delete lock.packages![key]; dropped++; }
164
+ for (const key of Object.keys(lock.dependencies ?? {}))
165
+ if (key.startsWith('@volter/')) { delete lock.dependencies![key]; dropped++; }
166
+ return dropped > 0 ? { path, content: jsonLayout(raw)(lock), original: raw } : null;
167
+ }
168
+
169
+ /** An exact release — a range would leave the pin and the install free to disagree again. */
170
+ const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
171
+
172
+ /**
173
+ * A JSON file's own layout, so a rewrite changes the rows it moves and nothing else. The same rule
174
+ * as `add-play`'s `jsonLayout` (packages/cyclotron/node/add-play.ts, #133); kept beside the
175
+ * verb that shares it across products rather than reaching into one product's CLI.
176
+ */
177
+ function jsonLayout(raw: string): (value: unknown) => string {
178
+ const indent = /^[{[]\r?\n([ \t]+)\S/.exec(raw)?.[1] ?? ' ';
179
+ const eol = raw.includes('\r\n') ? '\r\n' : '\n';
180
+ const trailing = /\r?\n$/.test(raw) ? eol : '';
181
+ return value => JSON.stringify(value, null, indent).replace(/\n/g, eol) + trailing;
182
+ }
183
+
184
+ /** `npm view <spec> version dependencies --json`, parsed. */
185
+ function npmView(spec: string): Promise<Release> {
186
+ return new Promise((done, fail) => {
187
+ // npm is npm.cmd on Windows, and node refuses to spawn a .cmd without a shell (EINVAL).
188
+ const child = spawn('npm', ['view', spec, 'version', 'dependencies', '--json'], {
189
+ windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'], shell: process.platform === 'win32',
190
+ });
191
+ let out = '';
192
+ let err = '';
193
+ child.stdout.on('data', (chunk: Buffer) => { out += chunk.toString('utf8'); });
194
+ child.stderr.on('data', (chunk: Buffer) => { err += chunk.toString('utf8'); });
195
+ child.once('error', fail);
196
+ child.once('exit', code => {
197
+ try {
198
+ const answer = JSON.parse(out) as { version?: unknown; dependencies?: unknown; error?: { summary?: unknown } };
199
+ if (code === 0 && typeof answer.version === 'string') {
200
+ done({ version: answer.version, dependencies: (answer.dependencies ?? {}) as Record<string, string> });
201
+ return;
202
+ }
203
+ fail(new Error(typeof answer.error?.summary === 'string' ? answer.error.summary : `npm view exited ${code}`));
204
+ } catch {
205
+ fail(new Error(err.trim().split('\n').at(-1) || `npm view exited ${code}`));
206
+ }
207
+ });
208
+ });
209
+ }
210
+
211
+ async function readRelease(product: UpgradingProduct, requested: string | undefined): Promise<Release> {
212
+ const running = JSON.parse(await readFile(join(product.dir, 'package.json'), 'utf8')) as { version: string; dependencies?: Record<string, string> };
213
+ if (requested === running.version) return { version: running.version, dependencies: running.dependencies ?? {} };
214
+ const spec = `${product.packageName}@${requested ?? 'latest'}`;
215
+ try {
216
+ return await npmView(spec);
217
+ } catch (error) {
218
+ const reason = error instanceof Error ? error.message : String(error);
219
+ throw new Error(`Could not read ${spec} from the registry (${reason}). Name a published version: ${product.command} upgrade <version>.`);
220
+ }
221
+ }
222
+
223
+ export async function upgradeProject(product: UpgradingProduct, requested?: string, cwd = process.cwd()): Promise<void> {
224
+ if (requested !== undefined && !EXACT_VERSION.test(requested))
225
+ throw new Error(`${product.command} upgrade takes an exact version (e.g. 0.5.189), not "${requested}".`);
226
+ // The project around the working directory, as `edit` with no folder finds it.
227
+ let project = resolve(cwd);
228
+ while (!hasManifest(project)) {
229
+ const parent = dirname(project);
230
+ if (parent === project) throw new Error(`Run ${product.command} upgrade inside a project: there is no ${MANIFEST_FILENAME} here or above.`);
231
+ project = parent;
232
+ }
233
+ const packagePath = join(project, 'package.json');
234
+ if (!existsSync(packagePath)) throw new Error(`${project} has no package.json to upgrade.`);
235
+
236
+ const release = await readRelease(product, requested);
237
+ const engine = release.dependencies['@volter/editor-core'];
238
+ const kit = release.dependencies['@volter/project'];
239
+ if (!engine || !kit)
240
+ throw new Error(`${product.packageName}@${release.version} declares no @volter/editor-core and @volter/project, so there is no engine to pin.`);
241
+
242
+ // PLAN BOTH FILES, THEN WRITE: a refusal below leaves the project as it was.
243
+ const changed: string[] = [];
244
+ const kept: string[] = [];
245
+ const warnings: string[] = [];
246
+
247
+ // 1. THE PACKAGES. The product moves to the release; a package the release depends on moves
248
+ // to that version; a lockstep kit package (declared at the project's old kit version) moves to
249
+ // the new kit version. Any other @volter package is the author's choice and is named, not moved.
250
+ const packageRaw = await readFile(packagePath, 'utf8');
251
+ const pkg = JSON.parse(packageRaw) as PackageJson;
252
+ const previousKit = pkg.devDependencies?.['@volter/project'] ?? pkg.dependencies?.['@volter/project'];
253
+ // Read before a retired name is moved below: a project on a retired name declares none of
254
+ // this product, and its direction is then read by its engine pin.
255
+ const currentProduct = declaredVersion(pkg.dependencies?.[product.packageName] ?? pkg.devDependencies?.[product.packageName]);
256
+
257
+ // NAMED, NOT MOVED: what this verb deliberately leaves to the author is listed, never skipped
258
+ // silently (#146 review) — peer and optional ranges are a package author's contract, and a
259
+ // workspace's members are projects of their own.
260
+ for (const section of ['peerDependencies', 'optionalDependencies'] as const) {
261
+ for (const [name, current] of Object.entries(pkg[section] ?? {})) {
262
+ if (name.startsWith('@volter/')) kept.push(`package.json ${section} keeps ${name}@${current}: ${section} are not moved by upgrade; edit them by hand if they should follow`);
263
+ }
264
+ }
265
+ if (pkg.workspaces !== undefined) {
266
+ const members = Array.isArray(pkg.workspaces) ? pkg.workspaces
267
+ : Array.isArray((pkg.workspaces as { packages?: unknown }).packages) ? (pkg.workspaces as { packages: unknown[] }).packages : [];
268
+ kept.push(`workspace members${members.length > 0 ? ` (${members.map(String).join(', ')})` : ''} are not upgraded: run ${product.command} upgrade in each member that is a project`);
269
+ }
270
+
271
+ let packagesChanged = false;
272
+ // 0. A RETIRED NAME becomes this product, where it stood and at the release — a project that
273
+ // already declares this product as well just loses the old row.
274
+ const retired = declaredRetiredProduct(product, project);
275
+ const textFiles: { path: string; content: string; original: string }[] = [];
276
+ if (retired !== null) {
277
+ const declaresProduct = pkg.dependencies?.[product.packageName] !== undefined || pkg.devDependencies?.[product.packageName] !== undefined;
278
+ for (const section of ['dependencies', 'devDependencies'] as const) {
279
+ const declared = pkg[section];
280
+ const current = declared?.[retired.packageName];
281
+ if (declared === undefined || current === undefined) continue;
282
+ if (declaresProduct) {
283
+ delete declared[retired.packageName];
284
+ changed.push(`package.json ${section}: ${retired.packageName} ${current} removed (${product.packageName} is already declared)`);
285
+ } else {
286
+ pkg[section] = renameKey(declared, retired.packageName, product.packageName, release.version);
287
+ changed.push(`package.json ${section}: ${retired.packageName} ${current} -> ${product.packageName} ${release.version}`);
288
+ }
289
+ packagesChanged = true;
290
+ }
291
+ // The scripts that call the old command (`create` wrote `dev` and one named for the command).
292
+ const scripts = pkg['scripts'];
293
+ if (scripts !== null && typeof scripts === 'object' && !Array.isArray(scripts)) {
294
+ let rewritten = scripts as Record<string, unknown>;
295
+ for (const [name, body] of Object.entries(rewritten)) {
296
+ const nextBody = typeof body === 'string' ? body.replace(wholeWord(retired.command), product.command) : body;
297
+ const nextName = name === retired.command && !(product.command in rewritten) ? product.command : name;
298
+ if (nextBody === body && nextName === name) continue;
299
+ rewritten = renameKey(rewritten, name, nextName, nextBody);
300
+ changed.push(`package.json scripts: ${name === nextName ? name : `${name} -> ${nextName}`}${nextBody === body ? '' : ` runs "${String(nextBody)}"`}`);
301
+ }
302
+ pkg['scripts'] = rewritten;
303
+ }
304
+ // The files that name the package or the command: the agent servers resolve the product's
305
+ // `bin` by package name, and the instructions tell the agent which command to run.
306
+ for (const file of RENAMED_PRODUCT_FILES) {
307
+ const path = join(project, file);
308
+ if (!existsSync(path)) continue;
309
+ const original = await readFile(path, 'utf8');
310
+ // Only exact references to the product move — its package, its command and its names, each
311
+ // as a whole word; any other phrase that happens to contain them is the author's and stays.
312
+ let content = original.replace(wholeWord(retired.packageName), product.packageName).replace(wholeWord(retired.command), product.command);
313
+ for (const [from, to] of retired.names) content = content.replace(wholeWord(from), to);
314
+ if (content === original) continue;
315
+ textFiles.push({ path, content, original });
316
+ changed.push(`${file}: names ${product.packageName} and \`${product.command}\``);
317
+ }
318
+ }
319
+ for (const section of ['dependencies', 'devDependencies'] as const) {
320
+ const declared = pkg[section];
321
+ if (!declared) continue;
322
+ for (const [name, current] of Object.entries(declared)) {
323
+ if (!name.startsWith('@volter/')) continue;
324
+ const target = name === product.packageName ? release.version : release.dependencies[name] ?? (current === previousKit ? kit : undefined);
325
+ if (target === undefined) {
326
+ kept.push(`package.json keeps ${name}@${current}: ${product.packageName}@${release.version} does not name it`);
327
+ continue;
328
+ }
329
+ if (current === target) continue;
330
+ declared[name] = target;
331
+ packagesChanged = true;
332
+ changed.push(`package.json ${section}: ${name} ${current} -> ${target}`);
333
+ }
334
+ }
335
+
336
+ // 2. THE ENGINE PIN — the row the refusal names. Only `engine.version` moves.
337
+ const manifestPath = join(project, MANIFEST_FILENAME);
338
+ const manifestRaw = await readFile(manifestPath, 'utf8');
339
+ const manifest = JSON.parse(manifestRaw) as { engine?: Record<string, unknown>; [key: string]: unknown };
340
+ const pinned = manifest.engine?.['version'];
341
+ const pinChanged = pinned !== engine;
342
+
343
+ // NEVER BACKWARDS BY ACCIDENT (#146 review). `latest` can be older than what a project is on (a
344
+ // prerelease, a version published from a branch), and moving a project back silently downgrades
345
+ // its engine under it. So a backwards move happens only when the version was named, and is said.
346
+ // The product's own declared version decides; a project that declares it as a range or a link
347
+ // is read by its engine pin instead.
348
+ const backwards = currentProduct !== null
349
+ ? (compareSemver(release.version, currentProduct) === -1 ? `${product.packageName}@${currentProduct}` : null)
350
+ : typeof pinned === 'string' && compareSemver(engine, pinned) === -1 ? `engine ${pinned}` : null;
351
+ if (backwards !== null) {
352
+ if (requested === undefined)
353
+ throw new Error(
354
+ `${product.packageName}@latest is ${release.version} (engine ${engine}), older than the ${backwards} this project is on, so upgrading to it would move the project backwards. ` +
355
+ `Nothing was changed. To go back on purpose, name the version: ${product.command} upgrade ${release.version}.`,
356
+ );
357
+ warnings.push(`This moves the project BACKWARDS, from ${backwards} to ${product.packageName}@${release.version} (engine ${engine}), because that version was named.`);
358
+ }
359
+ if (pinChanged) {
360
+ manifest.engine = { ...manifest.engine, version: engine };
361
+ changed.push(`${MANIFEST_FILENAME} engine.version: ${typeof pinned === 'string' ? pinned : '(none)'} -> ${engine}`);
362
+ }
363
+
364
+ // A checkout's project links the checkout's own install, which already holds every kit
365
+ // package; `npm install` there would write into the checkout (`add-play`'s same rule).
366
+ const linked = (() => { try { return lstatSync(join(project, 'node_modules')).isSymbolicLink(); } catch { return false; } })();
367
+ const locks = packagesChanged && !linked
368
+ ? (await Promise.all([join(project, 'package-lock.json'), join(project, 'node_modules', '.package-lock.json')]
369
+ .map(lockWithoutVolter))).filter((lock): lock is NonNullable<typeof lock> => lock !== null)
370
+ : [];
371
+ if (locks.length > 0) changed.push('package-lock.json: the old @volter rows dropped, so npm install resolves the new ones');
372
+
373
+ await writeAll([
374
+ ...(packagesChanged ? [{ path: packagePath, content: jsonLayout(packageRaw)(pkg), original: packageRaw }] : []),
375
+ ...(pinChanged ? [{ path: manifestPath, content: jsonLayout(manifestRaw)(manifest), original: manifestRaw }] : []),
376
+ ...textFiles,
377
+ ...locks,
378
+ ]);
379
+ for (const line of warnings) console.warn(`! ${line}`);
380
+
381
+ console.log(changed.length > 0
382
+ ? `Upgraded ${project} to ${product.packageName}@${release.version} (engine ${engine}):`
383
+ : `${project} is already on ${product.packageName}@${release.version} (engine ${engine}).`);
384
+ for (const line of changed) console.log(` + ${line}`);
385
+ if (kept.length > 0) console.log('Left as it was:');
386
+ for (const line of kept) console.log(` = ${line}`);
387
+ if (changed.length === 0) {
388
+ // NOTHING TO MOVE, YET AN EDITOR REFUSED: then the editor that refused is not this project's
389
+ // own installation (an older global install, another checkout), and saying "already on"
390
+ // alone left the person with no next step (#146 review).
391
+ console.log(`If an editor still refuses this project, it is running from another installation: open it with this project's own, \`npx --no-install ${product.command} edit .\` in ${project}.`);
392
+ return;
393
+ }
394
+ if (retired !== null)
395
+ console.log(`This project now opens in ${product.packageName}. Its command is ${product.command}: from the project folder, \`npx --no-install ${product.command} <command>\`.`);
396
+ console.log('Next:');
397
+ if (packagesChanged && !linked) console.log(` npm install # in ${project}; installs the versions above`);
398
+ // A running session keeps the editor it started with: new packages need a new session, while a
399
+ // pin that moved alone is read again by the page's Retry.
400
+ console.log(packagesChanged
401
+ ? ` ${product.command} close && ${product.command} edit . # restart the session on the new install`
402
+ : ` Press Retry on the editor page that refused the project, or run ${product.command} edit .`);
403
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * `~/.volter/editor-sessions.json` — the ONE spelling of the editor-session
3
+ * registry contract, and the shared machinery every reader was hand-copying.
4
+ *
5
+ * The registry's WRITE half stays with its owner
6
+ * (`packages/editor-core/server/session-registry.ts`); this module owns the
7
+ * FORMAT: the entry shape, the shape guards, the file path, the
8
+ * liveness-filtered read, the `/__editor/project` answer parser, and the
9
+ * pending-launch coordination. It existed as FOUR drifting copies
10
+ * (server registry, the CLI, and both volter-sdk transports) held together by
11
+ * a "the CLI has no editor dependency" premise that had stopped being true —
12
+ * and the drift was already real: one copy's `/__editor/project` parser
13
+ * dropped `manifestError`, reporting a degraded session as belonging to no
14
+ * project.
15
+ */
16
+
17
+ import {
18
+ mkdirSync,
19
+ readdirSync,
20
+ readFileSync,
21
+ realpathSync,
22
+ unlinkSync,
23
+ writeFileSync,
24
+ } from 'node:fs';
25
+ import { homedir } from 'node:os';
26
+ import { join, resolve } from 'node:path';
27
+ import { setTimeout as delay } from 'node:timers/promises';
28
+
29
+ /** One registry entry, exactly as the server's write half records it. */
30
+ export interface EditorSessionEntry {
31
+ /** Canonical project root currently open, or null when none. */
32
+ project: string | null;
33
+ port: number;
34
+ /** PID of the dev-server process. */
35
+ pid: number;
36
+ /** ISO timestamp of server start. */
37
+ startedAt: string;
38
+ sessionId: string | null;
39
+ /** Private loopback control credential — never returned by an editor HTTP
40
+ * route; exists only in this user-private registry. */
41
+ controlSecret?: string | null;
42
+ repositoryId: string | null;
43
+ worktreeId: string | null;
44
+ worktreeRoot: string | null;
45
+ projectRelativePath: string | null;
46
+ branch: string | null;
47
+ headCommit: string | null;
48
+ baseCommit: string | null;
49
+ }
50
+
51
+ export const EDITOR_SESSIONS_REGISTRY_FILE = join(homedir(), '.volter', 'editor-sessions.json');
52
+
53
+ export function isEditorSessionEntry(v: unknown): v is EditorSessionEntry {
54
+ if (typeof v !== 'object' || v === null) return false;
55
+ const s = v as Record<string, unknown>;
56
+ const optionalIdentity = (value: unknown) =>
57
+ value === undefined || value === null || typeof value === 'string';
58
+ return (
59
+ (typeof s['project'] === 'string' || s['project'] === null) &&
60
+ typeof s['port'] === 'number' &&
61
+ typeof s['pid'] === 'number' &&
62
+ typeof s['startedAt'] === 'string' &&
63
+ optionalIdentity(s['sessionId']) &&
64
+ optionalIdentity(s['controlSecret']) &&
65
+ optionalIdentity(s['repositoryId']) &&
66
+ optionalIdentity(s['worktreeId']) &&
67
+ optionalIdentity(s['worktreeRoot']) &&
68
+ optionalIdentity(s['projectRelativePath']) &&
69
+ optionalIdentity(s['branch']) &&
70
+ optionalIdentity(s['headCommit']) &&
71
+ optionalIdentity(s['baseCommit'])
72
+ );
73
+ }
74
+
75
+ export function normalizeEditorSessionEntry(session: EditorSessionEntry): EditorSessionEntry {
76
+ return {
77
+ ...session,
78
+ sessionId: session.sessionId ?? null,
79
+ controlSecret: session.controlSecret ?? null,
80
+ repositoryId: session.repositoryId ?? null,
81
+ worktreeId: session.worktreeId ?? null,
82
+ worktreeRoot: session.worktreeRoot ?? null,
83
+ projectRelativePath: session.projectRelativePath ?? null,
84
+ branch: session.branch ?? null,
85
+ headCommit: session.headCommit ?? null,
86
+ baseCommit: session.baseCommit ?? null,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Whether `pid` is a running process. Signal 0 checks existence only. EPERM means the
92
+ * process exists but this caller may not signal it: another user's process, or any
93
+ * process outside a sandboxed agent's shell (Codex on macOS runs commands under
94
+ * Seatbelt, which denies signals to the editor it did not start). Reading EPERM as
95
+ * dead made the CLI report "NO live sessions" beside a running editor.
96
+ */
97
+ export function pidAlive(pid: number): boolean {
98
+ try {
99
+ process.kill(pid, 0);
100
+ return true;
101
+ } catch (error) {
102
+ return (error as NodeJS.ErrnoException).code === 'EPERM';
103
+ }
104
+ }
105
+
106
+ /** The registry's live entries — shape-validated and PID-liveness-filtered. */
107
+ export function readLiveRegisteredSessions(): EditorSessionEntry[] {
108
+ try {
109
+ const raw: unknown = JSON.parse(readFileSync(EDITOR_SESSIONS_REGISTRY_FILE, 'utf8'));
110
+ return Array.isArray(raw)
111
+ ? raw
112
+ .filter(isEditorSessionEntry)
113
+ .map(normalizeEditorSessionEntry)
114
+ .filter((s) => pidAlive(s.pid))
115
+ : [];
116
+ } catch {
117
+ return [];
118
+ }
119
+ }
120
+
121
+ /** Announce a launch before the server can register. Each launcher owns its file. */
122
+ export function announceEditorLaunch(project: string): () => void {
123
+ const directory = join(project, '.volter', 'editor-launches');
124
+ mkdirSync(directory, { recursive: true });
125
+ const file = join(directory, `${process.pid}.json`);
126
+ writeFileSync(file, JSON.stringify({ pid: process.pid }));
127
+ const clear = () => {
128
+ process.removeListener('exit', clear);
129
+ try {
130
+ unlinkSync(file);
131
+ } catch {
132
+ // Already cleared when the server became ready.
133
+ }
134
+ };
135
+ process.once('exit', clear);
136
+ return clear;
137
+ }
138
+
139
+ function hasPendingEditorLaunch(project: string): boolean {
140
+ const directory = join(project, '.volter', 'editor-launches');
141
+ try {
142
+ return readdirSync(directory).some((name) => {
143
+ if (!/^\d+\.json$/.test(name)) return false;
144
+ const pid = Number(name.slice(0, -5));
145
+ return pid !== process.pid && pidAlive(pid);
146
+ });
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /** Wait only for an observed live launcher, never for a missing editor. */
153
+ export async function waitForPendingEditorLaunch(project: string): Promise<void> {
154
+ const canon = canonicalizeProjectPath(project);
155
+ const ready = () =>
156
+ readLiveRegisteredSessions().some(
157
+ (session) => session.project !== null && canonicalizeProjectPath(session.project) === canon,
158
+ );
159
+ if (ready() || !hasPendingEditorLaunch(canon)) return;
160
+ const deadline = Date.now() + 180_000;
161
+ while (!ready()) {
162
+ if (!hasPendingEditorLaunch(canon)) {
163
+ // The launcher clears its announcement only after registration.
164
+ if (ready()) return;
165
+ throw new Error(`The editor launch for ${canon} ended before registering a session.`);
166
+ }
167
+ if (Date.now() >= deadline) {
168
+ throw new Error(
169
+ `The editor launch for ${canon} is still running but has not registered within 180s.`,
170
+ );
171
+ }
172
+ await delay(100);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * WHICH PROJECT a `/__editor/project` body says its server is serving.
178
+ *
179
+ * `serving` is that server's own statement of "I AM serving this project, I
180
+ * just cannot describe it" (its manifest is unparseable or fails strict
181
+ * validation) — added to the route precisely because a bare
182
+ * `{ project: null }` is indistinguishable from "no project open". Reading
183
+ * only `project.path` collapses the two, and the cost is that every
184
+ * project-matched command loses a live session the moment a save breaks its
185
+ * manifest, reporting it as belonging to no project rather than as this
186
+ * project's degraded session.
187
+ */
188
+ export function servedProjectAnswer(body: unknown): {
189
+ path: string | null;
190
+ manifestError: string | null;
191
+ } {
192
+ const b = body as {
193
+ project?: { path?: string } | null;
194
+ serving?: { path?: string; error?: string } | null;
195
+ };
196
+ return {
197
+ path: b.project?.path ?? b.serving?.path ?? null,
198
+ manifestError: b.project ? null : (b.serving?.error ?? null),
199
+ };
200
+ }
201
+
202
+ /** Canonical (realpathed when possible) absolute form of a project path. */
203
+ function canonicalizeProjectPath(p: string): string {
204
+ const absolute = resolve(p);
205
+ try {
206
+ return realpathSync(absolute);
207
+ } catch {
208
+ return absolute;
209
+ }
210
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * THE spelling of "this string is a project-relative path that cannot escape
3
+ * its root". One helper, because the codebase had four incompatible ones and
4
+ * the weakest of them was load-bearing on a write route.
5
+ *
6
+ * What each of the four missed, and why a single predicate is the fix:
7
+ *
8
+ * - `path.includes('..')` (substring) is both too weak and too strong: it
9
+ * rejects the innocent `my..file.glb` while accepting nothing extra — but
10
+ * its real defect is that it says NOTHING about an ABSOLUTE path. A route
11
+ * that then does `resolve(root, 'public', filePath)` gets `filePath` back
12
+ * verbatim when it is absolute (that is `resolve`'s contract), so the write
13
+ * destination was never inside `root` at all and no `..` ever appeared.
14
+ * - `rel.startsWith('..')` false-negatives on a real directory named `..bak`.
15
+ * - `path.split('/').includes('..')` misses `a\..\b` on a Windows-shaped
16
+ * input; `path.split(/[\\/]/)` is the form that covers both.
17
+ * - none of them rejected a NUL byte or a `C:`-rooted path.
18
+ *
19
+ * Isomorphic on purpose — no `node:path` — so the browser-side resolver
20
+ * (`binding-resolver.ts`'s `guardModulePath`) and the Node-side route handlers
21
+ * enforce the SAME rule instead of two hand-rolled approximations of it. Its
22
+ * Node-side partner is `server/server-utils.ts`'s `isPathInside`, which
23
+ * answers the other half of the question (does the RESOLVED absolute path
24
+ * still land under the root); a write boundary wants both, because this one
25
+ * cannot see symlinks and that one cannot see intent.
26
+ */
27
+
28
+ /** Why a candidate path is not a contained project-relative path. */
29
+ export type RelativePathRejection = 'empty' | 'nul' | 'absolute' | 'traversal';
30
+
31
+ /**
32
+ * `null` when `path` is a safe project-relative path, otherwise the reason it
33
+ * is not. Callers that want a bespoke error message (see `guardModulePath`)
34
+ * switch on the reason; callers that only gate switch on
35
+ * {@link isContainedRelativePath}.
36
+ */
37
+ export function relativePathRejection(path: string): RelativePathRejection | null {
38
+ if (!path || path.trim() === '') return 'empty';
39
+ if (path.includes('\0')) return 'nul';
40
+ // Leading separator (POSIX absolute, UNC) or a Windows drive root. This is
41
+ // the case a `..` scan structurally cannot see.
42
+ // The drive form is `C:/…` / `C:\…` specifically — a POSIX file literally
43
+ // named `C:notes.txt` is relative and stays legal.
44
+ if (path.startsWith('/') || path.startsWith('\\') || /^[a-zA-Z]:[\\/]/.test(path)) {
45
+ return 'absolute';
46
+ }
47
+ // Segment-wise, over BOTH separators — `..bak` is a legitimate directory
48
+ // name and must survive; `a\..\b` must not.
49
+ if (path.split(/[\\/]/).includes('..')) return 'traversal';
50
+ return null;
51
+ }
52
+
53
+ /** True when `path` is a non-empty, relative, traversal-free project path. */
54
+ export function isContainedRelativePath(path: string): boolean {
55
+ return relativePathRejection(path) === null;
56
+ }