@volter/editor-game 0.5.65

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 (302) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +23 -0
  3. package/contributions/asset-budget-asset.menu.ts +26 -0
  4. package/contributions/asset-budget.action.ts +18 -0
  5. package/contributions/asset-budget.document.tsx +21 -0
  6. package/contributions/asset-budget.menu.ts +22 -0
  7. package/contributions/audio-unlock.service.ts +17 -0
  8. package/contributions/audio.utility.tsx +15 -0
  9. package/contributions/autoplay.service.ts +48 -0
  10. package/contributions/bridge.command.ts +172 -0
  11. package/contributions/build-profiles.document.tsx +21 -0
  12. package/contributions/build-progress.status.tsx +45 -0
  13. package/contributions/build.action.ts +26 -0
  14. package/contributions/build.command.ts +27 -0
  15. package/contributions/build.header.tsx +37 -0
  16. package/contributions/build.menu.ts +31 -0
  17. package/contributions/build.service.ts +31 -0
  18. package/contributions/connection.status.tsx +43 -0
  19. package/contributions/coverage.service.ts +118 -0
  20. package/contributions/edit-mode-audio.service.ts +23 -0
  21. package/contributions/edit-mode-networking.service.ts +30 -0
  22. package/contributions/game-document.service.ts +25 -0
  23. package/contributions/game-eval.command.ts +210 -0
  24. package/contributions/game.layout.ts +19 -0
  25. package/contributions/gameplay.command.ts +255 -0
  26. package/contributions/generation.service.ts +96 -0
  27. package/contributions/generations.status.tsx +53 -0
  28. package/contributions/ingest.service.ts +80 -0
  29. package/contributions/instances.command.ts +75 -0
  30. package/contributions/navmesh.menu.ts +39 -0
  31. package/contributions/navmesh.service.ts +19 -0
  32. package/contributions/network.utility.tsx +17 -0
  33. package/contributions/play.command.ts +355 -0
  34. package/contributions/profiler.action.ts +16 -0
  35. package/contributions/profiler.menu.ts +22 -0
  36. package/contributions/profiler.utility.tsx +17 -0
  37. package/contributions/react/component-board.service.ts +21 -0
  38. package/contributions/react/design-time-mount.service.ts +60 -0
  39. package/contributions/react/pasteboard.action.ts +41 -0
  40. package/contributions/react/react-inspector.service.ts +85 -0
  41. package/contributions/react/story-documents.service.ts +44 -0
  42. package/contributions/scene-document.service.ts +32 -0
  43. package/contributions/state-watch.action.ts +17 -0
  44. package/contributions/state-watch.menu.ts +23 -0
  45. package/contributions/state-watch.utility.tsx +20 -0
  46. package/contributions/team-playtest.service.ts +124 -0
  47. package/contributions/three/camera-runtime.inspector.tsx +34 -0
  48. package/contributions/three/component-board.service.ts +24 -0
  49. package/contributions/three/component-verbs.command.ts +110 -0
  50. package/contributions/three/component-verbs.service.ts +92 -0
  51. package/contributions/three/constraints.inspector.tsx +34 -0
  52. package/contributions/three/model-asset-sections.service.ts +26 -0
  53. package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
  54. package/contributions/three/story-documents.service.ts +31 -0
  55. package/contributions/three/three-authoring.service.ts +66 -0
  56. package/contributions/transport.header.tsx +19 -0
  57. package/contributions/xstate-behavior.action.ts +42 -0
  58. package/contributions/xstate-behavior.document.tsx +73 -0
  59. package/contributions/xstate-behavior.inspector.tsx +28 -0
  60. package/contributions/xstate-behavior.menu.ts +23 -0
  61. package/package.json +144 -0
  62. package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
  63. package/src/asset-budget/asset-budget-model.ts +799 -0
  64. package/src/asset-budget/basis-encoder.ts +165 -0
  65. package/src/asset-budget/gltf-io.ts +154 -0
  66. package/src/asset-budget/gltf-optimize.ts +384 -0
  67. package/src/asset-budget/image-dims.ts +78 -0
  68. package/src/asset-budget/optimize-apply.ts +221 -0
  69. package/src/audio/AudioDebuggerPanel.tsx +457 -0
  70. package/src/audio/audio-debugger-model.ts +87 -0
  71. package/src/bridge/call.ts +60 -0
  72. package/src/bridge/dispatch.ts +459 -0
  73. package/src/bridge/live-frames.ts +25 -0
  74. package/src/bridge/screenshot.ts +316 -0
  75. package/src/build/BuildProfilesPanel.tsx +476 -0
  76. package/src/build/build-session.ts +247 -0
  77. package/src/build/format-bytes.ts +14 -0
  78. package/src/command-results.ts +36 -0
  79. package/src/coverage/live-authoring-surface.ts +30 -0
  80. package/src/coverage/live-project-verbs.ts +162 -0
  81. package/src/coverage/native-system-coverage.ts +143 -0
  82. package/src/coverage/root-coverage.ts +79 -0
  83. package/src/coverage/session-coverage.ts +193 -0
  84. package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
  85. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
  86. package/src/edit-mode/edit-mode-audio.ts +56 -0
  87. package/src/edit-mode/edit-mode-networking.ts +112 -0
  88. package/src/game-document/DevicePresetPicker.tsx +84 -0
  89. package/src/game-document/GameCaptureFrameButton.tsx +45 -0
  90. package/src/game-document/GameDocument.tsx +215 -0
  91. package/src/game-document/GamePanel.tsx +662 -0
  92. package/src/game-document/InstanceInspectorPicker.tsx +183 -0
  93. package/src/game-document/crowd-debug.ts +183 -0
  94. package/src/game-document/device-preview.ts +336 -0
  95. package/src/game-document/game-view-store.ts +107 -0
  96. package/src/game-document/physics-debug.ts +187 -0
  97. package/src/generation/GenerationActivity.tsx +421 -0
  98. package/src/generation/GenerationGallery.css +231 -0
  99. package/src/generation/generation-documents.tsx +338 -0
  100. package/src/generation/generation-jobs.ts +128 -0
  101. package/src/generation/generation-presentation.ts +257 -0
  102. package/src/host/adapter-reach.ts +445 -0
  103. package/src/host/adapter-runtime-bindings.ts +303 -0
  104. package/src/host/after-paint.ts +112 -0
  105. package/src/host/api/configurations.ts +66 -0
  106. package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
  107. package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
  108. package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
  109. package/src/host/authoring/contract-scenes-stories.ts +204 -0
  110. package/src/host/authoring/creation-site-related.ts +55 -0
  111. package/src/host/authoring/ephemeral-persistence.ts +29 -0
  112. package/src/host/authoring/gesture-persist.ts +84 -0
  113. package/src/host/authoring/ingest-data-writer.ts +232 -0
  114. package/src/host/authoring/ingest-source-persistence.ts +826 -0
  115. package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
  116. package/src/host/authoring/mounted-authoring.ts +41 -0
  117. package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
  118. package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
  119. package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
  120. package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
  121. package/src/host/authoring/pixi-isolation-assets.ts +25 -0
  122. package/src/host/authoring/pixi-live-write-target.ts +979 -0
  123. package/src/host/authoring/pixi-source-identity.ts +141 -0
  124. package/src/host/authoring/pixi-still-presentation.ts +71 -0
  125. package/src/host/authoring/pixi-structure-history.ts +237 -0
  126. package/src/host/authoring/pixi-transform-channels.ts +205 -0
  127. package/src/host/authoring/selection-remount-handoff.ts +23 -0
  128. package/src/host/authoring/source-persistence-backend.ts +373 -0
  129. package/src/host/authoring/source-refresh-revisions.ts +81 -0
  130. package/src/host/authoring/struct-write-pipe.ts +143 -0
  131. package/src/host/auto-frame-window.ts +89 -0
  132. package/src/host/binding-resolver.ts +393 -0
  133. package/src/host/browser-transpile.ts +631 -0
  134. package/src/host/canvas-entry-runtime.ts +95 -0
  135. package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
  136. package/src/host/components/HeaderTelemetry.tsx +341 -0
  137. package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
  138. package/src/host/components/ResolutionPicker.tsx +89 -0
  139. package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
  140. package/src/host/components/frame-debugger-model.ts +579 -0
  141. package/src/host/components/header-telemetry-model.ts +74 -0
  142. package/src/host/components/scene-document.tsx +447 -0
  143. package/src/host/components/utility-view-state.ts +87 -0
  144. package/src/host/components/world-root-stage-binding.tsx +133 -0
  145. package/src/host/components/world-root-stage.ts +1046 -0
  146. package/src/host/coverage/authoring-read-probe.ts +583 -0
  147. package/src/host/coverage/capability-coverage.ts +1587 -0
  148. package/src/host/coverage/coverage-accounting.ts +261 -0
  149. package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
  150. package/src/host/coverage/project-verb-coverage.ts +148 -0
  151. package/src/host/coverage/system-adapter-coverage.ts +373 -0
  152. package/src/host/design-system-stories/StoryLayout.tsx +104 -0
  153. package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
  154. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
  155. package/src/host/document-preview-three.ts +110 -0
  156. package/src/host/entry-adjudication.ts +89 -0
  157. package/src/host/game-location-guard.ts +150 -0
  158. package/src/host/game-module-access.ts +196 -0
  159. package/src/host/game-realm-page.ts +358 -0
  160. package/src/host/game-realm-reclaim.ts +55 -0
  161. package/src/host/game-realm-storage.ts +104 -0
  162. package/src/host/gameplay-export.ts +288 -0
  163. package/src/host/gameplay-recording.ts +717 -0
  164. package/src/host/gated-globals.ts +1511 -0
  165. package/src/host/history/json-history-resource.ts +254 -0
  166. package/src/host/ingest/registry.ts +261 -0
  167. package/src/host/instance-extract-actions.ts +120 -0
  168. package/src/host/instance-fork-actions.ts +120 -0
  169. package/src/host/play-control-hook.ts +26 -0
  170. package/src/host/playwright-shim.ts +479 -0
  171. package/src/host/projection/dom.ts +295 -0
  172. package/src/host/projection/pixi.ts +288 -0
  173. package/src/host/r3f-entry-runtime.ts +77 -0
  174. package/src/host/react-mount-runtime.ts +162 -0
  175. package/src/host/realm-services.ts +148 -0
  176. package/src/host/recording-preview.ts +106 -0
  177. package/src/host/roots/module-root.ts +203 -0
  178. package/src/host/roots/react-root.ts +181 -0
  179. package/src/host/same-realm-loop-gate.ts +544 -0
  180. package/src/host/scene-view-drawability.ts +69 -0
  181. package/src/host/sdk/tools.ts +31 -0
  182. package/src/host/served-bundle-runtime-modules.ts +331 -0
  183. package/src/host/server-log-bridge.ts +60 -0
  184. package/src/host/staged-projects.ts +24 -0
  185. package/src/host/stories/mounted-story-viewport-source.ts +64 -0
  186. package/src/host/stories/story-arg-descriptors.ts +50 -0
  187. package/src/host/stories/story-media-presence.ts +152 -0
  188. package/src/host/surface-content.ts +87 -0
  189. package/src/host/take-named-export.ts +23 -0
  190. package/src/host/three-ingest-runtime.ts +76 -0
  191. package/src/host/types-fastnoise-lite.d.ts +7 -0
  192. package/src/host/types-mikktspace.d.ts +20 -0
  193. package/src/host/types-troika-three-text.d.ts +7 -0
  194. package/src/host/use-active-performance-source.ts +53 -0
  195. package/src/host/viewport-pose-memory.ts +48 -0
  196. package/src/host/viewport-root-presentation.ts +40 -0
  197. package/src/ingest/active-ingest.ts +251 -0
  198. package/src/ingest/active-scene-navigation.ts +43 -0
  199. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
  200. package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
  201. package/src/ingest/capture-wait-report.ts +122 -0
  202. package/src/ingest/deferred-ingest-play.ts +216 -0
  203. package/src/ingest/deferred-ingest-session.ts +23 -0
  204. package/src/ingest/discovery-public-ingest.ts +242 -0
  205. package/src/ingest/dom-stub-mark.ts +7 -0
  206. package/src/ingest/entry-load.ts +56 -0
  207. package/src/ingest/game-contract-realm.ts +34 -0
  208. package/src/ingest/game-pointer-lock.ts +89 -0
  209. package/src/ingest/held-scene-repaint.ts +73 -0
  210. package/src/ingest/host-surface-box.ts +174 -0
  211. package/src/ingest/ingest-boot-viewport.ts +54 -0
  212. package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
  213. package/src/ingest/ingest-canvas-scene.ts +50 -0
  214. package/src/ingest/ingest-evidence-hook.ts +131 -0
  215. package/src/ingest/ingest-frame-snapshot.ts +183 -0
  216. package/src/ingest/ingest-play-commands.ts +134 -0
  217. package/src/ingest/ingest-play-control.ts +299 -0
  218. package/src/ingest/ingest-render-debug.ts +320 -0
  219. package/src/ingest/ingest-siblings.ts +487 -0
  220. package/src/ingest/ingest-status.ts +62 -0
  221. package/src/ingest/live-ingest-facet.ts +46 -0
  222. package/src/ingest/module-mode.ts +229 -0
  223. package/src/ingest/mount-canvas-ingest-root.ts +899 -0
  224. package/src/ingest/mount-coverage.ts +296 -0
  225. package/src/ingest/mount-dom-ingest-root.ts +282 -0
  226. package/src/ingest/mount-ingest-root.ts +744 -0
  227. package/src/ingest/mount-three-ingest-root.ts +366 -0
  228. package/src/ingest/resolve-canvas.ts +47 -0
  229. package/src/ingest/resolve-three.ts +123 -0
  230. package/src/ingest/served-bundle.ts +109 -0
  231. package/src/ingest/served-html-boot.ts +292 -0
  232. package/src/ingest/surface-canvas.ts +84 -0
  233. package/src/ingest/surface-dom.ts +84 -0
  234. package/src/ingest/surface-three.ts +128 -0
  235. package/src/ingest/types.ts +76 -0
  236. package/src/ingest/unmount-ingest-root.ts +224 -0
  237. package/src/navmesh/navmesh-actions.ts +27 -0
  238. package/src/navmesh/navmesh-handler.ts +237 -0
  239. package/src/navmesh/navmesh-workflow-store.ts +78 -0
  240. package/src/network/NetworkInspectorPanel.tsx +644 -0
  241. package/src/network/network-inspector-model.ts +225 -0
  242. package/src/play/play-boot-stall.ts +118 -0
  243. package/src/play/play-log-events.ts +26 -0
  244. package/src/play/play-mode.ts +2553 -0
  245. package/src/play/play-recording.ts +335 -0
  246. package/src/play/react-play-live-authoring.ts +165 -0
  247. package/src/play-bar/PlayBar.tsx +488 -0
  248. package/src/play-bar/PlayerCountPicker.tsx +100 -0
  249. package/src/profiler/FrameDebuggerPanel.tsx +458 -0
  250. package/src/profiler/PerformancePanel.tsx +1068 -0
  251. package/src/profiler/ProfilerPanel.tsx +53 -0
  252. package/src/profiler/frame-debugger-store.ts +97 -0
  253. package/src/profiler/main-thread-busy.ts +90 -0
  254. package/src/react/design-time-react-mount.ts +782 -0
  255. package/src/react/dom-authoring-adapter.ts +764 -0
  256. package/src/react/pasteboard-materialize.ts +154 -0
  257. package/src/react/react-inspector-section.tsx +2496 -0
  258. package/src/react/react-world-authoring-adapter.ts +3795 -0
  259. package/src/react/story-documents/story-args-section.ts +19 -0
  260. package/src/react/story-documents/story-documents.tsx +1380 -0
  261. package/src/react/story-paint-bounds.ts +94 -0
  262. package/src/react/ui-board-document.tsx +101 -0
  263. package/src/react/ui-board-title.ts +9 -0
  264. package/src/react/ui-component-board.ts +72 -0
  265. package/src/services/audio-pose-guard.ts +81 -0
  266. package/src/services/game-audio-unlock.ts +48 -0
  267. package/src/state-watch/StateWatchPanel.tsx +535 -0
  268. package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
  269. package/src/three/authoring/constraint-inspector-section.tsx +189 -0
  270. package/src/three/authoring/design-time-renderer.ts +189 -0
  271. package/src/three/authoring/model-asset-inspector-section.css +41 -0
  272. package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
  273. package/src/three/authoring/oid-source-persistence.ts +557 -0
  274. package/src/three/authoring/r3f-design-session.ts +1174 -0
  275. package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
  276. package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
  277. package/src/three/authoring/spatial-audio-handles.ts +215 -0
  278. package/src/three/authoring/spatial-collider-handles.ts +229 -0
  279. package/src/three/authoring/spatial-joint-handles.ts +114 -0
  280. package/src/three/authoring/spatial-light-handles.ts +178 -0
  281. package/src/three/authoring/spatial-lod-handles.ts +93 -0
  282. package/src/three/authoring/spatial-particle-handles.ts +368 -0
  283. package/src/three/authoring/three-authoring-adapter.ts +1953 -0
  284. package/src/three/authoring/three-scene-identity.ts +19 -0
  285. package/src/three/authoring/three-spatial-handles.ts +161 -0
  286. package/src/three/authoring/typed-three-inspector.ts +528 -0
  287. package/src/three/component-verbs/extract-menu.ts +71 -0
  288. package/src/three/component-verbs/fork-menu.ts +78 -0
  289. package/src/three/component-verbs/internals-menu.ts +91 -0
  290. package/src/three/story-documents/three-story-documents.tsx +607 -0
  291. package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
  292. package/src/three/three-board/board-framing.ts +412 -0
  293. package/src/three/three-board/board-layout.ts +401 -0
  294. package/src/three/three-board/board-scene.ts +901 -0
  295. package/src/three/three-board/three-component-board.ts +62 -0
  296. package/src/xstate/XStateBehaviorSection.tsx +130 -0
  297. package/src/xstate/XStateMachineInspector.tsx +566 -0
  298. package/src/xstate/character-animation-machine.fixture.ts +74 -0
  299. package/src/xstate/live-behaviors.ts +106 -0
  300. package/src/xstate/use-live-actor-state.ts +44 -0
  301. package/src/xstate/xstate-graph.ts +235 -0
  302. package/src/xstate/xstate-layout.ts +76 -0
