/** * TeamGraphOverlay — full-screen overlay showing the agent graph. * Follows the exact ProjectEditorOverlay pattern (lazy-loaded, fixed z-50). */ import { useCallback, useState } from 'react'; import { GraphView } from '@claude-teams/agent-graph'; import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; import { useGraphMessagesPanel } from '../hooks/useGraphMessagesPanel'; import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility'; import { useGraphSurfaceInteractions } from '../hooks/useGraphSurfaceInteractions'; import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter'; import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions'; import { GraphActivityHud } from './GraphActivityHud'; import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover'; import { GraphMemberLogPreviewHud } from './GraphMemberLogPreviewHud'; import { GraphNodePopover } from './GraphNodePopover'; import { GraphProvisioningHud } from './GraphProvisioningHud'; import { GraphTransientHandoffHud } from './GraphTransientHandoffHud'; import type { GraphDomainRef, GraphEventPort, TransientHandoffCard, } from '@claude-teams/agent-graph'; export interface TeamGraphOverlayProps { teamName: string; onClose: () => void; onPinAsTab?: () => void; sidebarVisible?: boolean; onToggleSidebar?: () => void; messagesPanelEnabled?: boolean; } export const TeamGraphOverlay = ({ teamName, onClose, onPinAsTab, sidebarVisible, onToggleSidebar, messagesPanelEnabled = true, }: TeamGraphOverlayProps): React.JSX.Element => { const graphData = useTeamGraphAdapter(teamName); const { openTeamPage: openTeamTab, commitOwnerSlotDrop, commitOwnerGridOrderDrop, setLayoutMode, } = useTeamGraphSurfaceActions(teamName); const { sidebarVisible: persistedSidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const interactions = useGraphSurfaceInteractions(teamName); const [messagesPanelMountPoint, setMessagesPanelMountPoint] = useState( null ); const effectiveSidebarVisible = sidebarVisible ?? persistedSidebarVisible; const handleToggleSidebar = onToggleSidebar ?? toggleSidebarVisible; const graphMessagesPanel = useGraphMessagesPanel({ teamName, enabled: messagesPanelEnabled, mountPoint: messagesPanelMountPoint, onOpenMemberProfile: interactions.openMemberProfile, onOpenTaskDetail: interactions.openTaskDetail, }); const openTeamPage = useCallback(() => { openTeamTab(); onClose(); }, [onClose, openTeamTab]); const openCreateTask = useCallback(() => { interactions.openCreateTask(''); }, [interactions]); const events: GraphEventPort = { onNodeDoubleClick: useCallback( (ref: GraphDomainRef) => { if (ref.kind === 'task') interactions.openTaskDetail(ref.taskId); else if (ref.kind === 'member') interactions.openMemberProfile(ref.memberName); }, [interactions] ), onSendMessage: interactions.openSendMessage, onOpenTaskDetail: interactions.openTaskDetail, onOpenMemberProfile: useCallback( (memberName: string) => interactions.openMemberProfile(memberName), [interactions] ), }; return (
{effectiveSidebarVisible ? ( ) : null} } onLayoutModeChange={setLayoutMode} onOwnerSlotDrop={commitOwnerSlotDrop} onOwnerGridOrderDrop={commitOwnerGridOrderDrop} className="team-graph-view min-w-0 flex-1" renderHud={(hudProps) => { const extraHudProps = hudProps as typeof hudProps & { getViewportSize?: () => { width: number; height: number }; getActivityWorldRect?: (ownerNodeId: string) => { left: number; top: number; right: number; bottom: number; width: number; height: number; } | null; getLogWorldRect?: (ownerNodeId: string) => { left: number; top: number; right: number; bottom: number; width: number; height: number; } | null; getCameraZoom?: () => number; getTransientHandoffSnapshot?: (options?: { focusNodeIds?: ReadonlySet | null; focusEdgeIds?: ReadonlySet | null; }) => { cards: TransientHandoffCard[]; time: number; }; worldToScreen?: (x: number, y: number) => { x: number; y: number }; getNodeWorldPosition?: (nodeId: string) => { x: number; y: number } | null; focusEdgeIds?: ReadonlySet | null; }; const { getViewportSize, focusNodeIds, filters } = extraHudProps; return ( <> ); }} renderEdgeOverlay={({ edge, sourceNode, targetNode, onClose: closeEdge, onSelectNode }) => ( )} renderOverlay={({ node, onClose: closePopover }) => ( { interactions.openSendMessage(name); closePopover(); }} onCreateTask={interactions.openCreateTask} onOpenTaskDetail={(id) => { interactions.openTaskDetail(id); closePopover(); }} onOpenMemberProfile={(name, options) => { interactions.openMemberProfile(name, options); closePopover(); }} onStartTask={interactions.onStartTask} onCompleteTask={interactions.onCompleteTask} onApproveTask={interactions.onApproveTask} onRequestReview={interactions.onRequestReview} onRequestChanges={interactions.onRequestChanges} onCancelTask={interactions.onCancelTask} onMoveBackToDone={interactions.onMoveBackToDone} onViewChanges={interactions.openTaskChanges} onDeleteTask={interactions.onDeleteTask} /> )} /> {messagesPanelEnabled ? (
) : null} {graphMessagesPanel} {interactions.dialogs}
); };