@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,476 @@
1
+ /**
2
+ * THE BUILD PROFILES PANEL — the configuration surface the `build-profiles`
3
+ * document draws: the project's build-role configurations as a target list,
4
+ * the name/resolution/app-id fields it writes back into `vgai.project.json`
5
+ * through the project's file history, the Build trigger, and the finished
6
+ * build's report. The LOG is not here: it streams into the Build Output tab
7
+ * (the native Output panel’s Build channel), which is the whole point of the split — a
8
+ * build takes real seconds and a modal would block the viewport.
9
+ */
10
+
11
+ import { workspaceHistoryService } from '@volter/editor-core/components/workspace-history';
12
+ import { getDownloadUrl, readProjectTextFile, saveFile } from '@volter/editor-core/editor-api';
13
+ import { getProjectFileHistory } from '@volter/editor-core/history/project-file-history';
14
+ import { faGlobe } from '@fortawesome/free-solid-svg-icons';
15
+ import type { BuildReport } from '@volter/editor-sdk/session/build-report';
16
+ import {
17
+ accent,
18
+ Button,
19
+ bg,
20
+ border,
21
+ danger,
22
+ EditorIcon,
23
+ EditorListButton,
24
+ fontMono,
25
+ fontSizeVar,
26
+ radius,
27
+ spaceVar,
28
+ success,
29
+ TextInput,
30
+ text,
31
+ } from '@volter/editor-sdk/widgets';
32
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
33
+ import {
34
+ type BuildTarget,
35
+ buildSessionVersion,
36
+ cancelBuild,
37
+ getBuildSession,
38
+ resetBuildSession,
39
+ startBuild,
40
+ subscribeBuildSession,
41
+ useBuildProfiles,
42
+ } from './build-session';
43
+ import { formatBytes } from './format-bytes';
44
+
45
+ interface ProjectConfig {
46
+ name: string;
47
+ resolution: { width: number; height: number };
48
+ appId: string;
49
+ }
50
+
51
+ const MANIFEST_PATH = 'vgai.project.json';
52
+
53
+ const DEFAULT_CONFIG: ProjectConfig = {
54
+ name: 'My Game',
55
+ resolution: { width: 1280, height: 720 },
56
+ appId: 'com.example.mygame',
57
+ };
58
+
59
+ export function BuildProfilesPanel() {
60
+ useSyncExternalStore(subscribeBuildSession, buildSessionVersion, buildSessionVersion);
61
+ const session = getBuildSession();
62
+ // Session history via the workspace-history bridge (see its doc comment);
63
+ // the per-service manager is cached inside getProjectFileHistory.
64
+ const history = workspaceHistoryService();
65
+ const fileHistory = history ? getProjectFileHistory(history) : null;
66
+ const [config, setConfig] = useState<ProjectConfig>(DEFAULT_CONFIG);
67
+ const TARGETS = useBuildProfiles();
68
+ const [selected, setSelected] = useState<BuildTarget>('');
69
+ useEffect(() => {
70
+ if (!selected && TARGETS.length > 0) setSelected(TARGETS[0]?.id ?? '');
71
+ }, [TARGETS, selected]);
72
+ const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
73
+
74
+ const loadConfig = useCallback(async () => {
75
+ const manifestText = await readProjectTextFile(MANIFEST_PATH);
76
+ const manifest = manifestText
77
+ ? (JSON.parse(manifestText) as Partial<ProjectConfig>)
78
+ : DEFAULT_CONFIG;
79
+ setConfig({
80
+ ...DEFAULT_CONFIG,
81
+ ...manifest,
82
+ resolution: { ...DEFAULT_CONFIG.resolution, ...manifest.resolution },
83
+ });
84
+ }, []);
85
+
86
+ useEffect(() => {
87
+ void loadConfig().catch(() => {});
88
+ return (
89
+ fileHistory?.subscribeAll((path) => {
90
+ if (path === MANIFEST_PATH) void loadConfig().catch(() => {});
91
+ }) ?? (() => {})
92
+ );
93
+ }, [fileHistory, loadConfig]);
94
+
95
+ useEffect(
96
+ () => () => {
97
+ if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
98
+ },
99
+ [],
100
+ );
101
+
102
+ // Merge only the build-owned fields into the manifest. Roots and every
103
+ // unrelated authored field must survive a Build Profiles edit.
104
+ const saveConfig = useCallback(
105
+ (cfg: ProjectConfig) => {
106
+ if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
107
+ saveTimerRef.current = setTimeout(async () => {
108
+ const currentText = await readProjectTextFile(MANIFEST_PATH);
109
+ if (!currentText)
110
+ throw new Error(`Cannot edit build settings: ${MANIFEST_PATH} is missing`);
111
+ const manifest = JSON.parse(currentText) as Record<string, unknown>;
112
+ const content = `${JSON.stringify(
113
+ { ...manifest, name: cfg.name, resolution: cfg.resolution, appId: cfg.appId },
114
+ null,
115
+ 2,
116
+ )}\n`;
117
+ if (!fileHistory) {
118
+ await saveFile(MANIFEST_PATH, content);
119
+ return;
120
+ }
121
+ await fileHistory
122
+ .write(MANIFEST_PATH, content, {
123
+ label: 'Edit Build Settings',
124
+ kind: 'settings',
125
+ contentType: 'application/json',
126
+ })
127
+ .catch(() => void loadConfig().catch(() => {}));
128
+ }, 500);
129
+ },
130
+ [fileHistory, loadConfig],
131
+ );
132
+
133
+ const updateConfig = useCallback(
134
+ (patch: Partial<ProjectConfig>) => {
135
+ setConfig((prev) => {
136
+ const next = { ...prev, ...patch };
137
+ saveConfig(next);
138
+ return next;
139
+ });
140
+ },
141
+ [saveConfig],
142
+ );
143
+
144
+ const updateResolution = useCallback(
145
+ (key: 'width' | 'height', value: number) => {
146
+ setConfig((prev) => {
147
+ const next = { ...prev, resolution: { ...prev.resolution, [key]: Math.round(value) } };
148
+ saveConfig(next);
149
+ return next;
150
+ });
151
+ },
152
+ [saveConfig],
153
+ );
154
+
155
+ const phase = session.phase;
156
+ const artifact = session.result?.report?.artifact ?? null;
157
+
158
+ return (
159
+ <div
160
+ data-testid="build-profiles-document"
161
+ style={{ display: 'flex', flex: 1, minHeight: 0, background: bg[1], pointerEvents: 'auto' }}
162
+ >
163
+ {/* Left: target list (the "profiles") */}
164
+ <div style={targetListStyle}>
165
+ {TARGETS.map((t) => (
166
+ <EditorListButton
167
+ key={t.id}
168
+ selected={selected === t.id}
169
+ disabled={phase === 'building'}
170
+ onClick={() => {
171
+ if (phase !== 'building') setSelected(t.id);
172
+ }}
173
+ style={{
174
+ padding: `${spaceVar[4]} ${spaceVar[6]}`,
175
+ opacity: phase === 'building' && selected !== t.id ? 0.4 : 1,
176
+ }}
177
+ >
178
+ <div style={{ display: 'flex', alignItems: 'center', gap: spaceVar[4] }}>
179
+ <EditorIcon
180
+ icon={faGlobe}
181
+ style={{ fontSize: fontSizeVar.lg, color: selected === t.id ? accent : text[2] }}
182
+ />
183
+ <div>
184
+ <div
185
+ style={{
186
+ fontSize: fontSizeVar.md,
187
+ fontWeight: 600,
188
+ color: selected === t.id ? text[1] : text[2],
189
+ }}
190
+ >
191
+ {t.label}
192
+ </div>
193
+ <div style={{ fontSize: fontSizeVar.sm, color: text[3], marginTop: 1 }}>
194
+ {t.subtitle}
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </EditorListButton>
199
+ ))}
200
+ </div>
201
+
202
+ {/* Right: settings + phase-dependent action row */}
203
+ <div
204
+ style={{
205
+ flex: 1,
206
+ display: 'flex',
207
+ flexDirection: 'column',
208
+ minWidth: 0,
209
+ padding: `${spaceVar[5]} ${spaceVar[8]}`,
210
+ maxWidth: 560,
211
+ }}
212
+ >
213
+ <div style={sectionHeaderStyle}>Project</div>
214
+
215
+ <div style={fieldRowStyle}>
216
+ <label style={labelStyle}>Name</label>
217
+ <TextInput
218
+ className="vgai-input"
219
+ style={{ flex: 1 }}
220
+ value={config.name}
221
+ onChange={(e) => updateConfig({ name: e.target.value })}
222
+ />
223
+ </div>
224
+
225
+ <div style={fieldRowStyle}>
226
+ <label style={labelStyle}>Resolution</label>
227
+ <div style={{ display: 'flex', alignItems: 'center', gap: spaceVar[2], flex: 1 }}>
228
+ <TextInput
229
+ type="number"
230
+ className="vgai-input"
231
+ style={{ width: 0, flex: 1 }}
232
+ value={config.resolution.width}
233
+ onChange={(e) => {
234
+ const v = Number.parseInt(e.target.value, 10);
235
+ if (Number.isFinite(v) && v > 0) updateResolution('width', v);
236
+ }}
237
+ />
238
+ <span style={{ fontSize: fontSizeVar.sm, color: text[3] }}>&times;</span>
239
+ <TextInput
240
+ type="number"
241
+ className="vgai-input"
242
+ style={{ width: 0, flex: 1 }}
243
+ value={config.resolution.height}
244
+ onChange={(e) => {
245
+ const v = Number.parseInt(e.target.value, 10);
246
+ if (Number.isFinite(v) && v > 0) updateResolution('height', v);
247
+ }}
248
+ />
249
+ </div>
250
+ </div>
251
+
252
+ <div style={fieldRowStyle}>
253
+ <label style={labelStyle}>App ID</label>
254
+ <TextInput
255
+ className="vgai-input"
256
+ style={{ flex: 1 }}
257
+ value={config.appId}
258
+ onChange={(e) => updateConfig({ appId: e.target.value })}
259
+ />
260
+ </div>
261
+
262
+ <div style={{ ...fieldRowStyle, marginTop: spaceVar[2] }}>
263
+ <label style={labelStyle}>Output</label>
264
+ <span style={{ fontSize: fontSizeVar.md, color: text[2] }}>
265
+ {artifact ?? 'Artifact appears here after a build'}
266
+ </span>
267
+ </div>
268
+
269
+ {/* Action row: Build / Cancel / result — the log itself streams into
270
+ the Build Output utility (the split's whole point). */}
271
+ <div
272
+ style={{
273
+ marginTop: spaceVar[6],
274
+ display: 'flex',
275
+ alignItems: 'center',
276
+ gap: spaceVar[4],
277
+ paddingTop: spaceVar[4],
278
+ borderTop: `1px solid ${border[1]}`,
279
+ }}
280
+ >
281
+ {phase === 'building' ? (
282
+ <>
283
+ <span style={{ fontSize: fontSizeVar.md, fontWeight: 600, color: accent }}>
284
+ Building for {TARGETS.find((t) => t.id === session.target)?.label ?? session.target}
285
+ … (log in Build Output below)
286
+ </span>
287
+ <Button size="compact" onClick={cancelBuild}>
288
+ Cancel
289
+ </Button>
290
+ </>
291
+ ) : (
292
+ <>
293
+ {phase === 'done' && session.result && (
294
+ <span
295
+ data-testid="build-profiles-result"
296
+ style={{
297
+ fontSize: fontSizeVar.md,
298
+ fontWeight: 600,
299
+ color: session.result.ok ? success : danger,
300
+ }}
301
+ >
302
+ {session.result.ok
303
+ ? '✓ Build complete'
304
+ : `✗ Build failed (exit ${session.result.code ?? 'unknown'})`}
305
+ </span>
306
+ )}
307
+ {phase === 'done' && session.result?.ok && artifact && (
308
+ <a
309
+ href={getDownloadUrl(artifact)}
310
+ download={artifact}
311
+ className="vgai-btn"
312
+ data-variant="secondary"
313
+ data-size="default"
314
+ style={{
315
+ textDecoration: 'none',
316
+ display: 'inline-flex',
317
+ }}
318
+ >
319
+ Download
320
+ </a>
321
+ )}
322
+ <Button
323
+ variant="primary"
324
+ data-testid="build-profiles-build"
325
+ onClick={() => {
326
+ if (!selected) return;
327
+ if (phase === 'done') resetBuildSession();
328
+ void startBuild(selected);
329
+ }}
330
+ style={{ marginLeft: 'auto' }}
331
+ >
332
+ {phase === 'done' ? 'Build Again' : 'Build'}
333
+ </Button>
334
+ </>
335
+ )}
336
+ </div>
337
+
338
+ {phase === 'done' && session.result?.report && (
339
+ <BuildReportSummary report={session.result.report} />
340
+ )}
341
+ </div>
342
+ </div>
343
+ );
344
+ }
345
+
346
+ function BuildReportSummary({ report }: { report: BuildReport }) {
347
+ const largest = report.largestFiles[0]?.bytes ?? 1;
348
+ return (
349
+ <section
350
+ data-testid="build-report"
351
+ style={{
352
+ marginTop: spaceVar[6],
353
+ paddingTop: spaceVar[5],
354
+ borderTop: `1px solid ${border[1]}`,
355
+ }}
356
+ >
357
+ <div style={sectionHeaderStyle}>Build report</div>
358
+ <div style={{ display: 'flex', gap: spaceVar[4], marginBottom: spaceVar[5] }}>
359
+ <BuildMetric label="Package" value={formatBytes(report.artifactBytes)} />
360
+ <BuildMetric label="Output" value={formatBytes(report.outputBytes)} />
361
+ <BuildMetric label="Files" value={String(report.fileCount)} />
362
+ </div>
363
+ <div style={{ fontSize: fontSizeVar.sm, color: text[3], marginBottom: spaceVar[2] }}>
364
+ Largest output files
365
+ </div>
366
+ <div style={{ display: 'grid', gap: 3 }}>
367
+ {report.largestFiles.slice(0, 8).map((file) => (
368
+ <div
369
+ key={file.path}
370
+ style={{
371
+ display: 'grid',
372
+ gridTemplateColumns: 'minmax(0, 1fr) max-content',
373
+ gap: spaceVar[3],
374
+ }}
375
+ >
376
+ <div
377
+ title={file.path}
378
+ style={{ position: 'relative', minWidth: 0, overflow: 'hidden', borderRadius: 2 }}
379
+ >
380
+ <div
381
+ aria-hidden="true"
382
+ style={{
383
+ position: 'absolute',
384
+ inset: 0,
385
+ width: `${Math.max(2, (file.bytes / largest) * 100)}%`,
386
+ background: 'color-mix(in srgb, var(--vgai-accent) 16%, transparent)',
387
+ }}
388
+ />
389
+ <div
390
+ style={{
391
+ position: 'relative',
392
+ padding: `${spaceVar[1]} ${spaceVar[2]}`,
393
+ overflow: 'hidden',
394
+ textOverflow: 'ellipsis',
395
+ whiteSpace: 'nowrap',
396
+ fontFamily: fontMono,
397
+ fontSize: fontSizeVar.sm,
398
+ color: text[2],
399
+ }}
400
+ >
401
+ {file.path}
402
+ </div>
403
+ </div>
404
+ <div
405
+ style={{
406
+ textAlign: 'right',
407
+ padding: `${spaceVar[1]} 0`,
408
+ fontSize: fontSizeVar.sm,
409
+ color: text[2],
410
+ }}
411
+ >
412
+ {formatBytes(file.bytes)}
413
+ </div>
414
+ </div>
415
+ ))}
416
+ </div>
417
+ </section>
418
+ );
419
+ }
420
+
421
+ function BuildMetric({ label, value }: { label: string; value: string }) {
422
+ return (
423
+ <div
424
+ style={{
425
+ flex: 1,
426
+ padding: `${spaceVar[3]} ${spaceVar[4]}`,
427
+ background: bg[2],
428
+ borderRadius: radius.sm,
429
+ }}
430
+ >
431
+ <div style={{ fontSize: fontSizeVar.sm, color: text[3] }}>{label}</div>
432
+ <div
433
+ style={{
434
+ marginTop: spaceVar[1],
435
+ fontSize: fontSizeVar.lg,
436
+ fontWeight: 650,
437
+ color: text[1],
438
+ }}
439
+ >
440
+ {value}
441
+ </div>
442
+ </div>
443
+ );
444
+ }
445
+
446
+ /* ---- Styles (carried from the old panel) ---- */
447
+
448
+ const targetListStyle: React.CSSProperties = {
449
+ width: 160,
450
+ flexShrink: 0,
451
+ borderRight: `1px solid ${border[1]}`,
452
+ overflowY: 'auto',
453
+ };
454
+
455
+ const sectionHeaderStyle: React.CSSProperties = {
456
+ fontSize: fontSizeVar.sm,
457
+ fontWeight: 700,
458
+ textTransform: 'uppercase',
459
+ color: text[3],
460
+ letterSpacing: '0.05em',
461
+ marginBottom: spaceVar[3],
462
+ };
463
+
464
+ const fieldRowStyle: React.CSSProperties = {
465
+ display: 'flex',
466
+ alignItems: 'center',
467
+ gap: spaceVar[3],
468
+ marginBottom: spaceVar[2],
469
+ };
470
+
471
+ const labelStyle: React.CSSProperties = {
472
+ width: 65,
473
+ fontSize: fontSizeVar.base,
474
+ color: text[2],
475
+ flexShrink: 0,
476
+ };