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
72 lines
1.8 KiB
TypeScript
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;
|
|
}
|
|
}
|