@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,236 @@
1
+ /**
2
+ * Materialize a project's DECLARED asset packs (`asset-manifest.json`, D-AP1)
3
+ * from the `cloud-asset-library` Worker into a `StorageBackend`, recording
4
+ * the same provenance ledger (`.volter/assets.json`, D-AP3) every other
5
+ * materialization site writes.
6
+ *
7
+ * This is the D-AP4 mechanism: a project's DECLARED packs are materialized
8
+ * from the `cloud-asset-library` worker (existing manifest + object routes),
9
+ * writing the same ledger.
10
+ *
11
+ * Exported for the open path to CALL rather than
12
+ * wired into `seedIfEmpty` here, so any declared pack (in the template or an
13
+ * example) reuses it without change. The template's `asset-manifest.json` is
14
+ * `{"packs": {}}` today, so calling this against the shipped template is a
15
+ * documented, harmless no-op: `declared` reports 0 and nothing is fetched.
16
+ *
17
+ * FAIL CLOSED AND LOUD, never fabricate (the anti-shim rule): a network
18
+ * failure, a 404, or a sha256 mismatch aborts THAT entry only — nothing is
19
+ * written for it — and is reported in the returned result; it never blocks
20
+ * the other declared entries and never substitutes placeholder bytes.
21
+ *
22
+ * ## The expected digest comes from the DECLARATION, never from the response
23
+ *
24
+ * Every entry pins `sha256` in `asset-manifest.json` (see
25
+ * `asset-pack-manifest.ts`), and that pin is the only authority here. Reading
26
+ * the expected digest out of the worker's own `/v1/assets/<id>` record instead
27
+ * would authenticate nothing on the FIRST fetch — a moved or tampered library
28
+ * answers self-consistently (its record and its bytes agree), the bytes get
29
+ * written, and its digest becomes the project's permanent `sourceHash` in the
30
+ * ledger. So: the record's digest must MATCH the pin (a disagreement is a
31
+ * refusal naming the asset), the fetched bytes are verified against the pin,
32
+ * and the pin is what the ledger records.
33
+ *
34
+ * ## "Already materialized" means the FILE is there, not just the record
35
+ *
36
+ * Idempotent and retry-safe: an entry is skipped only when the ledger has it
37
+ * AND its destination exists. Skipping on the ledger record alone made the
38
+ * broken case unrepairable — a project whose bytes were deleted, or whose
39
+ * ledger outlived a failed write, reported success forever, and the reopen
40
+ * retry (`project-manager.ts`) plus the Retry banner both skipped exactly the
41
+ * entry they existed to refetch.
42
+ */
43
+
44
+ import { sha256Hex } from '@volter/sdk/kit/bytes-codec';
45
+ import type { StorageBackend } from '@volter/sdk/kit/storage-types';
46
+ import { normalizeLedgerPath } from './asset-ledger';
47
+ import {
48
+ readAssetLedgerFromBackend,
49
+ recordAssetMaterializationInBackend,
50
+ } from './asset-ledger-backend';
51
+ /** Project-relative path of the pack declaration (D-AP1) — sibling of
52
+ * `volter.project.json` in the template/every example, and (per D-AP4) one of
53
+ * the plain-text files the browser build's seed writes alongside the rest
54
+ * of the template source. */
55
+ import {
56
+ ASSET_MANIFEST_PATH,
57
+ type FlattenedPackEntry,
58
+ flattenAssetPacks,
59
+ parseAssetPackManifest,
60
+ } from './asset-pack-manifest';
61
+ import {
62
+ cloudAssetObjectUrl,
63
+ DEFAULT_CLOUD_ASSET_BASE_URL,
64
+ fetchCloudAssetRecord,
65
+ } from './cloud-asset-client';
66
+
67
+ export { ASSET_MANIFEST_PATH };
68
+
69
+ export interface AssetPackMaterializationFailure {
70
+ readonly entry: FlattenedPackEntry;
71
+ readonly reason: string;
72
+ }
73
+
74
+ export interface AssetPackMaterializationResult {
75
+ /** Total entries declared across every pack. */
76
+ readonly declared: number;
77
+ readonly materialized: FlattenedPackEntry[];
78
+ /** Recorded in the ledger AND present on disk — nothing to do, not a failure. */
79
+ readonly alreadyPresent: FlattenedPackEntry[];
80
+ readonly failed: AssetPackMaterializationFailure[];
81
+ }
82
+
83
+ export interface MaterializeAssetPacksOptions {
84
+ /** Test-only override of the deployed worker's base URL. */
85
+ readonly baseUrl?: string;
86
+ readonly signal?: AbortSignal;
87
+ /** Called once per entry, after it is attempted. */
88
+ readonly onProgress?: (
89
+ entry: FlattenedPackEntry,
90
+ outcome: 'materialized' | 'already-present' | 'failed',
91
+ ) => void;
92
+ }
93
+
94
+ /**
95
+ * Materialize every pack entry `backend` declares in its own
96
+ * `asset-manifest.json` that is not already in the ledger.
97
+ *
98
+ * Reads the manifest from `backend` itself (not the repo), whichever backend
99
+ * the caller is already using.
100
+ */
101
+ export async function materializeDeclaredAssetPacks(
102
+ backend: StorageBackend,
103
+ options: MaterializeAssetPacksOptions = {},
104
+ ): Promise<AssetPackMaterializationResult> {
105
+ const entries = await declaredPackEntries(backend);
106
+ const result: AssetPackMaterializationResult = {
107
+ declared: entries.length,
108
+ materialized: [],
109
+ alreadyPresent: [],
110
+ failed: [],
111
+ };
112
+ if (entries.length === 0) return result;
113
+
114
+ const ledger = await readAssetLedgerFromBackend(backend);
115
+ for (const entry of entries) {
116
+ if (options.signal?.aborted) break;
117
+ // A ledger record is a record, not the bytes — see the module header.
118
+ if (ledger[normalizeLedgerPath(entry.dest)] && (await backend.exists(entry.dest))) {
119
+ result.alreadyPresent.push(entry);
120
+ options.onProgress?.(entry, 'already-present');
121
+ continue;
122
+ }
123
+ try {
124
+ await materializeOne(backend, entry, options);
125
+ result.materialized.push(entry);
126
+ options.onProgress?.(entry, 'materialized');
127
+ } catch (error) {
128
+ result.failed.push({ entry, reason: error instanceof Error ? error.message : String(error) });
129
+ options.onProgress?.(entry, 'failed');
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+
135
+ async function declaredPackEntries(backend: StorageBackend): Promise<FlattenedPackEntry[]> {
136
+ if (!(await backend.exists(ASSET_MANIFEST_PATH))) return [];
137
+ const raw = await backend.read(ASSET_MANIFEST_PATH);
138
+ let parsed: unknown;
139
+ try {
140
+ parsed = JSON.parse(raw);
141
+ } catch (error) {
142
+ throw new Error(
143
+ `${ASSET_MANIFEST_PATH} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
144
+ );
145
+ }
146
+ return flattenAssetPacks(parseAssetPackManifest(parsed));
147
+ }
148
+
149
+ async function materializeOne(
150
+ backend: StorageBackend,
151
+ entry: FlattenedPackEntry,
152
+ options: MaterializeAssetPacksOptions,
153
+ ): Promise<void> {
154
+ const fetched = await fetchVerifiedPackEntry(entry, options);
155
+ await backend.write(entry.dest, fetched.bytes);
156
+ await recordAssetMaterializationInBackend(backend, {
157
+ destPath: entry.dest,
158
+ key: entry.key,
159
+ license: fetched.license,
160
+ // The PIN, not the library's reported digest — identical here by the
161
+ // check inside `fetchVerifiedPackEntry`, and the pin is the fact this
162
+ // project reviewed.
163
+ sourceSha256: entry.sha256,
164
+ });
165
+ }
166
+
167
+ /** The bytes of one declared pack entry, plus the library's license string. */
168
+ export interface VerifiedPackEntryBytes {
169
+ readonly bytes: Uint8Array;
170
+ /** The library's own license string, for the ledger. */
171
+ readonly license: string;
172
+ }
173
+
174
+ /**
175
+ * Fetch and VERIFY one declared pack entry, writing nothing.
176
+ *
177
+ * This is the whole network + trust sequence — record lookup, pin/record
178
+ * cross-check, byte fetch, digest verification — with the destination IO left
179
+ * to the caller, because the two materialization sites differ only in where the
180
+ * bytes land: this module writes through a `StorageBackend` (browser/hosted),
181
+ * and `packages/volter-cli/src/asset-packs.ts` writes to the filesystem for an
182
+ * on-disk project the editor's `add` command just declared a pack into. Extracted so neither
183
+ * site can drift from the other on the part that matters — a second copy of
184
+ * "fetch then trust" is exactly how trust-on-first-use gets reintroduced.
185
+ *
186
+ * Throws on every failure and never returns partial bytes.
187
+ */
188
+ export async function fetchVerifiedPackEntry(
189
+ entry: FlattenedPackEntry,
190
+ options: MaterializeAssetPacksOptions = {},
191
+ ): Promise<VerifiedPackEntryBytes> {
192
+ const separator = entry.key.indexOf(':');
193
+ if (separator < 0) throw new Error(`Malformed asset key "${entry.key}" (expected "source:id").`);
194
+ const source = entry.key.slice(0, separator);
195
+ const assetId = entry.key.slice(separator + 1);
196
+ if (source !== 'local') {
197
+ throw new Error(
198
+ `Asset key "${entry.key}": only the "local" (cloud-hosted) source can be materialized ` +
199
+ 'from the cloud worker.',
200
+ );
201
+ }
202
+ const baseUrl = options.baseUrl ?? DEFAULT_CLOUD_ASSET_BASE_URL;
203
+ const fetchOptions = options.signal ? { baseUrl, signal: options.signal } : { baseUrl };
204
+ const record = await fetchCloudAssetRecord(assetId, fetchOptions);
205
+ if (!record) throw new Error(`"${entry.key}" is not hosted by the cloud asset library.`);
206
+ const file = record.files[0];
207
+ if (!file) throw new Error(`"${entry.key}" has no files in the cloud library.`);
208
+ if (file.dependencies.length > 0) {
209
+ throw new Error(
210
+ `"${entry.key}" has ${file.dependencies.length} dependency file(s) — a pack entry names ` +
211
+ 'exactly one destination file.',
212
+ );
213
+ }
214
+ // The cloud record must AGREE with the checked-in pin before a byte is
215
+ // fetched. A library that has moved on is a legitimate event; silently
216
+ // adopting its new bytes is not (see the module header).
217
+ if (file.main.sha256 !== entry.sha256) {
218
+ throw new Error(
219
+ `"${entry.key}" → ${entry.dest}: the cloud library reports sha256:${file.main.sha256} but ` +
220
+ `${ASSET_MANIFEST_PATH} pins sha256:${entry.sha256}. Nothing was fetched or written; ` +
221
+ 'the pin is only re-cut by a reviewed change to the declaration.',
222
+ );
223
+ }
224
+ const url = cloudAssetObjectUrl(baseUrl, assetId, 0);
225
+ const response = await fetch(url, options.signal ? { signal: options.signal } : {});
226
+ if (!response.ok) throw new Error(`${url} returned ${response.status} ${response.statusText}.`);
227
+ const bytes = new Uint8Array(await response.arrayBuffer());
228
+ const actual = await sha256Hex(bytes);
229
+ if (actual !== entry.sha256) {
230
+ throw new Error(
231
+ `"${entry.key}" → ${entry.dest}: ${url} delivered sha256:${actual} but the declaration ` +
232
+ `pins sha256:${entry.sha256}. Nothing was written.`,
233
+ );
234
+ }
235
+ return { bytes, license: record.license };
236
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Integer zoom for pixel art: a fractional factor resamples the art, and a
3
+ * resampled sheet is a blur with the correct pixels still in the file.
4
+ */
5
+
6
+ /** Largest scale that fits `src` inside `box` without resampling. */
7
+ export function integerFitScale(
8
+ srcWidth: number,
9
+ srcHeight: number,
10
+ boxWidth: number,
11
+ boxHeight: number,
12
+ ): number {
13
+ if (srcWidth <= 0 || srcHeight <= 0 || boxWidth <= 0 || boxHeight <= 0) return 1;
14
+ const growX = srcWidth <= boxWidth ? Math.floor(boxWidth / srcWidth) : 0;
15
+ const growY = srcHeight <= boxHeight ? Math.floor(boxHeight / srcHeight) : 0;
16
+ const grow = Math.min(growX, growY);
17
+ if (grow >= 1) return grow;
18
+ return 1 / Math.max(Math.ceil(srcWidth / boxWidth), Math.ceil(srcHeight / boxHeight));
19
+ }
20
+
21
+ export type ImageScaleMode = 'fit' | 1 | 4 | 8;
22
+
23
+ export function resolveImageScale(
24
+ mode: ImageScaleMode,
25
+ srcWidth: number,
26
+ srcHeight: number,
27
+ boxWidth: number,
28
+ boxHeight: number,
29
+ ): number {
30
+ return mode === 'fit' ? integerFitScale(srcWidth, srcHeight, boxWidth, boxHeight) : mode;
31
+ }
@@ -0,0 +1,124 @@
1
+ import { isContainedRelativePath } from '@volter/sdk/session/relative-path-guard';
2
+ import { z } from 'zod';
3
+
4
+ export const assetImportSettingsSchema = z
5
+ .object({
6
+ scale: z.number().positive().default(1),
7
+ sourceUnits: z.enum(['auto', 'mm', 'cm', 'm', 'in', 'ft']).default('auto'),
8
+ upAxis: z.enum(['auto', 'x', 'y', 'z']).default('auto'),
9
+ forwardAxis: z.enum(['auto', 'x', '-x', 'y', '-y', 'z', '-z']).default('auto'),
10
+ normals: z.enum(['preserve', 'generate', 'recalculate']).default('preserve'),
11
+ tangents: z.enum(['preserve', 'generate', 'discard']).default('preserve'),
12
+ materials: z.enum(['import', 'discard']).default('import'),
13
+ textures: z.enum(['copy', 'embed', 'discard']).default('copy'),
14
+ animations: z.array(z.string()).default([]),
15
+ meshOptimization: z.enum(['none', 'safe', 'aggressive']).default('safe'),
16
+ retainSource: z.boolean().default(true),
17
+ })
18
+ .strict();
19
+ export type AssetImportSettings = z.infer<typeof assetImportSettingsSchema>;
20
+
21
+ const safePathSchema = z
22
+ .string()
23
+ .min(1)
24
+ .refine(isContainedRelativePath, 'Path must stay within the source/project root.');
25
+
26
+ const importFileSchema = z
27
+ .object({
28
+ path: safePathSchema,
29
+ url: z.string().min(1),
30
+ hash: z.string().min(8),
31
+ size: z.number().int().nonnegative(),
32
+ role: z.enum(['primary', 'dependency', 'material', 'texture']),
33
+ })
34
+ .strict();
35
+
36
+ export const assetImportPlanSchema = z
37
+ .object({
38
+ version: z.literal(1),
39
+ id: z.string().min(1),
40
+ identity: z
41
+ .object({
42
+ familyId: z.string().min(1),
43
+ variantId: z.string().min(1),
44
+ deliveryId: z.string().min(1),
45
+ source: z.string().min(1).optional(),
46
+ displayName: z.string().min(1).optional(),
47
+ })
48
+ .strict(),
49
+ sourceFiles: z.array(importFileSchema).min(1),
50
+ generatedFiles: z.array(safePathSchema),
51
+ destination: safePathSchema,
52
+ contentKind: z
53
+ .enum(['model', 'animation-only', 'image', 'audio', 'material', 'data'])
54
+ .default('model'),
55
+ targetSkeleton: z.string().optional(),
56
+ settings: assetImportSettingsSchema,
57
+ conflicts: z.array(
58
+ z
59
+ .object({ path: z.string(), resolution: z.enum(['reuse', 'update', 'rename', 'cancel']) })
60
+ .strict(),
61
+ ),
62
+ attribution: z
63
+ .object({
64
+ sourceUrl: z.string(),
65
+ author: z.string(),
66
+ license: z.string(),
67
+ text: z.string(),
68
+ })
69
+ .strict(),
70
+ sourceHash: z.string().min(8),
71
+ runtimeReady: z.boolean(),
72
+ backend: z.enum(['native', 'three', 'blender']),
73
+ createdAt: z.string(),
74
+ })
75
+ .superRefine((plan, context) => {
76
+ if (plan.contentKind === 'animation-only' && plan.runtimeReady && !plan.targetSkeleton) {
77
+ context.addIssue({
78
+ code: 'custom',
79
+ path: ['targetSkeleton'],
80
+ message: 'Runtime-ready animation-only imports require a compatible target skeleton.',
81
+ });
82
+ }
83
+ });
84
+ export type AssetImportPlan = z.infer<typeof assetImportPlanSchema>;
85
+
86
+ export const assetImportResultSchema = z
87
+ .object({
88
+ version: z.literal(1),
89
+ planId: z.string(),
90
+ status: z.enum(['imported', 'skipped', 'failed', 'cancelled']),
91
+ writtenFiles: z.array(
92
+ z
93
+ .object({ path: z.string(), hash: z.string(), size: z.number().int().nonnegative() })
94
+ .strict(),
95
+ ),
96
+ skippedFiles: z.array(z.string()),
97
+ issues: z.array(
98
+ z.object({ code: z.string(), stage: z.string(), message: z.string() }).strict(),
99
+ ),
100
+ })
101
+ .strict();
102
+ export type AssetImportResult = z.infer<typeof assetImportResultSchema>;
103
+
104
+ export type AssetImportStage = 'fetch' | 'convert' | 'validate' | 'commit';
105
+ export interface AssetImportProgress {
106
+ stage: AssetImportStage;
107
+ item: string;
108
+ loaded: number;
109
+ total: number;
110
+ }
111
+
112
+ export type AssetConflictKind =
113
+ | 'same-identity-same-hash'
114
+ | 'same-identity-updated'
115
+ | 'different-identity-same-name'
116
+ | 'user-edited-output';
117
+ export function safeConflictResolution(
118
+ kind: AssetConflictKind,
119
+ ): 'reuse' | 'update' | 'rename' | 'cancel' {
120
+ if (kind === 'same-identity-same-hash') return 'reuse';
121
+ if (kind === 'same-identity-updated') return 'update';
122
+ if (kind === 'different-identity-same-name') return 'rename';
123
+ return 'cancel';
124
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The two mechanical halves of "a read-modify-write of one file never loses a
3
+ * record", shared by the ledger's two I/O sides (`../../server/
4
+ * asset-ledger-store.ts` over `node:fs`, `./asset-ledger-backend.ts` over a
5
+ * `StorageBackend`).
6
+ *
7
+ * Why the ledger needs this at all: `.volter/assets.json` (D-AP3) is updated by
8
+ * READING it, adding one entry, and writing the whole object back. Two of those
9
+ * in flight interleave — both read the old object, the later write wins, and the
10
+ * earlier record is gone with no error anywhere. Two materializations at once is
11
+ * ordinary, not exotic: two the editor's `edit` command sessions on one project, `npm run
12
+ * asset-packs:sync` while an editor is prewarming, two tabs on one project
13
+ * both retrying their declared packs on reopen.
14
+ *
15
+ * Neither half alone is enough, which is why both are here:
16
+ *
17
+ * - `createSerializer` — an in-process (in-realm) promise chain per key. This
18
+ * is what makes concurrency INSIDE one process correct, and it is the only
19
+ * mechanism that can, since a file lock cannot exclude its own holder.
20
+ * - `withExclusiveLock` — a lock FILE, which is the only thing a second
21
+ * process/realm can see. Injected I/O (`ExclusiveLockIO`) so the same
22
+ * waiting/stale-breaking policy runs over `node:fs` and over a
23
+ * `StorageBackend` without either being reimplemented.
24
+ *
25
+ * This module is deliberately a helper, not a system: no ownership of when a
26
+ * ledger is written, no retries of the caller's work, no state beyond the map a
27
+ * `createSerializer` call hands back.
28
+ */
29
+
30
+ /** File operations `withExclusiveLock` needs. One lock path per instance. */
31
+ export interface ExclusiveLockIO {
32
+ /** Create the lock holding `content` iff absent. `false` when already held. */
33
+ createIfAbsent(content: string): Promise<boolean>;
34
+ /** The held lock's content, or `null` if it has since vanished. */
35
+ read(): Promise<string | null>;
36
+ /**
37
+ * Delete the lock ONLY while it still holds `expected`. Must tolerate an
38
+ * already-absent lock, and must leave a lock holding anything else alone.
39
+ *
40
+ * There is deliberately no unconditional delete: every deletion in this
41
+ * module is either "release the lock I took" or "break the corpse I judged
42
+ * stale", and both name the exact content they mean. A caller that could
43
+ * delete whatever is there deletes ANOTHER writer's live lock whenever its
44
+ * own view is one step out of date — which is how two writers used to end up
45
+ * inside the critical section at once.
46
+ *
47
+ * The compare and the delete must be ATOMIC WITH RESPECT TO EACH OTHER, and
48
+ * that requirement is on the implementer, not on this module. A backend that
49
+ * reads, compares, then deletes as separate steps reintroduces the same
50
+ * defect one level down: two waiters both compare against the corpse, the
51
+ * first deletes it, acquires and enters, and the second's delete — already
52
+ * past its comparison — removes the winner's fresh lock and lets it in behind
53
+ * it. The two shipped backends each pay for this: the browser one runs inside
54
+ * the `navigator.locks` name `writeIfAbsent` takes, and the `node:fs` one has
55
+ * no atomic compare-and-delete at all and uses `removeIfHeldByTakeover`.
56
+ */
57
+ removeIfHeldBy(expected: string): Promise<void>;
58
+ }
59
+
60
+ /**
61
+ * The atomic single steps a takeover-based compare-and-delete needs. Each
62
+ * instance belongs to ONE attempt: the aside path is chosen by the caller and
63
+ * unique per attempt, so two concurrent attempts can never collide on it.
64
+ *
65
+ * On `node:fs` these are `rename`, `readFile`, `rm` and `writeFile` with the
66
+ * `wx` flag — every one a single syscall, which is the whole reason the
67
+ * algorithm below can be reasoned about at all.
68
+ */
69
+ export interface LockTakeoverOps {
70
+ /**
71
+ * Atomically move the lock to this attempt's aside path. `false` when the
72
+ * lock was not there — which, when several attempts race, is how every
73
+ * attempt but one finds out that it lost.
74
+ */
75
+ moveAside(): Promise<boolean>;
76
+ /** Content of the moved-aside lock; `null` if it is somehow not there. */
77
+ readAside(): Promise<string | null>;
78
+ deleteAside(): Promise<void>;
79
+ /** Put `content` back at the lock path, ONLY while it is still absent. */
80
+ restoreIfAbsent(content: string): Promise<boolean>;
81
+ }
82
+
83
+ /**
84
+ * `removeIfHeldBy` for a filesystem that has no atomic compare-and-delete:
85
+ * SEIZE the lock with one atomic move, and only then compare.
86
+ *
87
+ * What this buys, and it is the entire point: for a given lock, AT MOST ONE
88
+ * attempt can move it aside. Every other racer's `moveAside` reports `false`
89
+ * and deletes nothing, so it loops, re-reads, and sees the winner's fresh
90
+ * stamp. Read-then-delete could not offer this — its comparison is a snapshot,
91
+ * and a snapshot is stale by the time the delete runs.
92
+ *
93
+ * If the seized content is NOT the content this attempt judged, this attempt had
94
+ * no business taking it: put it straight back, then drop the aside copy.
95
+ *
96
+ * RESIDUAL — stated precisely, because it is not zero. Between the move and the
97
+ * restore, the lock path is briefly absent. A writer whose `createIfAbsent`
98
+ * lands in exactly that gap acquires while the lock's real holder is still
99
+ * inside its critical section. Reaching it takes two coincidences: the content
100
+ * must change between the caller's read and this move (the holder of a stamp
101
+ * older than `staleAfterMs` releasing, and a successor acquiring, inside that
102
+ * window), AND a third writer's create must land inside the restore window.
103
+ * POSIX offers no primitive that closes it — there is no atomic
104
+ * compare-and-delete — and closing it would take a lease/heartbeat protocol
105
+ * with a "your lock was compromised" callback, which this helper deliberately
106
+ * is not.
107
+ */
108
+ export async function removeIfHeldByTakeover(
109
+ ops: LockTakeoverOps,
110
+ expected: string,
111
+ ): Promise<void> {
112
+ if (!(await ops.moveAside())) return;
113
+ const seized = await ops.readAside();
114
+ if (seized !== null && seized !== expected) await ops.restoreIfAbsent(seized);
115
+ await ops.deleteAside();
116
+ }
117
+
118
+ export interface ExclusiveLockOptions {
119
+ /**
120
+ * How long a lock may be held before another writer BREAKS it.
121
+ *
122
+ * Breaking, rather than failing, is deliberate: the holder of a lock file is
123
+ * usually a process that has since died (Ctrl-C in the middle of
124
+ * `asset-packs:sync`, a closed tab), and a provenance record must not be
125
+ * refused forever because of a corpse. A real holder finishes in single-digit
126
+ * milliseconds, so this is orders of magnitude above the honest wait — and it
127
+ * is also the loop bound, so waiting can never deadlock.
128
+ */
129
+ readonly staleAfterMs?: number;
130
+ /** Poll interval while a live lock is held. */
131
+ readonly retryDelayMs?: number;
132
+ /** Recorded in the lock so a human can see who holds it. */
133
+ readonly owner?: string;
134
+ readonly now?: () => number;
135
+ readonly sleep?: (ms: number) => Promise<void>;
136
+ /**
137
+ * The per-ATTEMPT unique part of the stamp. Injectable so a test can name the
138
+ * attempts it schedules; never for production use.
139
+ *
140
+ * It is load-bearing rather than cosmetic: `removeIfHeldBy` can only tell "my
141
+ * lock" from "someone else's lock" if no two attempts can ever write the same
142
+ * stamp, and `{at, owner}` alone repeats whenever two writers with the same
143
+ * owner name land in the same millisecond.
144
+ */
145
+ readonly token?: () => string;
146
+ }
147
+
148
+ const DEFAULT_STALE_AFTER_MS = 10_000;
149
+ const DEFAULT_RETRY_DELAY_MS = 15;
150
+
151
+ /** Same guarded form as `../storage/index.ts` — `crypto` is absent in a
152
+ * non-secure browser context, and a missing token must not fail a write. */
153
+ function defaultToken(): string {
154
+ return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
155
+ }
156
+
157
+ /** Run `work` while holding an exclusive on-disk lock, always releasing it. */
158
+ export async function withExclusiveLock<T>(
159
+ io: ExclusiveLockIO,
160
+ work: () => Promise<T>,
161
+ options: ExclusiveLockOptions = {},
162
+ ): Promise<T> {
163
+ const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
164
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
165
+ const now = options.now ?? Date.now;
166
+ const sleep =
167
+ options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
168
+ const token = options.token ?? defaultToken;
169
+
170
+ for (;;) {
171
+ const stamp = JSON.stringify({ at: now(), owner: options.owner ?? 'unknown', token: token() });
172
+ if (await io.createIfAbsent(stamp)) {
173
+ try {
174
+ return await work();
175
+ } finally {
176
+ // Release only OUR lock: if a waiter judged it stale and its successor
177
+ // has already taken the lock, `stamp` is no longer the content and this
178
+ // is a no-op. An unconditional delete here would hand the successor's
179
+ // lock to whoever came next.
180
+ await io.removeIfHeldBy(stamp);
181
+ }
182
+ }
183
+ const held = await io.read();
184
+ // Gone between the failed create and this read — race the create again.
185
+ if (held === null) continue;
186
+ if (now() - lockHeldSince(held) > staleAfterMs) {
187
+ // Break the exact corpse we judged, not "whatever is there now" — an
188
+ // unconditional delete here removes the lock of whoever broke this same
189
+ // corpse a step ahead of us, and lets us walk in behind them.
190
+ //
191
+ // `held` is a SNAPSHOT: by the time the io acts on it, another waiter may
192
+ // have broken this corpse and acquired. That is why `removeIfHeldBy` is
193
+ // required to compare and delete atomically (see its doc comment) rather
194
+ // than being handed a "delete if this is still there" hint it can act on
195
+ // late — the atomicity is what makes at most one waiter break one corpse.
196
+ // The entrant is then decided only by `createIfAbsent`, and the loser of
197
+ // that race loops, re-reads, and sees the winner's fresh stamp.
198
+ await io.removeIfHeldBy(held);
199
+ continue;
200
+ }
201
+ await sleep(retryDelayMs);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * When the holder took the lock. An unreadable stamp reports epoch 0, i.e.
207
+ * immediately stale: a lock whose age cannot be established cannot be waited
208
+ * out honestly, and refusing to write provenance because of an unparseable
209
+ * scratch file would be the worse failure.
210
+ */
211
+ function lockHeldSince(content: string): number {
212
+ try {
213
+ const parsed = JSON.parse(content) as { at?: unknown };
214
+ return typeof parsed.at === 'number' ? parsed.at : 0;
215
+ } catch {
216
+ return 0;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * A per-key promise chain: `serialize(key, work)` starts `work` only after
222
+ * every earlier `work` for that key has settled (rejections included — one
223
+ * failed write must not wedge the queue).
224
+ *
225
+ * Each call returns its OWN map, so two independent callers can never collide
226
+ * on a key that happens to spell the same.
227
+ */
228
+ export function createSerializer(): <T>(key: string, work: () => Promise<T>) => Promise<T> {
229
+ const tails = new Map<string, Promise<unknown>>();
230
+ return <T>(key: string, work: () => Promise<T>): Promise<T> => {
231
+ const previous = tails.get(key) ?? Promise.resolve();
232
+ const result = previous.then(work, work);
233
+ // Swallow only on the CHAIN copy — the returned promise still rejects.
234
+ const tail = result.then(
235
+ () => undefined,
236
+ () => undefined,
237
+ );
238
+ tails.set(key, tail);
239
+ void tail.then(() => {
240
+ if (tails.get(key) === tail) tails.delete(key);
241
+ });
242
+ return result;
243
+ };
244
+ }