Route agent lineage links diagonally

This commit is contained in:
Brennan Benson
2026-08-09 23:23:28 -07:00
parent d3f5f5e73c
commit a5a6a43ce7
4 changed files with 45 additions and 27 deletions
@@ -12,7 +12,7 @@ import type {
} from './agent-map-layout'
import { AGENT_MAP_LINEAGE_RELATION, shouldAggregateAgentMapWorktree } from './agent-map-layout'
import { selectVisibleAgentMapLabels } from './agent-map-label-declutter'
import { agentMapLineageChevronPath } from './agent-map-lineage-chevron-path'
import { agentMapDirectLineageChevronPath } from './agent-map-lineage-chevron-path'
import { AgentMapWorktreeLabel } from './AgentMapWorktreeLabel'
import { AgentMapWorktreeRingNode } from './AgentMapWorktreeRingNode'
@@ -53,18 +53,7 @@ type VisibleAgentLocation = {
}
function agentLineagePath(parent: AgentMapAgentNode, child: AgentMapAgentNode): string {
const dx = child.x - parent.x
const dy = child.y - parent.y
const distance = Math.hypot(dx, dy)
if (distance === 0) {
return `M ${parent.x} ${parent.y}`
}
const unitX = dx / distance
const unitY = dy / distance
return agentMapLineageChevronPath([
{ x: parent.x + unitX * parent.radius, y: parent.y + unitY * parent.radius },
{ x: child.x - unitX * child.radius, y: child.y - unitY * child.radius }
])
return agentMapDirectLineageChevronPath(parent, child)
}
/** Memoization keeps pointer panning to one SVG viewBox write, not a map rerender. */
@@ -14,7 +14,7 @@ import type {
AgentMapWorktreeRing
} from './agent-map-layout'
import { AGENT_MAP_LINEAGE_RELATION, shouldAggregateAgentMapWorktree } from './agent-map-layout'
import { agentMapLineageChevronPath } from './agent-map-lineage-chevron-path'
import { agentMapDirectLineageChevronPath } from './agent-map-lineage-chevron-path'
import { agentMapWorktreeActiveStatus } from './agent-map-worktree-active-status'
type AgentMapWorktreeRingNodeProps = {
@@ -51,20 +51,8 @@ function formatDuration(minutes: number): string {
})
}
// Why direction-aware: packing can place a child level with or above its parent, and
// a fixed downward elbow would then exit the wrong side and draw back through both
// nodes. Leaving the rank tie at +1 keeps the common downward case byte-identical.
function lineagePath(parent: AgentMapAgentNode, child: AgentMapAgentNode): string {
const direction = child.y < parent.y ? -1 : 1
const startY = parent.y + parent.radius * direction
const endY = child.y - child.radius * direction
const branchY = (startY + endY) / 2
return agentMapLineageChevronPath([
{ x: parent.x, y: startY },
{ x: parent.x, y: branchY },
{ x: child.x, y: branchY },
{ x: child.x, y: endY }
])
return agentMapDirectLineageChevronPath(parent, child)
}
function agentName(card: DashboardCard): string {
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest'
import { agentMapDirectLineageChevronPath } from './agent-map-lineage-chevron-path'
describe('agentMapDirectLineageChevronPath', () => {
it('runs every chevron directly from the parent toward the child', () => {
const path = agentMapDirectLineageChevronPath(
{ x: 0, y: 0, radius: 4 },
{ x: 40, y: 40, radius: 4 }
)
const tips = [...path.matchAll(/M [-\d.]+ [-\d.]+ L ([-\d.]+) ([-\d.]+) L/g)].map((match) => ({
x: Number(match[1]),
y: Number(match[2])
}))
expect(tips.length).toBeGreaterThan(1)
expect(tips.every((tip) => tip.x === tip.y)).toBe(true)
expect(tips.at(-1)?.x).toBeGreaterThan(tips[0].x)
})
})
@@ -3,6 +3,10 @@ export type AgentMapLineagePoint = {
y: number
}
type AgentMapLineageNode = AgentMapLineagePoint & {
radius: number
}
type LineageSegment = {
start: AgentMapLineagePoint
unitX: number
@@ -64,3 +68,21 @@ export function agentMapLineageChevronPath(points: AgentMapLineagePoint[]): stri
}
return commands.join(' ')
}
export function agentMapDirectLineageChevronPath(
parent: AgentMapLineageNode,
child: AgentMapLineageNode
): string {
const dx = child.x - parent.x
const dy = child.y - parent.y
const distance = Math.hypot(dx, dy)
if (distance === 0) {
return agentMapLineageChevronPath([parent])
}
const unitX = dx / distance
const unitY = dy / distance
return agentMapLineageChevronPath([
{ x: parent.x + unitX * parent.radius, y: parent.y + unitY * parent.radius },
{ x: child.x - unitX * child.radius, y: child.y - unitY * child.radius }
])
}