From 548ec0733eeb381bec6ea37233ee79f951397c62 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sun, 7 Dec 2025 17:09:33 -0800 Subject: [PATCH] feat: dark theme social network graph with arrows + responsive MI bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../networking/NetworkGraphMinimap.tsx | 83 +++++++++++++++---- src/ui/MycelialIntelligenceBar.tsx | 23 ++++- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/components/networking/NetworkGraphMinimap.tsx b/src/components/networking/NetworkGraphMinimap.tsx index a86191e..7988697 100644 --- a/src/components/networking/NetworkGraphMinimap.tsx +++ b/src/components/networking/NetworkGraphMinimap.tsx @@ -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(); diff --git a/src/ui/MycelialIntelligenceBar.tsx b/src/ui/MycelialIntelligenceBar.tsx index 98585f6..9d53e87 100644 --- a/src/ui/MycelialIntelligenceBar.tsx +++ b/src/ui/MycelialIntelligenceBar.tsx @@ -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)}