claude-cortex 1.0.0 → 1.1.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 (79) hide show
  1. package/dashboard/README.md +36 -0
  2. package/dashboard/components.json +22 -0
  3. package/dashboard/eslint.config.mjs +18 -0
  4. package/dashboard/next.config.ts +7 -0
  5. package/dashboard/package-lock.json +7784 -0
  6. package/dashboard/package.json +42 -0
  7. package/dashboard/postcss.config.mjs +7 -0
  8. package/dashboard/public/file.svg +1 -0
  9. package/dashboard/public/globe.svg +1 -0
  10. package/dashboard/public/next.svg +1 -0
  11. package/dashboard/public/vercel.svg +1 -0
  12. package/dashboard/public/window.svg +1 -0
  13. package/dashboard/src/app/favicon.ico +0 -0
  14. package/dashboard/src/app/globals.css +125 -0
  15. package/dashboard/src/app/layout.tsx +35 -0
  16. package/dashboard/src/app/page.tsx +338 -0
  17. package/dashboard/src/components/Providers.tsx +27 -0
  18. package/dashboard/src/components/brain/ActivityPulseSystem.tsx +229 -0
  19. package/dashboard/src/components/brain/BrainMesh.tsx +118 -0
  20. package/dashboard/src/components/brain/BrainRegions.tsx +254 -0
  21. package/dashboard/src/components/brain/BrainScene.tsx +255 -0
  22. package/dashboard/src/components/brain/CategoryLabels.tsx +103 -0
  23. package/dashboard/src/components/brain/CoreSphere.tsx +215 -0
  24. package/dashboard/src/components/brain/DataFlowParticles.tsx +123 -0
  25. package/dashboard/src/components/brain/DataStreamRings.tsx +161 -0
  26. package/dashboard/src/components/brain/ElectronFlow.tsx +323 -0
  27. package/dashboard/src/components/brain/HolographicGrid.tsx +235 -0
  28. package/dashboard/src/components/brain/MemoryLinks.tsx +271 -0
  29. package/dashboard/src/components/brain/MemoryNode.tsx +245 -0
  30. package/dashboard/src/components/brain/NeuralPathways.tsx +441 -0
  31. package/dashboard/src/components/brain/SynapseNodes.tsx +306 -0
  32. package/dashboard/src/components/brain/TimelineControls.tsx +205 -0
  33. package/dashboard/src/components/chip/ChipScene.tsx +497 -0
  34. package/dashboard/src/components/chip/ChipSubstrate.tsx +238 -0
  35. package/dashboard/src/components/chip/CortexCore.tsx +210 -0
  36. package/dashboard/src/components/chip/DataBus.tsx +416 -0
  37. package/dashboard/src/components/chip/MemoryCell.tsx +225 -0
  38. package/dashboard/src/components/chip/MemoryGrid.tsx +328 -0
  39. package/dashboard/src/components/chip/QuantumCell.tsx +316 -0
  40. package/dashboard/src/components/chip/SectionLabel.tsx +113 -0
  41. package/dashboard/src/components/chip/index.ts +14 -0
  42. package/dashboard/src/components/controls/ControlPanel.tsx +100 -0
  43. package/dashboard/src/components/dashboard/StatsPanel.tsx +164 -0
  44. package/dashboard/src/components/debug/ActivityLog.tsx +238 -0
  45. package/dashboard/src/components/debug/DebugPanel.tsx +101 -0
  46. package/dashboard/src/components/debug/QueryTester.tsx +192 -0
  47. package/dashboard/src/components/debug/RelationshipGraph.tsx +403 -0
  48. package/dashboard/src/components/debug/SqlConsole.tsx +313 -0
  49. package/dashboard/src/components/memory/MemoryDetail.tsx +325 -0
  50. package/dashboard/src/components/ui/button.tsx +62 -0
  51. package/dashboard/src/components/ui/card.tsx +92 -0
  52. package/dashboard/src/components/ui/input.tsx +21 -0
  53. package/dashboard/src/hooks/useDebouncedValue.ts +24 -0
  54. package/dashboard/src/hooks/useMemories.ts +276 -0
  55. package/dashboard/src/hooks/useSuggestions.ts +46 -0
  56. package/dashboard/src/lib/category-colors.ts +84 -0
  57. package/dashboard/src/lib/position-algorithm.ts +177 -0
  58. package/dashboard/src/lib/simplex-noise.ts +217 -0
  59. package/dashboard/src/lib/store.ts +88 -0
  60. package/dashboard/src/lib/utils.ts +6 -0
  61. package/dashboard/src/lib/websocket.ts +216 -0
  62. package/dashboard/src/types/memory.ts +73 -0
  63. package/dashboard/tsconfig.json +34 -0
  64. package/dist/api/control.d.ts +27 -0
  65. package/dist/api/control.d.ts.map +1 -0
  66. package/dist/api/control.js +60 -0
  67. package/dist/api/control.js.map +1 -0
  68. package/dist/api/visualization-server.d.ts.map +1 -1
  69. package/dist/api/visualization-server.js +109 -2
  70. package/dist/api/visualization-server.js.map +1 -1
  71. package/dist/index.d.ts +2 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +80 -4
  74. package/dist/index.js.map +1 -1
  75. package/dist/memory/store.d.ts +6 -0
  76. package/dist/memory/store.d.ts.map +1 -1
  77. package/dist/memory/store.js +14 -0
  78. package/dist/memory/store.js.map +1 -1
  79. package/package.json +7 -3
