@vgai/editor 0.4.0 → 0.4.1

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 (137) hide show
  1. package/dist/assets/{axe-BJjvBeOZ.js → axe-N4cNyzfs.js} +1 -1
  2. package/dist/assets/{browserAll-Dn0pgD87.js → browserAll-BIBT0Z2H.js} +1 -1
  3. package/dist/assets/{create-runtime-BF9OD-U8.js → create-runtime-DxLhdk4U.js} +1 -1
  4. package/dist/assets/{game-u6c2Patu.js → game-4NbNRROm.js} +1 -1
  5. package/dist/assets/{game-umrHIqFB.js → game-B3UH5LjS.js} +1 -1
  6. package/dist/assets/{game-BR0GBNgF.js → game-ByxRLJi1.js} +1 -1
  7. package/dist/assets/{game-DeTGdgYt.js → game-CJ5BqOYG.js} +1 -1
  8. package/dist/assets/{game-DQxhCYsZ.js → game-CSJm1kwB.js} +1 -1
  9. package/dist/assets/{game-CLNm2g0y.js → game-D9Mo4L3Q.js} +1 -1
  10. package/dist/assets/{game-B346oj_f.js → game-DJmU9BQF.js} +1 -1
  11. package/dist/assets/{game-BCNN63uV.js → game-ZqGLgxRj.js} +1 -1
  12. package/dist/assets/{game-loop-t5W_oqxz.js → game-loop-9YP76JB1.js} +1 -1
  13. package/dist/assets/{game-source-DzE9taML.js → game-source-DLKG9pmj.js} +1 -1
  14. package/dist/assets/{index-DbQzGLxv.js → index-8A4JLNIp.js} +902 -892
  15. package/dist/assets/{index-CgcH1RpF.js → index-Brzr17q0.js} +1 -1
  16. package/dist/assets/{index-D3tcNB2T.js → index-CnKtvrru.js} +1 -1
  17. package/dist/assets/{index-Fc8Ezr20.js → index-D-WR11Jw.js} +1 -1
  18. package/dist/assets/index-DiUacMXR.css +1 -0
  19. package/dist/assets/{ingest-siblings-CW_xbvrF.js → ingest-siblings-Cmx39OBK.js} +1 -1
  20. package/dist/assets/{mount-manifest-DgUscyaD.js → mount-manifest-kzdZgkkc.js} +1 -1
  21. package/dist/assets/{react-18-C4tVCii_.js → react-18-DvWsdwa_.js} +1 -1
  22. package/dist/assets/webworkerAll-C9GR-KrR.js +1 -0
  23. package/dist/assets/{world-documents-BSaA5YIF.js → world-documents-DM_aIMAW.js} +1 -1
  24. package/dist/index.html +2 -2
  25. package/dist-server/packaged.mjs +27125 -1075
  26. package/package.json +2 -2
  27. package/server/asset-history-snapshots.ts +189 -0
  28. package/server/asset-library-routes.ts +404 -53
  29. package/server/catalog-import-recipe.ts +97 -0
  30. package/server/catalog-reimport.ts +141 -0
  31. package/server/cloud-asset-catalog.ts +71 -5
  32. package/server/dev.ts +1 -1
  33. package/server/editor-server.ts +106 -51
  34. package/server/editor-sse.ts +5 -0
  35. package/server/generative-execution-context.ts +45 -0
  36. package/server/model-import-conversion.ts +345 -0
  37. package/server/packaged.ts +1 -1
  38. package/server/project-hmr-files.ts +3 -5
  39. package/server/project-output-writer.ts +440 -36
  40. package/server/project-tools.ts +529 -0
  41. package/server/server-utils.ts +14 -11
  42. package/src/EditorContext.tsx +4 -4
  43. package/src/action-registry.ts +12 -0
  44. package/src/asset-editor-context.tsx +9 -0
  45. package/src/asset-events.ts +3 -15
  46. package/src/asset-selection.ts +3 -0
  47. package/src/asset-workflow/asset-capabilities.ts +40 -0
  48. package/src/asset-workflow/asset-import-jobs.ts +108 -0
  49. package/src/asset-workflow/asset-library-query.ts +8 -1
  50. package/src/asset-workflow/asset-workflow-quality.ts +2 -0
  51. package/src/asset-workflow/import-contract.ts +2 -0
  52. package/src/asset-workflow/model-inspection.ts +92 -1
  53. package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
  54. package/src/asset-workflow/project-asset-commands.ts +1 -0
  55. package/src/asset-workflow/project-asset-health.ts +3 -6
  56. package/src/asset-workflow/project-asset-operations.ts +119 -4
  57. package/src/command-listener.ts +37 -9
  58. package/src/components/ApplicationMenus.tsx +13 -15
  59. package/src/components/AssetBrowser.tsx +301 -26
  60. package/src/components/AssetImportDetails.tsx +207 -92
  61. package/src/components/AssetImportJobStatus.tsx +55 -0
  62. package/src/components/CenterDocuments.tsx +28 -1
  63. package/src/components/CommandPalette.tsx +2 -2
  64. package/src/components/DefaultEditorLayout.tsx +4 -0
  65. package/src/components/InspectorToolSection.tsx +12 -10
  66. package/src/components/OnlineAssetBrowser.tsx +197 -11
  67. package/src/components/ToolHost.tsx +8 -8
  68. package/src/components/ViewportOverlay.tsx +13 -26
  69. package/src/components/ViewportPanel.tsx +19 -14
  70. package/src/components/ViewportShadingMenu.tsx +103 -0
  71. package/src/components/WorkspaceDock.tsx +103 -17
  72. package/src/components/asset-documents.tsx +38 -11
  73. package/src/components/asset-editor-persistence.ts +300 -5
  74. package/src/components/asset-selection-section.tsx +83 -9
  75. package/src/components/asset-viewers/AudioViewer.tsx +4 -7
  76. package/src/components/asset-viewers/ModelViewer.tsx +168 -5
  77. package/src/components/asset-viewers/OnlineAssetDetail.tsx +52 -12
  78. package/src/components/asset-viewers/SourceAssetViewer.tsx +7 -26
  79. package/src/components/asset-workflow.css +30 -3
  80. package/src/components/primitives/editor-icons.ts +1 -0
  81. package/src/components/project-tool-documents.tsx +298 -0
  82. package/src/components/status-contributions.tsx +38 -0
  83. package/src/components/tool-documents.tsx +20 -17
  84. package/src/components/tool-schema-form.tsx +258 -0
  85. package/src/components/workspace-dock.css +16 -0
  86. package/src/editor-api.ts +159 -22
  87. package/src/editor-store.ts +6 -5
  88. package/src/editor-viewport.ts +12 -7
  89. package/src/game-viewport-state.ts +28 -0
  90. package/src/model-thumbnail.ts +97 -9
  91. package/src/play-mode.ts +30 -3
  92. package/src/project-tool-discovery.ts +5 -5
  93. package/src/{project-operations.ts → project-tools.ts} +39 -26
  94. package/src/scene-sync.ts +0 -67
  95. package/src/storage/handle-storage.ts +5 -1
  96. package/src/storage/http-storage.ts +7 -2
  97. package/src/storage/mem-storage.ts +7 -1
  98. package/src/storage/types.ts +2 -0
  99. package/src/theme-css.ts +17 -7
  100. package/src/tool-loader.ts +152 -241
  101. package/src/viewport-shading-boundary.ts +15 -0
  102. package/src/widgets/index.ts +2 -1
  103. package/src/workspace-dock-controller.ts +8 -0
  104. package/src/workspace-document-registry.ts +4 -4
  105. package/src/workspace-state-persistence.ts +36 -10
  106. package/template/.claude/skills/editor/SKILL.md +13 -3
  107. package/template/AGENTS.md +100 -83
  108. package/template/package.json +16 -0
  109. package/template/public/scenes/default.vscn.json +1 -2
  110. package/template/scripts/playtest.ts +7 -37
  111. package/template/server/rooms/game-room.ts +2 -2
  112. package/template/src/data/README.md +3 -2
  113. package/template/src/data/tuning.data.json +1 -3
  114. package/template/src/data/tuning.schema.json +0 -12
  115. package/template/src/data/tuning.schema.ts +2 -9
  116. package/template/src/data/tuning.ts +1 -1
  117. package/template/src/scripts/registry.ts +0 -3
  118. package/template/src/tools/README.md +151 -16
  119. package/template/src/tools/autoplay.tool.ts +31 -0
  120. package/template/src/tools/autoplay.ts +15 -0
  121. package/template/src/tools/tuning.document.tsx +107 -0
  122. package/template/src/tools/tuning.tool.ts +24 -0
  123. package/template/src/tools/tuning.ts +9 -0
  124. package/template/src/ui/Button.stories.tsx +1 -1
  125. package/template/tests/acceptance/example.spec.ts +4 -59
  126. package/template/vgai.game.json +1 -1
  127. package/dist/assets/index-bAGhEnnO.css +0 -1
  128. package/dist/assets/webworkerAll-BA4aq7xq.js +0 -1
  129. package/dist/project-scripts.bundle.js +0 -3917
  130. package/server/project-operations.ts +0 -343
  131. package/src/components/project-operation-documents.tsx +0 -226
  132. package/template/src/operations/README.md +0 -88
  133. package/template/src/scripts/components/data-spinner.ts +0 -86
  134. package/template/src/scripts/components/spin-lap-math.ts +0 -27
  135. package/template/src/tools/example.tool.tsx +0 -97
  136. package/template/src/tools/spin.tool.tsx +0 -101
  137. package/template/tests/logic/example.test.ts +0 -31
