feat: dark theme social network graph with arrows + responsive MI bar

Social Network Graph:
- Dark/black theme with semi-transparent background
- Arrow markers on edges showing connection direction
- Color-coded arrows: grey (default), yellow (connected), green (trusted)
- Updated header, stats, and icon button colors for dark theme

MI (Mycelial Intelligence) Bar:
- Responsive width: full width on mobile, percentage on narrow, fixed on desktop
- Position: moves to bottom on mobile (above toolbar), stays at top on desktop
- Smooth transitions when resizing
- Smaller max height on mobile (300px vs 400px)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-07 17:09:33 -08:00
parent 27c82246ef
commit 548ec0733e
2 changed files with 88 additions and 18 deletions

View File

@ -62,11 +62,12 @@ const styles = {
gap: '8px',
},
panel: {
backgroundColor: 'rgba(255, 255, 255, 0.95)',
backgroundColor: 'rgba(20, 20, 25, 0.95)',
borderRadius: '12px',
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.15)',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
overflow: 'hidden',
transition: 'all 0.2s ease',
border: '1px solid rgba(255, 255, 255, 0.1)',
},
panelCollapsed: {
width: '48px',
@ -81,13 +82,13 @@ const styles = {
alignItems: 'center',
justifyContent: 'space-between',
padding: '8px 12px',
borderBottom: '1px solid rgba(0, 0, 0, 0.1)',
backgroundColor: 'rgba(0, 0, 0, 0.02)',
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
backgroundColor: 'rgba(255, 255, 255, 0.03)',
},
title: {
fontSize: '12px',
fontWeight: 600,
color: '#1a1a2e',
color: '#e0e0e0',
margin: 0,
},
headerButtons: {
@ -105,8 +106,8 @@ const styles = {
alignItems: 'center',
justifyContent: 'center',
fontSize: '14px',
color: '#666',
transition: 'background-color 0.15s',
color: '#a0a0a0',
transition: 'background-color 0.15s, color 0.15s',
},
canvas: {
display: 'block',
@ -131,9 +132,10 @@ const styles = {
display: 'flex',
gap: '12px',
padding: '6px 12px',
borderTop: '1px solid rgba(0, 0, 0, 0.1)',
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
fontSize: '11px',
color: '#666',
color: '#888',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
stat: {
display: 'flex',
@ -214,22 +216,74 @@ export function NetworkGraphMinimap({
// Create container group
const g = svg.append('g');
// Create arrow marker definitions for edges
const defs = svg.append('defs');
// Arrow marker for regular edges (grey)
defs.append('marker')
.attr('id', 'arrow-grey')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 18)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-4L10,0L0,4')
.attr('fill', 'rgba(150, 150, 150, 0.6)');
// Arrow marker for connected (yellow)
defs.append('marker')
.attr('id', 'arrow-connected')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 18)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-4L10,0L0,4')
.attr('fill', 'rgba(234, 179, 8, 0.8)');
// Arrow marker for trusted (green)
defs.append('marker')
.attr('id', 'arrow-trusted')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 18)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-4L10,0L0,4')
.attr('fill', 'rgba(34, 197, 94, 0.8)');
// Helper to get edge color based on trust level
const getEdgeColor = (d: SimulationLink) => {
const edge = edges.find(e => e.id === d.id);
if (!edge) return 'rgba(0, 0, 0, 0.15)';
if (!edge) return 'rgba(150, 150, 150, 0.4)';
// Use effective trust level for mutual connections, otherwise the edge's trust level
const level = edge.effectiveTrustLevel || edge.trustLevel;
if (level === 'trusted') {
return 'rgba(34, 197, 94, 0.6)'; // green
return 'rgba(34, 197, 94, 0.7)'; // green
} else if (level === 'connected') {
return 'rgba(234, 179, 8, 0.6)'; // yellow
return 'rgba(234, 179, 8, 0.7)'; // yellow
}
return 'rgba(0, 0, 0, 0.15)';
return 'rgba(150, 150, 150, 0.4)';
};
// Create edges
// Helper to get arrow marker based on trust level
const getArrowMarker = (d: SimulationLink) => {
const edge = edges.find(e => e.id === d.id);
if (!edge) return 'url(#arrow-grey)';
const level = edge.effectiveTrustLevel || edge.trustLevel;
if (level === 'trusted') return 'url(#arrow-trusted)';
if (level === 'connected') return 'url(#arrow-connected)';
return 'url(#arrow-grey)';
};
// Create edges as paths (lines) with arrow markers
const link = g.append('g')
.attr('class', 'links')
.selectAll('line')
@ -237,6 +291,7 @@ export function NetworkGraphMinimap({
.join('line')
.attr('stroke', d => getEdgeColor(d))
.attr('stroke-width', d => d.isMutual ? 2.5 : 1.5)
.attr('marker-end', d => getArrowMarker(d))
.style('cursor', 'pointer')
.on('click', (event, d) => {
event.stopPropagation();

View File

@ -1339,11 +1339,23 @@ export function MycelialIntelligenceBar() {
}
}, [editor, suggestedTools, spawnedToolIds])
// Responsive layout - detect window width
const [windowWidth, setWindowWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 1024)
const isMobile = windowWidth < 640
const isNarrow = windowWidth < 768
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// Height: taller when showing suggestion chips (single tool or 2+ selected)
const showSuggestions = selectedToolInfo || (selectionInfo && selectionInfo.count > 1)
const collapsedHeight = showSuggestions ? 76 : 48
const maxExpandedHeight = 400
const barWidth = 520 // Consistent width
const maxExpandedHeight = isMobile ? 300 : 400
// Responsive width: full width on mobile, percentage on narrow, fixed on desktop
const barWidth = isMobile ? 'calc(100% - 20px)' : isNarrow ? 'calc(100% - 120px)' : 520
// Calculate dynamic height when expanded based on content
// Header: ~45px, Input area: ~56px, padding: ~24px = ~125px fixed
@ -1360,17 +1372,20 @@ export function MycelialIntelligenceBar() {
className="mycelial-intelligence-bar"
style={{
position: 'fixed',
top: '10px',
// On mobile: bottom of screen, on desktop: top center
top: isMobile ? 'auto' : '10px',
bottom: isMobile ? '70px' : 'auto', // Above bottom toolbar on mobile
left: '50%',
transform: 'translateX(-50%)',
width: barWidth,
maxWidth: isMobile ? 'none' : '520px',
height: isExpanded ? 'auto' : collapsedHeight,
minHeight: isExpanded ? minExpandedHeight : collapsedHeight,
maxHeight: isExpanded ? maxExpandedHeight : collapsedHeight,
zIndex: isModalOpen ? 1 : 99999, // Lower z-index when modals are open
pointerEvents: isModalOpen ? 'none' : 'auto', // Disable interactions when modal is open
opacity: isModalOpen ? 0.3 : 1, // Fade when modal is open
transition: 'opacity 0.2s ease, z-index 0s',
transition: 'opacity 0.2s ease, z-index 0s, top 0.3s ease, bottom 0.3s ease, width 0.3s ease',
}}
onPointerEnter={() => setIsHovering(true)}
onPointerLeave={() => setIsHovering(false)}