fix: edges tab truncation and layout polish

- Add minWidth: 0 on all flex containers to enable proper truncation
- Title now truncates with ellipsis instead of pushing panel out
- Explanation row truncates to single line with ellipsis and title tooltip
- Type chip has flexShrink: 0 + whiteSpace: nowrap to prevent wrapping
- Delete button uses compact × with flexShrink: 0 (inline, not separate)
- Edit input has minWidth: 0 to prevent overflow
- Remove expand/collapse (5-item limit) — show all connections in tab
- Tighter spacing (6px padding, 2px gap) matching private repo polish

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
“BeeRad”
2026-02-15 09:28:01 +11:00
co-authored by Claude Opus 4.6
parent f7374590d9
commit 1d323de11a
+110 -116
View File
@@ -107,7 +107,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const [chunkExpanded, setChunkExpanded] = useState<{ [key: number]: boolean }>({}); const [chunkExpanded, setChunkExpanded] = useState<{ [key: number]: boolean }>({});
// Edges expand/collapse state // Edges expand/collapse state
const [edgesExpanded, setEdgesExpanded] = useState<{ [key: number]: boolean }>({}); // edgesExpanded removed — all connections shown in Edges tab
// Connections section collapsed state (default closed) // Connections section collapsed state (default closed)
const [edgeSearchOpen, setEdgeSearchOpen] = useState(false); const [edgeSearchOpen, setEdgeSearchOpen] = useState(false);
@@ -1369,56 +1369,96 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
</div> </div>
)} )}
{/* Existing Connections */} {/* Connections List */}
<div style={{ flex: 1, overflowY: 'auto' }}> <div style={{ flex: 1, overflowY: 'auto' }}>
<div style={{ fontSize: '12px', color: '#737373', marginBottom: '12px', letterSpacing: '0.02em' }}>
Existing Connections
</div>
{loadingEdges.has(activeTab) ? ( {loadingEdges.has(activeTab) ? (
<div style={{ color: '#777', fontSize: '12px', fontStyle: 'italic' }}>Loading connections</div> <div style={{ color: '#555', fontSize: '12px', fontStyle: 'italic', padding: '16px 0', textAlign: 'center' }}>Loading connections</div>
) : (() => { ) : (() => {
const list = edgesData[activeTab] || []; const list = edgesData[activeTab] || [];
if (list.length === 0) { if (list.length === 0) {
return <div style={{ color: '#777', fontSize: '12px', fontStyle: 'italic' }}>No connections yet. Search above to add one.</div>; return <div style={{ color: '#555', fontSize: '12px', fontStyle: 'italic', padding: '16px 0', textAlign: 'center' }}>No connections yet.</div>;
} }
const visible = edgesExpanded[activeTab] ? list : list.slice(0, 5);
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}> <div style={{ display: 'flex', flexDirection: 'column' }}>
{visible.map((connection) => ( {list.map((connection) => (
<div key={connection.id} style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px' }}> <div
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '4px' }}> key={connection.id}
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}> style={{
{/* Direction arrow: → if current node is FROM (outgoing), ← if current node is TO (incoming) */} padding: '6px 0',
borderBottom: '1px solid #141414',
display: 'flex',
flexDirection: 'column',
gap: '2px',
minWidth: 0,
}}
>
{/* Row 1: arrow + ID + icon + title + delete */}
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 }}>
<span style={{ <span style={{
fontSize: '12px', fontSize: '11px',
color: connection.edge.from_node_id === activeTab ? '#22c55e' : '#f59e0b', color: connection.edge.from_node_id === activeTab ? '#22c55e' : '#f59e0b',
fontWeight: 600, fontWeight: 600,
width: '16px', width: '14px',
textAlign: 'center' textAlign: 'center',
flexShrink: 0,
}}> }}>
{connection.edge.from_node_id === activeTab ? '→' : '←'} {connection.edge.from_node_id === activeTab ? '→' : '←'}
</span> </span>
<span style={{ <span style={{
display: 'inline-block', fontSize: '9px',
fontSize: '10px',
fontWeight: 700, fontWeight: 700,
color: '#000', color: '#000',
background: '#22c55e', background: '#22c55e',
padding: '2px 6px', padding: '1px 5px',
borderRadius: '6px', borderRadius: '4px',
minWidth: '20px', flexShrink: 0,
textAlign: 'center', fontFamily: "'SF Mono', 'Fira Code', monospace",
letterSpacing: '0.05em'
}}> }}>
{connection.connected_node.id} {connection.connected_node.id}
</span> </span>
<span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}> <span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
{getNodeIcon(connection.connected_node, dimensionIcons, 12)} {getNodeIcon(connection.connected_node, dimensionIcons, 12)}
</span> </span>
<span style={{ color: '#f8fafc', fontSize: '13px', fontWeight: 500 }}>{connection.connected_node.title}</span> <span
onClick={() => onNodeClick?.(connection.connected_node.id)}
style={{
fontSize: '12px',
color: '#e5e5e5',
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,
minWidth: 0,
cursor: 'pointer',
}}
onMouseEnter={(e) => { e.currentTarget.style.color = '#22c55e'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = '#e5e5e5'; }}
>
{connection.connected_node.title}
</span>
<button
onClick={() => deleteEdge(connection.edge.id)}
disabled={deletingEdge === connection.edge.id}
style={{
color: '#444',
fontSize: '14px',
background: 'transparent',
border: 'none',
cursor: deletingEdge === connection.edge.id ? 'not-allowed' : 'pointer',
padding: '0 2px',
flexShrink: 0,
lineHeight: 1,
}}
onMouseEnter={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = '#dc2626'; }}
onMouseLeave={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = '#444'; }}
>
{deletingEdge === connection.edge.id ? '...' : '×'}
</button>
</div> </div>
{/* Row 2: type chip + explanation (compact, single line) */}
{edgeEditingId === connection.edge.id ? ( {edgeEditingId === connection.edge.id ? (
<div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}> <div style={{ display: 'flex', gap: '4px', alignItems: 'center', paddingLeft: '20px' }}>
<input <input
value={edgeEditingValue} value={edgeEditingValue}
onChange={(e) => setEdgeEditingValue(e.target.value)} onChange={(e) => setEdgeEditingValue(e.target.value)}
@@ -1435,12 +1475,13 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
flex: 1, flex: 1,
fontSize: '11px', fontSize: '11px',
color: '#ddd', color: '#ddd',
background: 'transparent', background: '#111',
border: '1px solid #1a1a1a', border: '1px solid #2a2a2a',
borderRadius: '0', borderRadius: '4px',
padding: '4px', padding: '3px 6px',
outline: 'none', outline: 'none',
fontFamily: 'inherit' fontFamily: 'inherit',
minWidth: 0,
}} }}
placeholder="Add explanation…" placeholder="Add explanation…"
autoFocus autoFocus
@@ -1450,127 +1491,80 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
disabled={edgeSavingId === connection.edge.id} disabled={edgeSavingId === connection.edge.id}
style={{ style={{
fontSize: '10px', fontSize: '10px',
color: edgeSavingId === connection.edge.id ? '#555' : '#999', color: '#000',
background: 'transparent', background: '#22c55e',
border: '1px solid #222', border: 'none',
borderRadius: '2px', borderRadius: '4px',
padding: '3px 6px', padding: '3px 8px',
cursor: edgeSavingId === connection.edge.id ? 'not-allowed' : 'pointer' cursor: edgeSavingId === connection.edge.id ? 'not-allowed' : 'pointer',
fontWeight: 600,
flexShrink: 0,
}} }}
> >
{edgeSavingId === connection.edge.id ? 'saving…' : 'save'} {edgeSavingId === connection.edge.id ? '...' : 'Save'}
</button> </button>
<button <button
onClick={cancelEditEdgeExplanation} onClick={cancelEditEdgeExplanation}
disabled={edgeSavingId === connection.edge.id}
style={{ style={{
fontSize: '10px', fontSize: '10px',
color: '#666', color: '#666',
background: 'transparent', background: 'transparent',
border: 'none', border: 'none',
padding: '3px 4px', padding: '3px 4px',
cursor: 'pointer' cursor: 'pointer',
flexShrink: 0,
}} }}
> >
cancel Cancel
</button> </button>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> <div
onClick={() => startEditEdgeExplanation(connection.edge.id, connection.edge.context?.explanation)}
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
paddingLeft: '20px',
cursor: 'pointer',
minWidth: 0,
}}
onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.8'; }}
onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
>
{typeof connection.edge.context?.type === 'string' && ( {typeof connection.edge.context?.type === 'string' && (
<span style={{ <span style={{
fontSize: '10px', fontSize: '9px',
color: '#a3a3a3', color: '#888',
background: '#1f1f1f', background: '#1a1a1a',
border: '1px solid #262626', border: '1px solid #222',
padding: '2px 6px', padding: '1px 5px',
borderRadius: '999px', borderRadius: '999px',
textTransform: 'none' flexShrink: 0,
whiteSpace: 'nowrap',
}}> }}>
{String(connection.edge.context.type).replace(/_/g, ' ')} {String(connection.edge.context.type).replace(/_/g, ' ')}
</span> </span>
)} )}
{connection.edge.context?.explanation ? (
<span <span
style={{ style={{
color: '#94a3b8', fontSize: '11px',
fontSize: '12px', color: connection.edge.context?.explanation ? '#777' : '#444',
fontStyle: connection.edge.context?.explanation ? 'normal' : 'italic',
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
flex: 1,
minWidth: 0, minWidth: 0,
}} }}
title={String(connection.edge.context.explanation)} title={String(connection.edge.context?.explanation || '')}
> >
{connection.edge.context.explanation} {(connection.edge.context?.explanation as string) || 'Add explanation...'}
</span> </span>
) : (
<span style={{ color: '#64748b', fontSize: '12px', fontStyle: 'italic' }}>No explanation</span>
)}
<button
onClick={() => startEditEdgeExplanation(connection.edge.id, connection.edge.context?.explanation)}
style={{
fontSize: '11px',
color: '#94a3b8',
background: 'transparent',
border: 'none',
cursor: 'pointer',
textDecoration: 'underline'
}}
onMouseEnter={(e) => { e.currentTarget.style.color = '#cbd5f5'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = '#94a3b8'; }}
>
edit
</button>
</div> </div>
)} )}
</div> </div>
<button
onClick={() => deleteEdge(connection.edge.id)}
disabled={deletingEdge === connection.edge.id}
style={{
color: deletingEdge === connection.edge.id ? '#64748b' : '#94a3b8',
fontSize: '12px',
background: 'transparent',
border: 'none',
cursor: deletingEdge === connection.edge.id ? 'not-allowed' : 'pointer',
padding: '2px 4px',
transition: 'color 0.2s'
}}
onMouseEnter={(e) => {
if (deletingEdge !== connection.edge.id) {
e.currentTarget.style.color = '#dc2626';
}
}}
onMouseLeave={(e) => {
if (deletingEdge !== connection.edge.id) {
e.currentTarget.style.color = '#94a3b8';
}
}}
>
{deletingEdge === connection.edge.id ? '...' : '×'}
</button>
</div>
))} ))}
{list.length > 5 && (
<div style={{ marginTop: '6px' }}>
<button
onClick={() => setEdgesExpanded(prev => ({ ...prev, [activeTab]: !prev[activeTab] }))}
style={{
fontSize: '9px',
color: '#666',
background: 'transparent',
border: 'none',
cursor: 'pointer',
transition: 'color 0.2s'
}}
onMouseEnter={(e) => { e.currentTarget.style.color = '#999'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = '#666'; }}
>
{edgesExpanded[activeTab] ? 'show less' : `show ${list.length - 5} more`}
</button>
</div>
)}
</div> </div>
); );
})()} })()}