agent-ecosystem/packages/agent-graph/src/strategies/memberStrategy.ts
Илия 11bb49c53e
feat(graph): force-directed agent graph visualization with kanban-zone task layout
Force-directed graph visualization for agent teams.

Package: @claude-teams/agent-graph (isolated workspace package)
- Space theme: bloom, particles, hex grid, depth stars
- Members as hexagonal nodes with breathing glow
- Tasks as pill cards in kanban columns (todo/wip/done/review/approved) per owner
- Message particles along edges (real-time only)
- Deterministic layout, Figma-style pan, scroll/pinch zoom
- Clean Architecture: ports/adapters/strategies, ES #private classes

Integration: features/agent-graph/ (adapter + overlay + tab)
- Full-screen overlay (Cmd+Shift+G) + Pin as Tab
- Graph button in Team section header
- Frustum culling, zero per-frame allocations, adaptive fps
- Performance overlay via ?perf query param

Also: CI runs on all PR branches, features/CLAUDE.md architecture guide
2026-03-28 12:03:42 +02:00

72 lines
1.8 KiB
TypeScript

/**
* Render strategy for member and lead nodes.
* Uses the holographic hexagonal rendering from draw-agents.ts.
*/
import type { GraphNode } from '../ports/types';
import type { NodeRenderStrategy, NodeRenderState } from './types';
import { drawAgents } from '../canvas/draw-agents';
import { NODE, HIT_DETECTION } from '../constants/canvas-constants';
export class MemberStrategy implements NodeRenderStrategy {
readonly kind = 'member' as const;
draw(ctx: CanvasRenderingContext2D, node: GraphNode, state: NodeRenderState): void {
// drawAgents handles both member and lead — we delegate to it
drawAgents(
ctx,
[node],
state.time,
state.isSelected ? node.id : null,
state.isHovered ? node.id : null,
);
}
hitTest(node: GraphNode, wx: number, wy: number): boolean {
const x = node.x ?? 0;
const y = node.y ?? 0;
const r = NODE.radiusMember + HIT_DETECTION.agentPadding;
const dx = wx - x;
const dy = wy - y;
return dx * dx + dy * dy <= r * r;
}
getCollisionRadius(): number {
return NODE.radiusMember + 20;
}
getChargeStrength(): number {
return -600;
}
}
export class LeadStrategy implements NodeRenderStrategy {
readonly kind = 'lead' as const;
draw(ctx: CanvasRenderingContext2D, node: GraphNode, state: NodeRenderState): void {
drawAgents(
ctx,
[node],
state.time,
state.isSelected ? node.id : null,
state.isHovered ? node.id : null,
);
}
hitTest(node: GraphNode, wx: number, wy: number): boolean {
const x = node.x ?? 0;
const y = node.y ?? 0;
const r = NODE.radiusLead + HIT_DETECTION.agentPadding;
const dx = wx - x;
const dy = wy - y;
return dx * dx + dy * dy <= r * r;
}
getCollisionRadius(): number {
return NODE.radiusLead + 30;
}
getChargeStrength(): number {
return -1200;
}
}