@@ -0,0 +1,338 @@
1
+ import type { GenerationBilling, GenerationJob } from '@volter/editor-sdk/generations';
2
+ import { useEffect, useState, useSyncExternalStore } from 'react';
3
+ import './GenerationGallery.css';
4
+ import {
5
+ accountSnapshot,
6
+ accountVersion,
7
+ contributionAccount,
8
+ subscribeAccount,
9
+ } from '@volter/editor-core/account';
10
+ import { toolContributionSurfaces } from '@volter/editor-core/components/ToolContributionSurfaces';
11
+ import { ToolErrorBoundary } from '@volter/editor-core/components/ToolHost';
12
+ import { openToolDocument } from '@volter/editor-core/components/tool-documents';
13
+ import {
14
+ subscribeToolContributionPlay,
15
+ toolContributionPlay,
16
+ toolContributionPlayKey,
17
+ } from '@volter/editor-core/tool-contribution-play';
18
+ import {
19
+ getGenerationResultContribution,
20
+ getGlobalToolContributions,
21
+ getToolContributionClient,
22
+ subscribeToolContributions,
23
+ } from '@volter/editor-core/tool-loader';
24
+ import {
25
+ closeWorkspaceDocument,
26
+ openWorkspaceDocument,
27
+ type WorkspaceDocumentContentProps,
28
+ } from '@volter/editor-core/workspace-document-registry';
29
+ import { Button, TextInput } from '@volter/editor-sdk/widgets';
30
+ import {
31
+ acceptGenerationJob,
32
+ forgetGenerationJob,
33
+ generationJobsSnapshot,
34
+ inspectGenerationJob,
35
+ subscribeGenerationJobs,
36
+ } from './generation-jobs';
37
+ import {
38
+ GENERATION_STATE_LABEL,
39
+ generationCardState,
40
+ generationRemovable,
41
+ generationSaveRetryable,
42
+ } from './generation-presentation';
43
+
44
+ const PREFIX = 'generation:';
45
+ export const GENERATION_CREATE_DOCUMENT_ID = 'generation:create';
46
+
47
+ function billingLabel(billing: GenerationBilling): string {
48
+ if (billing.route === 'mock') return 'Free mock';
49
+ if (billing.route === 'managed') {
50
+ if (billing.settledCredits !== undefined) return `${billing.settledCredits} credits settled`;
51
+ if (billing.estimatedCredits !== undefined) return `Up to ${billing.estimatedCredits} credits`;
52
+ return 'VGAI subscription · estimate unavailable';
53
+ }
54
+ if (billing.settledAmount !== undefined) return `$${billing.settledAmount.toFixed(3)} settled`;
55
+ if (billing.estimatedAmount !== undefined) return `Up to $${billing.estimatedAmount.toFixed(3)}`;
56
+ return 'BYOK · estimate unavailable';
57
+ }
58
+
59
+ export function openGenerationDocument(job: GenerationJob): string {
60
+ return openWorkspaceDocument({
61
+ id: `${PREFIX}${job.id}`,
62
+ title: job.label,
63
+ kind: 'generation',
64
+ workspaceRole: 'authored-subject',
65
+ provenance: { origin: `${job.provider}:${job.externalId}` },
66
+ Content: GenerationDocumentContent,
67
+ closeable: true,
68
+ presentation: () => ({ kind: 'generation', id: job.id }),
69
+ });
70
+ }
71
+
72
+ export function openGenerationCreateDocument(): string {
73
+ return openWorkspaceDocument({
74
+ id: GENERATION_CREATE_DOCUMENT_ID,
75
+ title: 'Create Asset',
76
+ kind: 'generation',
77
+ workspaceRole: 'workspace-task',
78
+ provenance: { origin: 'registered generation tools' },
79
+ Content: GenerationCreateDocumentContent,
80
+ closeable: true,
81
+ presentation: () => ({ kind: 'generation', id: 'create' }),
82
+ });
83
+ }
84
+
85
+ export function GenerationCreateDocumentContent() {
86
+ useSyncExternalStore(subscribeToolContributions, getGlobalToolContributions);
87
+ useSyncExternalStore(subscribeAccount, accountVersion, accountVersion);
88
+ const account = accountSnapshot();
89
+ const [query, setQuery] = useState('');
90
+ const normalized = query.trim().toLowerCase();
91
+ const tools = getGlobalToolContributions()
92
+ .filter(
93
+ (item) => item.point === 'workspace.document' && item.tool?.generation?.role === 'submit',
94
+ )
95
+ .filter((item) => {
96
+ if (!normalized) return true;
97
+ return [
98
+ item.title,
99
+ item.tool?.generation?.provider,
100
+ item.tool?.summary,
101
+ item.tool?.description,
102
+ ]
103
+ .filter(Boolean)
104
+ .some((value) => String(value).toLowerCase().includes(normalized));
105
+ });
106
+ const providers = new Map<string, typeof tools>();
107
+ for (const item of tools) {
108
+ const provider = item.tool?.generation?.provider ?? 'other';
109
+ providers.set(provider, [...(providers.get(provider) ?? []), item]);
110
+ }
111
+ return (
112
+ <div className="vgai-tool-page" data-testid="generation-create-catalog">
113
+ <header className="vgai-tool-header">
114
+ <div className="vgai-tool-eyebrow">Installed provider extensions</div>
115
+ <h2 className="vgai-tool-title">Create an asset</h2>
116
+ <p className="vgai-tool-description">
117
+ Choose a native provider operation. Mock, managed, and BYOK execution remain explicit in
118
+ the operation you open. Current default: {account.preferredRoute}.
119
+ </p>
120
+ </header>
121
+ <TextInput
122
+ type="search"
123
+ aria-label="Search generation tools"
124
+ placeholder="Search operations or providers…"
125
+ value={query}
126
+ onChange={(event) => setQuery(event.target.value)}
127
+ />
128
+ {[...providers.entries()].map(([provider, contributions]) => (
129
+ <section className="vgai-tool-card" key={provider}>
130
+ <h3 className="vgai-tool-section-title">{provider}</h3>
131
+ <div className="vgai-generation-create-grid">
132
+ {contributions.map((contribution) => (
133
+ <Button
134
+ variant="ghost"
135
+ className="vgai-generation-create-card"
136
+ key={contribution.id}
137
+ onClick={() => openToolDocument(null, contribution.id)}
138
+ >
139
+ <strong>{contribution.title}</strong>
140
+ <span>{contribution.tool?.summary}</span>
141
+ </Button>
142
+ ))}
143
+ </div>
144
+ </section>
145
+ ))}
146
+ {tools.length === 0 && (
147
+ <div className="vgai-generation-document-empty">
148
+ No installed generation operation matches this search.
149
+ </div>
150
+ )}
151
+ </div>
152
+ );
153
+ }
154
+
155
+ function ProviderResult({
156
+ job,
157
+ result,
158
+ documentId,
159
+ active,
160
+ }: {
161
+ job: GenerationJob;
162
+ result: unknown;
163
+ documentId: string;
164
+ active: boolean;
165
+ }) {
166
+ // Subscribe to contribution discovery/HMR even though this document does
167
+ // not otherwise consume the global document contribution list.
168
+ useSyncExternalStore(subscribeToolContributions, getGlobalToolContributions);
169
+ useSyncExternalStore(
170
+ subscribeToolContributionPlay,
171
+ toolContributionPlayKey,
172
+ toolContributionPlayKey,
173
+ );
174
+ const contribution = getGenerationResultContribution(job.poll.tool, job, result);
175
+ if (!contribution) {
176
+ return <pre className="vgai-generation-native-result">{JSON.stringify(result, null, 2)}</pre>;
177
+ }
178
+ const Component = contribution.Component;
179
+ return (
180
+ <ToolErrorBoundary file={contribution.file}>
181
+ <Component
182
+ tool={contribution.tool}
183
+ contributionId={contribution.id}
184
+ client={getToolContributionClient()}
185
+ surfaces={toolContributionSurfaces}
186
+ account={contributionAccount()}
187
+ play={toolContributionPlay()}
188
+ documentId={documentId}
189
+ active={active}
190
+ job={job}
191
+ result={result}
192
+ />
193
+ </ToolErrorBoundary>
194
+ );
195
+ }
196
+
197
+ export function GenerationDocumentContent({ documentId, active }: WorkspaceDocumentContentProps) {
198
+ const { jobs } = useSyncExternalStore(
199
+ subscribeGenerationJobs,
200
+ generationJobsSnapshot,
201
+ generationJobsSnapshot,
202
+ );
203
+ const jobId = documentId.startsWith(PREFIX) ? documentId.slice(PREFIX.length) : documentId;
204
+ const job = jobs.find((candidate) => candidate.id === jobId);
205
+ const [result, setResult] = useState<unknown>();
206
+ const [loading, setLoading] = useState(false);
207
+ const [error, setError] = useState<string | null>(null);
208
+ const [busy, setBusy] = useState(false);
209
+
210
+ const inspect = async (current: GenerationJob) => {
211
+ setLoading(true);
212
+ setError(null);
213
+ try {
214
+ setResult(await inspectGenerationJob(current));
215
+ } catch (cause) {
216
+ setError(cause instanceof Error ? cause.message : String(cause));
217
+ } finally {
218
+ setLoading(false);
219
+ }
220
+ };
221
+
222
+ useEffect(() => {
223
+ if (!job) return;
224
+ void inspect(job);
225
+ // Re-inspect when a background transition reaches a new provider state;
226
+ // polling within one state does not create a render loop.
227
+ }, [job?.id, job?.status]);
228
+
229
+ if (!job) {
230
+ return (
231
+ <div className="vgai-generation-document-empty">
232
+ This generation is no longer in project activity.
233
+ </div>
234
+ );
235
+ }
236
+
237
+ // A finished job's recorded error is a failed SAVE, and the server has
238
+ // already spent its automatic attempts — so it is stated here as a result,
239
+ // with no promise of another try, and the Retry button is the door.
240
+ const alert =
241
+ error ??
242
+ (generationCardState(job) === 'save-failed'
243
+ ? `Could not save into the project: ${job.pollError}`
244
+ : null);
245
+
246
+ return (
247
+ <div className="vgai-generation-document" data-status={job.status}>
248
+ <header className="vgai-generation-document-header">
249
+ <div>
250
+ <div className="vgai-tool-eyebrow">
251
+ {job.provider} · {job.operation}
252
+ </div>
253
+ <h2 className="vgai-tool-title">{job.label}</h2>
254
+ <div className="vgai-generation-job-meta">
255
+ {GENERATION_STATE_LABEL[generationCardState(job)]} · {billingLabel(job.billing)}
256
+ </div>
257
+ </div>
258
+ <div className="vgai-tool-actions">
259
+ <Button
260
+ size="compact"
261
+ variant="ghost"
262
+ disabled={loading}
263
+ onClick={() => void inspect(job)}
264
+ >
265
+ {loading ? 'Refreshing…' : 'Refresh'}
266
+ </Button>
267
+ {generationSaveRetryable(job, error) && (
268
+ // ONE rule, shared with the gallery card: the server reconciler
269
+ // saves a finished output on its own, so this is only ever the
270
+ // RETRY door for a save that failed — never an approval gate, and
271
+ // never a blocking browser dialog.
272
+ <Button
273
+ size="compact"
274
+ variant="primary"
275
+ disabled={busy}
276
+ onClick={() => {
277
+ setBusy(true);
278
+ setError(null);
279
+ void acceptGenerationJob(job)
280
+ .catch((cause) =>
281
+ setError(cause instanceof Error ? cause.message : String(cause)),
282
+ )
283
+ .finally(() => setBusy(false));
284
+ }}
285
+ >
286
+ {busy ? 'Saving…' : 'Retry save'}
287
+ </Button>
288
+ )}
289
+ </div>
290
+ </header>
291
+
292
+ {job.message && <div className="vgai-tool-status">{job.message}</div>}
293
+ {alert && (
294
+ <div className="vgai-tool-status" data-tone="error" role="alert">
295
+ {alert}
296
+ </div>
297
+ )}
298
+
299
+ <section className="vgai-generation-result" aria-label="Generation result">
300
+ {result === undefined ? (
301
+ <div className="vgai-generation-document-empty">
302
+ {loading ? 'Loading provider result…' : 'No provider result is available yet.'}
303
+ </div>
304
+ ) : (
305
+ <ProviderResult job={job} result={result} documentId={documentId} active={active} />
306
+ )}
307
+ </section>
308
+
309
+ {job.outputPaths && job.outputPaths.length > 0 && (
310
+ <section className="vgai-generation-output-paths">
311
+ <strong>Project assets</strong>
312
+ {job.outputPaths.map((path) => (
313
+ <code key={path}>{path}</code>
314
+ ))}
315
+ </section>
316
+ )}
317
+
318
+ {generationRemovable(job) && (
319
+ <div className="vgai-generation-document-footer">
320
+ <Button
321
+ size="compact"
322
+ variant="ghost"
323
+ disabled={busy}
324
+ onClick={() => {
325
+ setBusy(true);
326
+ void forgetGenerationJob(job.id)
327
+ .then(() => closeWorkspaceDocument(documentId))
328
+ .catch((cause) => setError(cause instanceof Error ? cause.message : String(cause)))
329
+ .finally(() => setBusy(false));
330
+ }}
331
+ >
332
+ Remove from activity
333
+ </Button>
334
+ </div>
335
+ )}
336
+ </div>
337
+ );
338
+ }
@@ -0,0 +1,128 @@
1
+ import { assertEditorServerResponse, editorServerJson } from '@volter/editor-core/editor-server-response';
2
+ import { runProjectTool } from '@volter/editor-core/project-tools';
3
+ import type { GenerationJob, GenerationJobsDocument } from '@volter/editor-sdk/generations';
4
+
5
+ const EMPTY: GenerationJobsDocument = { version: 1, jobs: [] };
6
+ let document = EMPTY;
7
+ let error: string | null = null;
8
+ let version = 0;
9
+ let polling = false;
10
+ const listeners = new Set<() => void>();
11
+
12
+ function publish(next: GenerationJobsDocument, nextError: string | null = null): void {
13
+ document = next;
14
+ error = nextError;
15
+ version++;
16
+ for (const listener of listeners) listener();
17
+ }
18
+
19
+ export function generationJobsSnapshot(): GenerationJobsDocument {
20
+ return document;
21
+ }
22
+
23
+ export function generationJobsError(): string | null {
24
+ return error;
25
+ }
26
+
27
+ export function generationJobsVersion(): number {
28
+ return version;
29
+ }
30
+
31
+ export function subscribeGenerationJobs(listener: () => void): () => void {
32
+ listeners.add(listener);
33
+ return () => listeners.delete(listener);
34
+ }
35
+
36
+ export function generationJobIsActive(job: GenerationJob): boolean {
37
+ return job.status === 'queued' || job.status === 'running';
38
+ }
39
+
40
+ export function generationJobIsUnread(job: GenerationJob): boolean {
41
+ return (
42
+ !generationJobIsActive(job) &&
43
+ (!job.readAt || Date.parse(job.readAt) < Date.parse(job.updatedAt))
44
+ );
45
+ }
46
+
47
+ export function generationUnreadCount(jobs: readonly GenerationJob[]): number {
48
+ return jobs.filter(generationJobIsUnread).length;
49
+ }
50
+
51
+ export function generationAttentionCount(jobs: readonly GenerationJob[]): number {
52
+ return jobs.filter((job) => generationJobIsActive(job) || generationJobIsUnread(job)).length;
53
+ }
54
+
55
+ export async function refreshGenerationJobs(): Promise<void> {
56
+ try {
57
+ const response = await fetch('/__editor/generations');
58
+ publish(
59
+ await editorServerJson<GenerationJobsDocument>(
60
+ response,
61
+ 'Generation activity request failed',
62
+ ),
63
+ );
64
+ } catch (cause) {
65
+ error = cause instanceof Error ? cause.message : String(cause);
66
+ version++;
67
+ for (const listener of listeners) listener();
68
+ }
69
+ }
70
+
71
+ export async function pollActiveGenerationJobs(): Promise<void> {
72
+ if (polling) return;
73
+ polling = true;
74
+ try {
75
+ const response = await fetch('/__editor/generations/reconcile', { method: 'POST' });
76
+ // A page fallback answers this POST `200 OK` and nothing reconciles, so
77
+ // `response.ok` alone reported a completed reconciliation that never ran.
78
+ assertEditorServerResponse(response, 'Generation reconciliation failed');
79
+ await refreshGenerationJobs();
80
+ } finally {
81
+ polling = false;
82
+ }
83
+ }
84
+
85
+ /** Run the provider's native poll operation and return its unnormalized result. */
86
+ export async function inspectGenerationJob(job: GenerationJob): Promise<unknown> {
87
+ const outcome = await runProjectTool(job.poll.tool, job.poll.input);
88
+ if (!outcome.ok) throw new Error(outcome.error.message);
89
+ if (outcome.generationWarning) throw new Error(outcome.generationWarning);
90
+ await refreshGenerationJobs();
91
+ await markGenerationJobRead(job.id);
92
+ return outcome.data;
93
+ }
94
+
95
+ export async function acceptGenerationJob(job: GenerationJob): Promise<void> {
96
+ if (!job.accept) throw new Error('This completed generation has no project asset to add.');
97
+ const outcome = await runProjectTool(job.accept.tool, job.accept.input, { confirm: true });
98
+ if (!outcome.ok) throw new Error(outcome.error.message);
99
+ if (outcome.generationWarning) throw new Error(outcome.generationWarning);
100
+ await refreshGenerationJobs();
101
+ await markGenerationJobRead(job.id);
102
+ }
103
+
104
+ export async function markGenerationJobRead(id: string): Promise<void> {
105
+ const response = await fetch(`/__editor/generations/${encodeURIComponent(id)}/read`, {
106
+ method: 'POST',
107
+ });
108
+ assertEditorServerResponse(response, 'Mark generation read failed');
109
+ await refreshGenerationJobs();
110
+ }
111
+
112
+ export async function forgetGenerationJob(id: string): Promise<void> {
113
+ const response = await fetch(`/__editor/generations/${encodeURIComponent(id)}`, {
114
+ method: 'DELETE',
115
+ });
116
+ assertEditorServerResponse(response, 'Forget generation job failed');
117
+ await refreshGenerationJobs();
118
+ }
119
+
120
+ /** Project-lifetime observation. The editor server owns provider
121
+ * reconciliation; browser tabs only refresh the resulting durable ledger. */
122
+ export function startGenerationJobActivity(): () => void {
123
+ void refreshGenerationJobs();
124
+ const refreshTimer = window.setInterval(() => void refreshGenerationJobs(), 2_000);
125
+ return () => {
126
+ window.clearInterval(refreshTimer);
127
+ };
128
+ }