@@ -0,0 +1,497 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Chip Scene - Circuit Board Style
5
+ * Clean top-down orthographic view of memory as a PCB
6
+ */
7
+
8
+ import { Suspense, useMemo, useState, useCallback, useRef } from 'react';
9
+ import { Canvas } from '@react-three/fiber';
10
+ import { OrthographicCamera } from '@react-three/drei';
11
+ import { EffectComposer, Bloom } from '@react-three/postprocessing';
12
+ import { Memory, MemoryLink } from '@/types/memory';
13
+ import { useMemoryWebSocket } from '@/lib/websocket';
14
+ import * as THREE from 'three';
15
+
16
+ // Layout constants
17
+ const CHIP_WIDTH = 24;
18
+ const CHIP_HEIGHT = 16;
19
+ const NODE_SPACING = 1.2;
20
+ const SECTION_GAP = 2;
21
+
22
+ // Category colors
23
+ const CATEGORY_COLORS: Record<string, string> = {
24
+ architecture: '#06b6d4', // cyan
25
+ pattern: '#22c55e', // green
26
+ preference: '#eab308', // yellow
27
+ error: '#ef4444', // red
28
+ context: '#f97316', // orange
29
+ learning: '#84cc16', // lime
30
+ todo: '#a855f7', // purple
31
+ note: '#3b82f6', // blue
32
+ relationship: '#6366f1', // indigo
33
+ custom: '#ec4899', // pink
34
+ };
35
+
36
+ interface ChipSceneProps {
37
+ memories: Memory[];
38
+ links?: MemoryLink[];
39
+ selectedMemory: Memory | null;
40
+ onSelectMemory: (memory: Memory | null) => void;
41
+ }
42
+
43
+ export function ChipScene({
44
+ memories = [],
45
+ links = [],
46
+ selectedMemory,
47
+ onSelectMemory,
48
+ }: ChipSceneProps) {
49
+ const [hoveredMemory, setHoveredMemory] = useState<Memory | null>(null);
50
+
51
+ // Group memories by type
52
+ const groupedMemories = useMemo(() => {
53
+ const stm = memories.filter((m) => m.type === 'short_term');
54
+ const episodic = memories.filter((m) => m.type === 'episodic');
55
+ const ltm = memories.filter((m) => m.type === 'long_term');
56
+ return { stm, episodic, ltm };
57
+ }, [memories]);
58
+
59
+ return (
60
+ <div className="w-full h-full bg-[#0a0a12]">
61
+ <Canvas
62
+ orthographic
63
+ camera={{ zoom: 40, position: [0, 0, 100], near: 0.1, far: 1000 }}
64
+ gl={{ antialias: true }}
65
+ onClick={() => onSelectMemory(null)}
66
+ >
67
+ <Suspense fallback={null}>
68
+ <ChipContent
69
+ memories={memories}
70
+ groupedMemories={groupedMemories}
71
+ links={links}
72
+ selectedMemory={selectedMemory}
73
+ hoveredMemory={hoveredMemory}
74
+ onSelectMemory={onSelectMemory}
75
+ onHoverMemory={setHoveredMemory}
76
+ />
77
+ </Suspense>
78
+ </Canvas>
79
+
80
+ {/* Hover tooltip */}
81
+ {hoveredMemory && (
82
+ <div className="absolute top-4 left-1/2 -translate-x-1/2 bg-slate-900/95 border border-slate-600 rounded-lg px-4 py-2 text-sm backdrop-blur-sm pointer-events-none z-50">
83
+ <div className="font-semibold text-white">{hoveredMemory.title}</div>
84
+ <div className="text-slate-400 text-xs mt-1 flex items-center gap-2">
85
+ <span
86
+ className="w-2 h-2 rounded-full"
87
+ style={{ backgroundColor: CATEGORY_COLORS[hoveredMemory.category] }}
88
+ />
89
+ <span className="capitalize">{hoveredMemory.category}</span>
90
+ <span className="text-slate-500">|</span>
91
+ <span>{Math.round(hoveredMemory.salience * 100)}% salience</span>
92
+ </div>
93
+ </div>
94
+ )}
95
+
96
+ {/* Legend */}
97
+ <div className="absolute bottom-4 left-4 bg-slate-900/90 border border-slate-700 rounded-lg p-3 text-xs backdrop-blur-sm">
98
+ <h4 className="font-semibold text-white mb-2">Memory Banks</h4>
99
+ <div className="space-y-1.5">
100
+ <div className="flex items-center gap-2">
101
+ <div className="w-3 h-3 rounded-sm bg-orange-500/30 border border-orange-500" />
102
+ <span className="text-slate-300">STM</span>
103
+ <span className="text-slate-500 ml-auto">{groupedMemories.stm.length}</span>
104
+ </div>
105
+ <div className="flex items-center gap-2">
106
+ <div className="w-3 h-3 rounded-sm bg-purple-500/30 border border-purple-500" />
107
+ <span className="text-slate-300">Episodic</span>
108
+ <span className="text-slate-500 ml-auto">{groupedMemories.episodic.length}</span>
109
+ </div>
110
+ <div className="flex items-center gap-2">
111
+ <div className="w-3 h-3 rounded-sm bg-blue-500/30 border border-blue-500" />
112
+ <span className="text-slate-300">Long-Term</span>
113
+ <span className="text-slate-500 ml-auto">{groupedMemories.ltm.length}</span>
114
+ </div>
115
+ </div>
116
+ <div className="mt-3 pt-2 border-t border-slate-700 text-slate-400">
117
+ {memories.length} total memories
118
+ </div>
119
+ </div>
120
+
121
+ {/* Status */}
122
+ <div className="absolute top-4 right-4 text-xs text-slate-500 font-mono">
123
+ CORTEX PCB v1.0
124
+ </div>
125
+ </div>
126
+ );
127
+ }
128
+
129
+ interface ChipContentProps {
130
+ memories: Memory[];
131
+ groupedMemories: { stm: Memory[]; episodic: Memory[]; ltm: Memory[] };
132
+ links: MemoryLink[];
133
+ selectedMemory: Memory | null;
134
+ hoveredMemory: Memory | null;
135
+ onSelectMemory: (memory: Memory | null) => void;
136
+ onHoverMemory: (memory: Memory | null) => void;
137
+ }
138
+
139
+ function ChipContent({
140
+ memories,
141
+ groupedMemories,
142
+ links,
143
+ selectedMemory,
144
+ hoveredMemory,
145
+ onSelectMemory,
146
+ onHoverMemory,
147
+ }: ChipContentProps) {
148
+ // Calculate grid layout for each section
149
+ const layoutConfig = useMemo(() => {
150
+ const cols = 12;
151
+ const stmRows = Math.ceil(groupedMemories.stm.length / cols) || 1;
152
+ const episodicRows = Math.ceil(groupedMemories.episodic.length / cols) || 1;
153
+ const ltmRows = Math.ceil(groupedMemories.ltm.length / cols) || 1;
154
+
155
+ const totalHeight = (stmRows + episodicRows + ltmRows) * NODE_SPACING + SECTION_GAP * 2;
156
+ const startY = totalHeight / 2;
157
+
158
+ return {
159
+ cols,
160
+ stm: {
161
+ startY: startY - NODE_SPACING / 2,
162
+ rows: stmRows,
163
+ },
164
+ episodic: {
165
+ startY: startY - stmRows * NODE_SPACING - SECTION_GAP,
166
+ rows: episodicRows,
167
+ },
168
+ ltm: {
169
+ startY: startY - stmRows * NODE_SPACING - episodicRows * NODE_SPACING - SECTION_GAP * 2,
170
+ rows: ltmRows,
171
+ },
172
+ };
173
+ }, [groupedMemories]);
174
+
175
+ // Calculate positions for all memories
176
+ const memoryPositions = useMemo(() => {
177
+ const positions = new Map<number, [number, number]>();
178
+ const gridWidth = layoutConfig.cols * NODE_SPACING;
179
+ const startX = -gridWidth / 2 + NODE_SPACING / 2;
180
+
181
+ // STM positions
182
+ groupedMemories.stm.forEach((m, i) => {
183
+ const col = i % layoutConfig.cols;
184
+ const row = Math.floor(i / layoutConfig.cols);
185
+ const x = startX + col * NODE_SPACING;
186
+ const y = layoutConfig.stm.startY - row * NODE_SPACING;
187
+ positions.set(m.id, [x, y]);
188
+ });
189
+
190
+ // Episodic positions
191
+ groupedMemories.episodic.forEach((m, i) => {
192
+ const col = i % layoutConfig.cols;
193
+ const row = Math.floor(i / layoutConfig.cols);
194
+ const x = startX + col * NODE_SPACING;
195
+ const y = layoutConfig.episodic.startY - row * NODE_SPACING;
196
+ positions.set(m.id, [x, y]);
197
+ });
198
+
199
+ // LTM positions
200
+ groupedMemories.ltm.forEach((m, i) => {
201
+ const col = i % layoutConfig.cols;
202
+ const row = Math.floor(i / layoutConfig.cols);
203
+ const x = startX + col * NODE_SPACING;
204
+ const y = layoutConfig.ltm.startY - row * NODE_SPACING;
205
+ positions.set(m.id, [x, y]);
206
+ });
207
+
208
+ return positions;
209
+ }, [groupedMemories, layoutConfig]);
210
+
211
+ return (
212
+ <>
213
+ {/* Subtle ambient light */}
214
+ <ambientLight intensity={0.5} />
215
+
216
+ {/* PCB Background */}
217
+ <PCBBackground width={CHIP_WIDTH} height={CHIP_HEIGHT} />
218
+
219
+ {/* Section dividers and labels */}
220
+ <SectionDividers layoutConfig={layoutConfig} cols={layoutConfig.cols} />
221
+
222
+ {/* Memory links (PCB traces) */}
223
+ <MemoryTraces links={links} positions={memoryPositions} />
224
+
225
+ {/* Memory nodes */}
226
+ {memories.map((memory) => {
227
+ const pos = memoryPositions.get(memory.id);
228
+ if (!pos) return null;
229
+
230
+ return (
231
+ <MemoryNode
232
+ key={memory.id}
233
+ memory={memory}
234
+ position={pos}
235
+ isSelected={selectedMemory?.id === memory.id}
236
+ isHovered={hoveredMemory?.id === memory.id}
237
+ onSelect={onSelectMemory}
238
+ onHover={onHoverMemory}
239
+ />
240
+ );
241
+ })}
242
+
243
+ {/* Post-processing - subtle bloom */}
244
+ <EffectComposer>
245
+ <Bloom
246
+ luminanceThreshold={0.6}
247
+ luminanceSmoothing={0.5}
248
+ intensity={0.4}
249
+ radius={0.4}
250
+ />
251
+ </EffectComposer>
252
+ </>
253
+ );
254
+ }
255
+
256
+ // PCB-style background with grid
257
+ function PCBBackground({ width, height }: { width: number; height: number }) {
258
+ const gridGeometry = useMemo(() => {
259
+ const points: THREE.Vector3[] = [];
260
+ const spacing = 1;
261
+
262
+ // Vertical lines
263
+ for (let x = -width / 2; x <= width / 2; x += spacing) {
264
+ points.push(new THREE.Vector3(x, -height / 2, 0));
265
+ points.push(new THREE.Vector3(x, height / 2, 0));
266
+ }
267
+
268
+ // Horizontal lines
269
+ for (let y = -height / 2; y <= height / 2; y += spacing) {
270
+ points.push(new THREE.Vector3(-width / 2, y, 0));
271
+ points.push(new THREE.Vector3(width / 2, y, 0));
272
+ }
273
+
274
+ return new THREE.BufferGeometry().setFromPoints(points);
275
+ }, [width, height]);
276
+
277
+ return (
278
+ <group>
279
+ {/* Dark background */}
280
+ <mesh position={[0, 0, -1]}>
281
+ <planeGeometry args={[width + 4, height + 4]} />
282
+ <meshBasicMaterial color="#0d1117" />
283
+ </mesh>
284
+
285
+ {/* PCB substrate */}
286
+ <mesh position={[0, 0, -0.5]}>
287
+ <planeGeometry args={[width, height]} />
288
+ <meshBasicMaterial color="#1a1f2e" />
289
+ </mesh>
290
+
291
+ {/* Grid lines */}
292
+ <lineSegments geometry={gridGeometry}>
293
+ <lineBasicMaterial color="#252d3d" opacity={0.5} transparent />
294
+ </lineSegments>
295
+
296
+ {/* Border */}
297
+ <lineLoop>
298
+ <bufferGeometry>
299
+ <float32BufferAttribute
300
+ attach="attributes-position"
301
+ args={[
302
+ new Float32Array([
303
+ -width / 2, -height / 2, 0,
304
+ width / 2, -height / 2, 0,
305
+ width / 2, height / 2, 0,
306
+ -width / 2, height / 2, 0,
307
+ ]),
308
+ 3,
309
+ ]}
310
+ />
311
+ </bufferGeometry>
312
+ <lineBasicMaterial color="#3d4a5c" />
313
+ </lineLoop>
314
+ </group>
315
+ );
316
+ }
317
+
318
+ // Section dividers and labels
319
+ function SectionDividers({
320
+ layoutConfig,
321
+ cols,
322
+ }: {
323
+ layoutConfig: { stm: { startY: number; rows: number }; episodic: { startY: number; rows: number }; ltm: { startY: number; rows: number }; cols: number };
324
+ cols: number;
325
+ }) {
326
+ const gridWidth = cols * NODE_SPACING;
327
+
328
+ // Divider line between STM and Episodic
329
+ const divider1Y = layoutConfig.stm.startY - layoutConfig.stm.rows * NODE_SPACING - SECTION_GAP / 2 + NODE_SPACING / 2;
330
+ // Divider line between Episodic and LTM
331
+ const divider2Y = layoutConfig.episodic.startY - layoutConfig.episodic.rows * NODE_SPACING - SECTION_GAP / 2 + NODE_SPACING / 2;
332
+
333
+ return (
334
+ <group>
335
+ {/* STM label */}
336
+ <SectionLabel
337
+ text="STM"
338
+ position={[-gridWidth / 2 - 1.5, layoutConfig.stm.startY - (layoutConfig.stm.rows * NODE_SPACING) / 2 + NODE_SPACING / 2, 0]}
339
+ color="#f97316"
340
+ />
341
+
342
+ {/* Divider 1 */}
343
+ <line>
344
+ <bufferGeometry>
345
+ <float32BufferAttribute
346
+ attach="attributes-position"
347
+ args={[new Float32Array([-gridWidth / 2 - 0.5, divider1Y, 0, gridWidth / 2 + 0.5, divider1Y, 0]), 3]}
348
+ />
349
+ </bufferGeometry>
350
+ <lineBasicMaterial color="#3d4a5c" opacity={0.5} transparent />
351
+ </line>
352
+
353
+ {/* Episodic label */}
354
+ <SectionLabel
355
+ text="EPISODIC"
356
+ position={[-gridWidth / 2 - 1.5, layoutConfig.episodic.startY - (layoutConfig.episodic.rows * NODE_SPACING) / 2 + NODE_SPACING / 2, 0]}
357
+ color="#a855f7"
358
+ />
359
+
360
+ {/* Divider 2 */}
361
+ <line>
362
+ <bufferGeometry>
363
+ <float32BufferAttribute
364
+ attach="attributes-position"
365
+ args={[new Float32Array([-gridWidth / 2 - 0.5, divider2Y, 0, gridWidth / 2 + 0.5, divider2Y, 0]), 3]}
366
+ />
367
+ </bufferGeometry>
368
+ <lineBasicMaterial color="#3d4a5c" opacity={0.5} transparent />
369
+ </line>
370
+
371
+ {/* LTM label */}
372
+ <SectionLabel
373
+ text="LTM"
374
+ position={[-gridWidth / 2 - 1.5, layoutConfig.ltm.startY - (layoutConfig.ltm.rows * NODE_SPACING) / 2 + NODE_SPACING / 2, 0]}
375
+ color="#3b82f6"
376
+ />
377
+ </group>
378
+ );
379
+ }
380
+
381
+ // Section label as 3D text alternative (simple box with color)
382
+ function SectionLabel({ text, position, color }: { text: string; position: [number, number, number]; color: string }) {
383
+ return (
384
+ <group position={position}>
385
+ <mesh>
386
+ <planeGeometry args={[0.1, 0.8]} />
387
+ <meshBasicMaterial color={color} />
388
+ </mesh>
389
+ </group>
390
+ );
391
+ }
392
+
393
+ // Memory traces (connections between related memories)
394
+ function MemoryTraces({
395
+ links,
396
+ positions,
397
+ }: {
398
+ links: MemoryLink[];
399
+ positions: Map<number, [number, number]>;
400
+ }) {
401
+ const traceGeometry = useMemo(() => {
402
+ const points: number[] = [];
403
+
404
+ links.forEach((link) => {
405
+ const sourcePos = positions.get(link.source_id);
406
+ const targetPos = positions.get(link.target_id);
407
+ if (sourcePos && targetPos) {
408
+ points.push(sourcePos[0], sourcePos[1], 0.1);
409
+ points.push(targetPos[0], targetPos[1], 0.1);
410
+ }
411
+ });
412
+
413
+ const geometry = new THREE.BufferGeometry();
414
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
415
+ return geometry;
416
+ }, [links, positions]);
417
+
418
+ if (links.length === 0) return null;
419
+
420
+ return (
421
+ <lineSegments geometry={traceGeometry}>
422
+ <lineBasicMaterial color="#FFB347" opacity={0.15} transparent />
423
+ </lineSegments>
424
+ );
425
+ }
426
+
427
+ // Individual memory node
428
+ function MemoryNode({
429
+ memory,
430
+ position,
431
+ isSelected,
432
+ isHovered,
433
+ onSelect,
434
+ onHover,
435
+ }: {
436
+ memory: Memory;
437
+ position: [number, number];
438
+ isSelected: boolean;
439
+ isHovered: boolean;
440
+ onSelect: (memory: Memory | null) => void;
441
+ onHover: (memory: Memory | null) => void;
442
+ }) {
443
+ const color = CATEGORY_COLORS[memory.category] || CATEGORY_COLORS.custom;
444
+ const isQuantum = memory.salience >= 0.7;
445
+
446
+ // Size based on salience
447
+ const baseSize = 0.25;
448
+ const size = baseSize + memory.salience * 0.15;
449
+
450
+ return (
451
+ <group position={[position[0], position[1], 0]}>
452
+ {/* Main node */}
453
+ <mesh
454
+ onClick={(e) => {
455
+ e.stopPropagation();
456
+ onSelect(isSelected ? null : memory);
457
+ }}
458
+ onPointerOver={(e) => {
459
+ e.stopPropagation();
460
+ onHover(memory);
461
+ document.body.style.cursor = 'pointer';
462
+ }}
463
+ onPointerOut={() => {
464
+ onHover(null);
465
+ document.body.style.cursor = 'auto';
466
+ }}
467
+ >
468
+ <circleGeometry args={[size, 16]} />
469
+ <meshBasicMaterial color={color} />
470
+ </mesh>
471
+
472
+ {/* Quantum glow ring */}
473
+ {isQuantum && (
474
+ <mesh>
475
+ <ringGeometry args={[size + 0.05, size + 0.12, 32]} />
476
+ <meshBasicMaterial color="#FFD700" transparent opacity={0.6} />
477
+ </mesh>
478
+ )}
479
+
480
+ {/* Selection ring */}
481
+ {isSelected && (
482
+ <mesh>
483
+ <ringGeometry args={[size + 0.15, size + 0.22, 32]} />
484
+ <meshBasicMaterial color="#ffffff" transparent opacity={0.8} />
485
+ </mesh>
486
+ )}
487
+
488
+ {/* Hover highlight */}
489
+ {isHovered && !isSelected && (
490
+ <mesh>
491
+ <ringGeometry args={[size + 0.08, size + 0.14, 32]} />
492
+ <meshBasicMaterial color={color} transparent opacity={0.5} />
493
+ </mesh>
494
+ )}
495
+ </group>
496
+ );
497
+ }