@@ -1,8 +1,8 @@
1
- import { connectOperationFileEvents } from './asset-events';
1
+ import { connectToolFileEvents } from './asset-events';
2
2
  import { getEditorMode } from './editor-mode';
3
3
  import { onProjectChange } from './project-manager';
4
4
 
5
- export interface ProjectOperationCatalogEntry {
5
+ export interface ProjectToolCatalogEntry {
6
6
  name: string;
7
7
  summary: string;
8
8
  description: string;
@@ -16,58 +16,71 @@ export interface ProjectOperationCatalogEntry {
16
16
  supportsDryRun: boolean;
17
17
  longRunning: boolean;
18
18
  permission: { risk: 'read' | 'write' | 'destructive'; summary: string };
19
+ contributions: ProjectToolContribution[];
19
20
  }
20
21
 
21
- export interface ProjectOperationCatalog {
22
- operations: ProjectOperationCatalogEntry[];
22
+ export type ToolContributionPoint =
23
+ | 'workspace.document'
24
+ | 'selection.inspector'
25
+ | 'workspace.utility';
26
+
27
+ export interface ProjectToolContribution {
28
+ id: string;
29
+ point: ToolContributionPoint;
30
+ title: string;
31
+ entryPath: string;
32
+ }
33
+
34
+ export interface ProjectToolCatalog {
35
+ tools: ProjectToolCatalogEntry[];
23
36
  loadErrors: Array<{ sourcePath: string; message: string }>;
24
37
  }
25
38
 
26
- export type ProjectOperationOutcome =
39
+ export type ProjectToolOutcome =
27
40
  | { ok: true; data: unknown }
28
41
  | { ok: false; error: { code: string; message: string; data?: unknown; issues?: unknown[] } };
29
42
 
30
- const EMPTY_CATALOG: ProjectOperationCatalog = { operations: [], loadErrors: [] };
43
+ const EMPTY_CATALOG: ProjectToolCatalog = { tools: [], loadErrors: [] };
31
44
  let catalog = EMPTY_CATALOG;
32
45
  let version = 0;
33
46
  const listeners = new Set<() => void>();
34
47
 
35
- function publish(next: ProjectOperationCatalog): void {
48
+ function publish(next: ProjectToolCatalog): void {
36
49
  catalog = next;
37
50
  version++;
38
51
  for (const listener of listeners) listener();
39
52
  }
40
53
 
41
- export function getProjectOperations(): ProjectOperationCatalog {
54
+ export function getProjectTools(): ProjectToolCatalog {
42
55
  return catalog;
43
56
  }
44
57
 
45
- export function projectOperationsVersion(): number {
58
+ export function projectToolsVersion(): number {
46
59
  return version;
47
60
  }
48
61
 
49
- export function subscribeProjectOperations(listener: () => void): () => void {
62
+ export function subscribeProjectTools(listener: () => void): () => void {
50
63
  listeners.add(listener);
51
64
  return () => listeners.delete(listener);
52
65
  }
53
66
 
54
- export async function refreshProjectOperations(): Promise<ProjectOperationCatalog> {
67
+ export async function refreshProjectTools(): Promise<ProjectToolCatalog> {
55
68
  if (getEditorMode() === 'browser') {
56
69
  publish(EMPTY_CATALOG);
57
70
  return EMPTY_CATALOG;
58
71
  }
59
72
  try {
60
- const response = await fetch('/__editor/project-operations');
73
+ const response = await fetch('/__editor/project-tools');
61
74
  if (!response.ok) throw new Error(`catalog request failed (${response.status})`);
62
- const next = (await response.json()) as ProjectOperationCatalog;
75
+ const next = (await response.json()) as ProjectToolCatalog;
63
76
  publish(next);
64
77
  return next;
65
78
  } catch (error) {
66
79
  const next = {
67
- operations: [],
80
+ tools: [],
68
81
  loadErrors: [
69
82
  {
70
- sourcePath: 'src/operations',
83
+ sourcePath: 'package.json#vgai.tools',
71
84
  message: error instanceof Error ? error.message : String(error),
72
85
  },
73
86
  ],
@@ -77,35 +90,35 @@ export async function refreshProjectOperations(): Promise<ProjectOperationCatalo
77
90
  }
78
91
  }
79
92
 
80
- export async function runProjectOperation(
93
+ export async function runProjectTool(
81
94
  name: string,
82
95
  input: unknown,
83
96
  options: { confirm?: boolean } = {},
84
- ): Promise<ProjectOperationOutcome> {
85
- const response = await fetch('/__editor/project-operations/run', {
97
+ ): Promise<ProjectToolOutcome> {
98
+ const response = await fetch('/__editor/project-tools/run', {
86
99
  method: 'POST',
87
100
  headers: { 'Content-Type': 'application/json' },
88
101
  body: JSON.stringify({ name, input, confirm: options.confirm === true }),
89
102
  });
90
- const outcome = (await response.json()) as ProjectOperationOutcome;
103
+ const outcome = (await response.json()) as ProjectToolOutcome;
91
104
  if (!outcome || typeof outcome !== 'object' || typeof outcome.ok !== 'boolean') {
92
- throw new Error(`Operation request returned an invalid response (${response.status}).`);
105
+ throw new Error(`Tool request returned an invalid response (${response.status}).`);
93
106
  }
94
107
  return outcome;
95
108
  }
96
109
 
97
- /** Editor-init lifecycle for Node-side project operation discovery. */
98
- export function startProjectOperationDiscovery(): () => void {
99
- void refreshProjectOperations();
100
- const stopProjectWatch = onProjectChange(() => void refreshProjectOperations());
101
- const stopFileWatch = connectOperationFileEvents(() => void refreshProjectOperations());
110
+ /** Editor-init lifecycle for the Node-side registered tool catalog. */
111
+ export function startProjectToolCatalog(): () => void {
112
+ void refreshProjectTools();
113
+ const stopProjectWatch = onProjectChange(() => void refreshProjectTools());
114
+ const stopFileWatch = connectToolFileEvents(() => void refreshProjectTools());
102
115
  return () => {
103
116
  stopProjectWatch();
104
117
  stopFileWatch();
105
118
  };
106
119
  }
107
120
 
108
- export function __resetProjectOperationsForTest(): void {
121
+ export function __resetProjectToolsForTest(): void {
109
122
  catalog = EMPTY_CATALOG;
110
123
  version = 0;
111
124
  listeners.clear();
package/src/scene-sync.ts CHANGED
@@ -14,7 +14,6 @@ import * as THREE from 'three';
14
14
  import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
15
15
  import type { BatchedRenderer, ParticleSystem } from 'three.quarks';
16
16
  import { readProjectTextFile } from './editor-api';
17
- import type { ShadingMode } from './editor-store';
18
17
  import { createEntityObject, disposeEntityAnimation } from './entity-factory';
19
18
 
20
19
  /**
@@ -231,7 +230,6 @@ export function unregisterParticles(
231
230
  export function buildPreview(
232
231
  doc: SceneFile,
233
232
  scene: THREE.Scene,
234
- shadingMode: ShadingMode = 'solid',
235
233
  renderer?: THREE.WebGLRenderer,
236
234
  batchedRenderer?: BatchedRenderer | null,
237
235
  ): Map<string, THREE.Object3D> {
@@ -340,11 +338,6 @@ export function buildPreview(
340
338
  // Apply environment
341
339
  applyEnvironment(doc.environment, scene, renderer);
342
340
 
343
- // Apply shading mode override
344
- if (shadingMode !== 'solid') {
345
- applyShadingOverride(scene, shadingMode);
346
- }
347
-
348
341
  return objectMap;
349
342
  }
350
343
 
@@ -530,63 +523,3 @@ async function loadEnvTexture(
530
523
  return null;
531
524
  }
532
525
  }
533
-
534
- function asMaterialArray(m: THREE.Material | THREE.Material[]): THREE.Material[] {
535
- return Array.isArray(m) ? m : [m];
536
- }
537
-
538
- /**
539
- * Apply (and revert) the viewport shading override on a single mesh.
540
- *
541
- * Reversible across all three modes so the toolbar toggle can re-shade live
542
- * meshes without a full scene rebuild (ED1):
543
- * - solid: restore the original material(s), clear wireframe.
544
- * - wireframe: restore original material(s), set wireframe on.
545
- * - unlit: swap in MeshBasicMaterial(s) built from the original color.
546
- *
547
- * The original material is stashed in `userData['__shadeOrig']` the first time
548
- * the mesh is touched; unlit overrides are stashed in `__shadeUnlit` so they
549
- * can be disposed when leaving unlit.
550
- */
551
- function applyShadingToMesh(mesh: THREE.Mesh, mode: ShadingMode): void {
552
- if (getUserData(mesh, '__shadeOrig') === undefined) {
553
- setUserData(mesh, '__shadeOrig', mesh.material);
554
- }
555
- const orig = getUserData(mesh, '__shadeOrig') as THREE.Material | THREE.Material[];
556
- const unlit = getUserData(mesh, '__shadeUnlit') as THREE.Material[] | undefined;
557
-
558
- if (mode === 'unlit') {
559
- if (unlit) {
560
- mesh.material = Array.isArray(orig) ? unlit : unlit[0]!;
561
- return;
562
- }
563
- const created = asMaterialArray(orig).map((m) => {
564
- const color = (m as THREE.MeshStandardMaterial).color?.clone() ?? new THREE.Color(0x888888);
565
- return new THREE.MeshBasicMaterial({ color });
566
- });
567
- setUserData(mesh, '__shadeUnlit', created);
568
- mesh.material = Array.isArray(orig) ? created : created[0]!;
569
- return;
570
- }
571
-
572
- // solid or wireframe: restore originals and drop any unlit override.
573
- mesh.material = orig;
574
- if (unlit) {
575
- for (const m of unlit) m.dispose();
576
- deleteUserData(mesh, '__shadeUnlit');
577
- }
578
- const wire = mode === 'wireframe';
579
- for (const m of asMaterialArray(orig)) {
580
- if ('wireframe' in m) (m as THREE.MeshStandardMaterial).wireframe = wire;
581
- }
582
- }
583
-
584
- /** Apply viewport shading override to all entity meshes. Reversible (ED1). */
585
- export function applyShadingOverride(scene: THREE.Scene, mode: ShadingMode): void {
586
- scene.traverse((obj) => {
587
- if (getUserData(obj, 'entityId') === undefined) return;
588
- const mesh = obj as THREE.Mesh;
589
- if (!mesh.isMesh || !mesh.material) return;
590
- applyShadingToMesh(mesh, mode);
591
- });
592
- }
@@ -97,7 +97,11 @@ export abstract class HandleStorage implements StorageBackend {
97
97
  path: base ? `${base}/${name}` : name,
98
98
  type: isDir ? 'dir' : 'file',
99
99
  };
100
- if (!isDir) entry.size = (await (child as FileSystemFileHandle).getFile()).size;
100
+ if (!isDir) {
101
+ const file = await (child as FileSystemFileHandle).getFile();
102
+ entry.size = file.size;
103
+ entry.mtime = file.lastModified;
104
+ }
101
105
  entries.push(entry);
102
106
  }
103
107
  // Directories first, then alphabetical — matches the server's /assets route.
@@ -8,7 +8,7 @@
8
8
  * (write) routes; reads come from Vite's `/@fs/` static serving.
9
9
  *
10
10
  * Route mapping (paths are relative to `public/`):
11
- * list → GET /__editor/assets?dir → { entries: [{name,type,size}] }
11
+ * list → GET /__editor/assets?dir → { entries: [{name,type,size,mtime}] }
12
12
  * write → POST /__editor/save-file ({path, content, encoding?})
13
13
  * read → GET /@fs/{projectRoot}/public/{path}
14
14
  * watch → EventSource /__editor/events (chokidar-backed)
@@ -27,6 +27,7 @@ interface ServerAssetEntry {
27
27
  name: string;
28
28
  type: 'file' | 'directory';
29
29
  size: number;
30
+ mtime: number;
30
31
  }
31
32
 
32
33
  export class HttpStorage implements StorageBackend {
@@ -69,8 +70,10 @@ export class HttpStorage implements StorageBackend {
69
70
  name: e.name,
70
71
  path: normalize(dir ? `${dir}/${e.name}` : e.name),
71
72
  type: e.type === 'directory' ? 'dir' : 'file',
73
+ mtime: e.mtime,
72
74
  };
73
75
  if (e.type === 'file') out.size = e.size;
76
+ out.mtime = e.mtime;
74
77
  return out;
75
78
  });
76
79
  }
@@ -87,7 +90,9 @@ export class HttpStorage implements StorageBackend {
87
90
  return null;
88
91
  }
89
92
  const hit = entries.find((e) => e.name === base);
90
- return hit ? { path: hit.path, type: hit.type, size: hit.size ?? 0, mtime: 0 } : null;
93
+ return hit
94
+ ? { path: hit.path, type: hit.type, size: hit.size ?? 0, mtime: hit.mtime ?? 0 }
95
+ : null;
91
96
  }
92
97
 
93
98
  async exists(path: string): Promise<boolean> {
@@ -78,8 +78,14 @@ export class MemStorage implements StorageBackend {
78
78
  for (const [path, node] of this.nodes) {
79
79
  const [parent, base] = dirAndBase(path);
80
80
  if (parent !== dn || !base) continue;
81
- const entry: DirEntry = { name: base, path, type: node.type === 'dir' ? 'dir' : 'file' };
81
+ const entry: DirEntry = {
82
+ name: base,
83
+ path,
84
+ type: node.type === 'dir' ? 'dir' : 'file',
85
+ mtime: node.mtime,
86
+ };
82
87
  if (node.type === 'file') entry.size = node.data.length;
88
+ entry.mtime = node.mtime;
83
89
  entries.push(entry);
84
90
  }
85
91
  entries.sort((a, b) => {
@@ -44,6 +44,8 @@ export interface DirEntry {
44
44
  type: NodeType;
45
45
  /** Byte length for files (omitted for directories). */
46
46
  size?: number;
47
+ /** Last-modified time, epoch ms (omitted if the backend cannot report it). */
48
+ mtime?: number;
47
49
  }
48
50
 
49
51
  export type WatchEventType = 'create' | 'update' | 'remove';
package/src/theme-css.ts CHANGED
@@ -125,6 +125,12 @@ ${themeDeclarations}
125
125
 
126
126
  .vgai-btn {
127
127
  display: inline-flex;
128
+ /* Inline controls should keep their intrinsic size even when authored as
129
+ * direct children of a CSS grid. Grid's default align-items: stretch
130
+ * otherwise turns a compact action into a full-width slab. A caller that
131
+ * deliberately wants a wide button can still set width: 100%. */
132
+ width: max-content;
133
+ max-width: 100%;
128
134
  align-items: center;
129
135
  justify-content: center;
130
136
  gap: var(--vgai-space-2);
@@ -829,6 +835,10 @@ ${themeDeclarations}
829
835
  .vgai-tab-badge,
830
836
  .vgai-badge {
831
837
  display: inline-flex;
838
+ /* Badges are labels, never layout surfaces. Keep them from stretching into
839
+ * long capsules when a contribution places one directly in a grid. */
840
+ width: max-content;
841
+ max-width: 100%;
832
842
  min-width: 15px;
833
843
  height: 15px;
834
844
  align-items: center;
@@ -952,14 +962,14 @@ ${themeDeclarations}
952
962
  .vgai-drop-indicator[data-position='inside'] { inset: 1px; border-radius: var(--vgai-radius-sm); }
953
963
 
954
964
  .vgai-model-tree-item { text-align: left; }
955
- .vgai-operation-card {
956
- display: block;
965
+ .vgai-btn.vgai-project-tool-card {
966
+ display: grid;
957
967
  width: 100%;
958
- margin-top: 10px;
959
- padding: 10px;
960
- border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
961
- border-radius: var(--vgai-radius-md);
962
- background: var(--vgai-surface-panel);
968
+ height: auto;
969
+ align-items: start;
970
+ justify-content: normal;
971
+ gap: var(--vgai-space-2);
972
+ padding-block: var(--vgai-space-3);
963
973
  text-align: left;
964
974
  }
965
975