feat: improve graph readability with colored edges, bezier curves, and auto-layout

- Color-code edges by semantic role: structural edges (system↔node) in
  muted zinc, agent-to-agent edges in indigo for clear visual separation
- Switch from smoothstep to bezier curves for smoother, more natural
  edge routing that reduces right-angle overlaps
- Stagger agent nodes at alternating X offsets in concurrent, handoff,
  and group-chat layouts so parallel fan-out/fan-in edges take distinct
  bezier paths instead of stacking
- Increase spacing in all layouts to give edges more room
- Add dagre-powered auto-layout button (top-right of canvas) that
  computes optimal left-to-right hierarchical node positions
- Add 2 new tests for auto-layout across all orchestration modes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-24 23:27:43 +01:00
co-authored by Copilot
parent 9b689e1411
commit c81ea6f83b
6 changed files with 179 additions and 32 deletions
+33
View File
@@ -4,6 +4,7 @@ import { createBuiltinPatterns, resolvePatternGraph, type PatternDefinition } fr
import {
addAgentNodeToGraph,
addHandoffEdge,
autoLayoutGraph,
canMoveSequential,
findAgentForNode,
isConnectionAllowed,
@@ -301,3 +302,35 @@ describe('edge deletion rules', () => {
}
});
});
describe('auto-layout', () => {
test('autoLayoutGraph repositions nodes without changing edges', () => {
const pattern = findPattern('handoff');
const graph = resolvePatternGraph(pattern);
const layouted = autoLayoutGraph(graph);
expect(layouted.nodes.length).toBe(graph.nodes.length);
expect(layouted.edges.length).toBe(graph.edges.length);
expect(layouted.edges).toEqual(graph.edges);
const positionsChanged = layouted.nodes.some((n, i) => {
const orig = graph.nodes[i]!;
return n.position.x !== orig.position.x || n.position.y !== orig.position.y;
});
expect(positionsChanged).toBe(true);
});
test('autoLayoutGraph produces finite positions for all modes', () => {
for (const mode of ['sequential', 'concurrent', 'handoff', 'group-chat'] as const) {
const pattern = findPattern(mode);
const graph = resolvePatternGraph(pattern);
const layouted = autoLayoutGraph(graph);
expect(layouted.nodes.length).toBe(graph.nodes.length);
for (const node of layouted.nodes) {
expect(Number.isFinite(node.position.x)).toBe(true);
expect(Number.isFinite(node.position.y)).toBe(true);
}
}
});
});