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:
co-authored by
Claude Opus 4.6
parent
f7374590d9
commit
1d323de11a
+186
-192
@@ -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,208 +1369,202 @@ 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',
|
||||||
<span style={{
|
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={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: connection.edge.from_node_id === activeTab ? '#22c55e' : '#f59e0b',
|
||||||
|
fontWeight: 600,
|
||||||
|
width: '14px',
|
||||||
|
textAlign: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
{connection.edge.from_node_id === activeTab ? '→' : '←'}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '9px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: '#000',
|
||||||
|
background: '#22c55e',
|
||||||
|
padding: '1px 5px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
flexShrink: 0,
|
||||||
|
fontFamily: "'SF Mono', 'Fira Code', monospace",
|
||||||
|
}}>
|
||||||
|
{connection.connected_node.id}
|
||||||
|
</span>
|
||||||
|
<span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
|
||||||
|
{getNodeIcon(connection.connected_node, dimensionIcons, 12)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
onClick={() => onNodeClick?.(connection.connected_node.id)}
|
||||||
|
style={{
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: connection.edge.from_node_id === activeTab ? '#22c55e' : '#f59e0b',
|
color: '#e5e5e5',
|
||||||
fontWeight: 600,
|
fontWeight: 500,
|
||||||
width: '16px',
|
overflow: 'hidden',
|
||||||
textAlign: 'center'
|
textOverflow: 'ellipsis',
|
||||||
}}>
|
whiteSpace: 'nowrap',
|
||||||
{connection.edge.from_node_id === activeTab ? '→' : '←'}
|
flex: 1,
|
||||||
</span>
|
minWidth: 0,
|
||||||
<span style={{
|
cursor: 'pointer',
|
||||||
display: 'inline-block',
|
}}
|
||||||
fontSize: '10px',
|
onMouseEnter={(e) => { e.currentTarget.style.color = '#22c55e'; }}
|
||||||
fontWeight: 700,
|
onMouseLeave={(e) => { e.currentTarget.style.color = '#e5e5e5'; }}
|
||||||
color: '#000',
|
>
|
||||||
background: '#22c55e',
|
{connection.connected_node.title}
|
||||||
padding: '2px 6px',
|
</span>
|
||||||
borderRadius: '6px',
|
<button
|
||||||
minWidth: '20px',
|
onClick={() => deleteEdge(connection.edge.id)}
|
||||||
textAlign: 'center',
|
disabled={deletingEdge === connection.edge.id}
|
||||||
letterSpacing: '0.05em'
|
style={{
|
||||||
}}>
|
color: '#444',
|
||||||
{connection.connected_node.id}
|
fontSize: '14px',
|
||||||
</span>
|
background: 'transparent',
|
||||||
<span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
|
border: 'none',
|
||||||
{getNodeIcon(connection.connected_node, dimensionIcons, 12)}
|
cursor: deletingEdge === connection.edge.id ? 'not-allowed' : 'pointer',
|
||||||
</span>
|
padding: '0 2px',
|
||||||
<span style={{ color: '#f8fafc', fontSize: '13px', fontWeight: 500 }}>{connection.connected_node.title}</span>
|
flexShrink: 0,
|
||||||
</div>
|
lineHeight: 1,
|
||||||
{edgeEditingId === connection.edge.id ? (
|
}}
|
||||||
<div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
|
onMouseEnter={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = '#dc2626'; }}
|
||||||
<input
|
onMouseLeave={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = '#444'; }}
|
||||||
value={edgeEditingValue}
|
>
|
||||||
onChange={(e) => setEdgeEditingValue(e.target.value)}
|
{deletingEdge === connection.edge.id ? '...' : '×'}
|
||||||
onKeyDown={(e) => {
|
</button>
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
saveEdgeExplanation(connection.edge.id, connection.edge.context);
|
|
||||||
} else if (e.key === 'Escape') {
|
|
||||||
e.preventDefault();
|
|
||||||
cancelEditEdgeExplanation();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
fontSize: '11px',
|
|
||||||
color: '#ddd',
|
|
||||||
background: 'transparent',
|
|
||||||
border: '1px solid #1a1a1a',
|
|
||||||
borderRadius: '0',
|
|
||||||
padding: '4px',
|
|
||||||
outline: 'none',
|
|
||||||
fontFamily: 'inherit'
|
|
||||||
}}
|
|
||||||
placeholder="Add explanation…"
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => saveEdgeExplanation(connection.edge.id, connection.edge.context)}
|
|
||||||
disabled={edgeSavingId === connection.edge.id}
|
|
||||||
style={{
|
|
||||||
fontSize: '10px',
|
|
||||||
color: edgeSavingId === connection.edge.id ? '#555' : '#999',
|
|
||||||
background: 'transparent',
|
|
||||||
border: '1px solid #222',
|
|
||||||
borderRadius: '2px',
|
|
||||||
padding: '3px 6px',
|
|
||||||
cursor: edgeSavingId === connection.edge.id ? 'not-allowed' : 'pointer'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{edgeSavingId === connection.edge.id ? 'saving…' : 'save'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={cancelEditEdgeExplanation}
|
|
||||||
disabled={edgeSavingId === connection.edge.id}
|
|
||||||
style={{
|
|
||||||
fontSize: '10px',
|
|
||||||
color: '#666',
|
|
||||||
background: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
padding: '3px 4px',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
{typeof connection.edge.context?.type === 'string' && (
|
|
||||||
<span style={{
|
|
||||||
fontSize: '10px',
|
|
||||||
color: '#a3a3a3',
|
|
||||||
background: '#1f1f1f',
|
|
||||||
border: '1px solid #262626',
|
|
||||||
padding: '2px 6px',
|
|
||||||
borderRadius: '999px',
|
|
||||||
textTransform: 'none'
|
|
||||||
}}>
|
|
||||||
{String(connection.edge.context.type).replace(/_/g, ' ')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{connection.edge.context?.explanation ? (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: '#94a3b8',
|
|
||||||
fontSize: '12px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
minWidth: 0,
|
|
||||||
}}
|
|
||||||
title={String(connection.edge.context.explanation)}
|
|
||||||
>
|
|
||||||
— {connection.edge.context.explanation}
|
|
||||||
</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>
|
||||||
<button
|
{/* Row 2: type chip + explanation (compact, single line) */}
|
||||||
onClick={() => deleteEdge(connection.edge.id)}
|
{edgeEditingId === connection.edge.id ? (
|
||||||
disabled={deletingEdge === connection.edge.id}
|
<div style={{ display: 'flex', gap: '4px', alignItems: 'center', paddingLeft: '20px' }}>
|
||||||
style={{
|
<input
|
||||||
color: deletingEdge === connection.edge.id ? '#64748b' : '#94a3b8',
|
value={edgeEditingValue}
|
||||||
fontSize: '12px',
|
onChange={(e) => setEdgeEditingValue(e.target.value)}
|
||||||
background: 'transparent',
|
onKeyDown={(e) => {
|
||||||
border: 'none',
|
if (e.key === 'Enter') {
|
||||||
cursor: deletingEdge === connection.edge.id ? 'not-allowed' : 'pointer',
|
e.preventDefault();
|
||||||
padding: '2px 4px',
|
saveEdgeExplanation(connection.edge.id, connection.edge.context);
|
||||||
transition: 'color 0.2s'
|
} else if (e.key === 'Escape') {
|
||||||
}}
|
e.preventDefault();
|
||||||
onMouseEnter={(e) => {
|
cancelEditEdgeExplanation();
|
||||||
if (deletingEdge !== connection.edge.id) {
|
}
|
||||||
e.currentTarget.style.color = '#dc2626';
|
}}
|
||||||
}
|
style={{
|
||||||
}}
|
flex: 1,
|
||||||
onMouseLeave={(e) => {
|
fontSize: '11px',
|
||||||
if (deletingEdge !== connection.edge.id) {
|
color: '#ddd',
|
||||||
e.currentTarget.style.color = '#94a3b8';
|
background: '#111',
|
||||||
}
|
border: '1px solid #2a2a2a',
|
||||||
}}
|
borderRadius: '4px',
|
||||||
>
|
padding: '3px 6px',
|
||||||
{deletingEdge === connection.edge.id ? '...' : '×'}
|
outline: 'none',
|
||||||
</button>
|
fontFamily: 'inherit',
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
placeholder="Add explanation…"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => saveEdgeExplanation(connection.edge.id, connection.edge.context)}
|
||||||
|
disabled={edgeSavingId === connection.edge.id}
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
color: '#000',
|
||||||
|
background: '#22c55e',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '3px 8px',
|
||||||
|
cursor: edgeSavingId === connection.edge.id ? 'not-allowed' : 'pointer',
|
||||||
|
fontWeight: 600,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{edgeSavingId === connection.edge.id ? '...' : 'Save'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={cancelEditEdgeExplanation}
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
color: '#666',
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
padding: '3px 4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<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' && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: '9px',
|
||||||
|
color: '#888',
|
||||||
|
background: '#1a1a1a',
|
||||||
|
border: '1px solid #222',
|
||||||
|
padding: '1px 5px',
|
||||||
|
borderRadius: '999px',
|
||||||
|
flexShrink: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}>
|
||||||
|
{String(connection.edge.context.type).replace(/_/g, ' ')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: connection.edge.context?.explanation ? '#777' : '#444',
|
||||||
|
fontStyle: connection.edge.context?.explanation ? 'normal' : 'italic',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
title={String(connection.edge.context?.explanation || '')}
|
||||||
|
>
|
||||||
|
{(connection.edge.context?.explanation as string) || 'Add explanation...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</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>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
Reference in New Issue
Block a user