diff --git a/src/features/agent-graph/renderer/hooks/useGraphCreateTaskDialog.tsx b/src/features/agent-graph/renderer/hooks/useGraphCreateTaskDialog.tsx index 3daa41d8..7c61db12 100644 --- a/src/features/agent-graph/renderer/hooks/useGraphCreateTaskDialog.tsx +++ b/src/features/agent-graph/renderer/hooks/useGraphCreateTaskDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; import { api } from '@renderer/api'; import { CreateTaskDialog } from '@renderer/components/team/dialogs/CreateTaskDialog'; diff --git a/src/features/agent-graph/renderer/hooks/useGraphMemberPopoverContext.ts b/src/features/agent-graph/renderer/hooks/useGraphMemberPopoverContext.ts index 92dcf194..11ea2c25 100644 --- a/src/features/agent-graph/renderer/hooks/useGraphMemberPopoverContext.ts +++ b/src/features/agent-graph/renderer/hooks/useGraphMemberPopoverContext.ts @@ -6,29 +6,53 @@ import { } from '@renderer/store/slices/teamSlice'; import { useShallow } from 'zustand/react/shallow'; -import type { TeamGraphData } from '../adapters/TeamGraphAdapter'; +import type { AppState } from '@renderer/store/types'; -export function useGraphMemberPopoverContext(teamName: string, memberName: string) { +interface GraphMemberPopoverContext { + teamData: + | (NonNullable> & { + members: ReturnType; + messageFeed: []; + }) + | null; + teamMembers: ReturnType; + spawnEntry: AppState['memberSpawnStatusesByTeam'][string][string] | undefined; + leadActivity: AppState['leadActivityByTeam'][string] | undefined; + progress: ReturnType | null; + memberSpawnSnapshot: AppState['memberSpawnSnapshotsByTeam'][string] | undefined; + memberSpawnStatuses: AppState['memberSpawnStatusesByTeam'][string] | undefined; +} + +function selectGraphMemberPopoverContext( + state: AppState, + teamName: string, + memberName: string +): GraphMemberPopoverContext { + const snapshot = teamName ? selectTeamDataForName(state, teamName) : null; + const teamMembers = teamName ? selectResolvedMembersForTeamName(state, teamName) : []; + + return { + teamData: snapshot + ? { + ...snapshot, + members: teamMembers, + messageFeed: [], + } + : null, + teamMembers, + spawnEntry: teamName ? state.memberSpawnStatusesByTeam[teamName]?.[memberName] : undefined, + leadActivity: teamName ? state.leadActivityByTeam[teamName] : undefined, + progress: teamName ? getCurrentProvisioningProgressForTeam(state, teamName) : null, + memberSpawnSnapshot: teamName ? state.memberSpawnSnapshotsByTeam[teamName] : undefined, + memberSpawnStatuses: teamName ? state.memberSpawnStatusesByTeam[teamName] : undefined, + }; +} + +export function useGraphMemberPopoverContext( + teamName: string, + memberName: string +): ReturnType { return useStore( - useShallow((state) => { - const snapshot = teamName ? selectTeamDataForName(state, teamName) : null; - const teamMembers = teamName ? selectResolvedMembersForTeamName(state, teamName) : []; - - return { - teamData: snapshot - ? { - ...snapshot, - members: teamMembers, - messageFeed: [], - } - : null, - teamMembers, - spawnEntry: teamName ? state.memberSpawnStatusesByTeam[teamName]?.[memberName] : undefined, - leadActivity: teamName ? state.leadActivityByTeam[teamName] : undefined, - progress: teamName ? getCurrentProvisioningProgressForTeam(state, teamName) : null, - memberSpawnSnapshot: teamName ? state.memberSpawnSnapshotsByTeam[teamName] : undefined, - memberSpawnStatuses: teamName ? state.memberSpawnStatusesByTeam[teamName] : undefined, - }; - }) + useShallow((state) => selectGraphMemberPopoverContext(state, teamName, memberName)) ); } diff --git a/src/features/agent-graph/renderer/ui/GraphActivityHud.tsx b/src/features/agent-graph/renderer/ui/GraphActivityHud.tsx index 4e31fec3..0f7fc916 100644 --- a/src/features/agent-graph/renderer/ui/GraphActivityHud.tsx +++ b/src/features/agent-graph/renderer/ui/GraphActivityHud.tsx @@ -289,6 +289,15 @@ export const GraphActivityHud = ({ const handleMessageClick = useCallback((item: TimelineItem) => { setExpandedItem(item); }, []); + const handleMessageKeyDown = useCallback( + (event: React.KeyboardEvent, item: TimelineItem): void => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + handleMessageClick(item); + } + }, + [handleMessageClick] + ); const handleMemberClick = useCallback( (member: ResolvedTeamMember) => { @@ -360,6 +369,52 @@ export const GraphActivityHud = ({ }; }, [enabled, forwardWheelToGraph, visibleLanes]); + const renderLaneEntry = useCallback( + (entry: InlineActivityEntry, index: number): React.JSX.Element => { + const messageKey = toMessageKey(entry.message); + const timelineItem: TimelineItem = { + type: 'message', + message: entry.message, + }; + const isUnread = !entry.message.read && !readSet.has(messageKey); + + return ( +
handleMessageClick(timelineItem)} + onKeyDown={(event) => handleMessageKeyDown(event, timelineItem)} + > + handleMessageClick(timelineItem)} + onOpenTaskDetail={onOpenTaskDetail} + onOpenMemberProfile={onOpenMemberProfile} + /> +
+ ); + }, + [ + handleMessageClick, + handleMessageKeyDown, + messageContext, + onOpenMemberProfile, + onOpenTaskDetail, + readSet, + teamColorByName, + teamName, + teamNames, + ] + ); + if (!enabled || !teamSnapshot || visibleLanes.length === 0) { return null; } @@ -421,43 +476,7 @@ export const GraphActivityHud = ({ No recent activity ) : null} - {lane.entries.map((entry, index) => { - const messageKey = toMessageKey(entry.message); - const timelineItem: TimelineItem = { - type: 'message', - message: entry.message, - }; - const isUnread = !entry.message.read && !readSet.has(messageKey); - - return ( -
handleMessageClick(timelineItem)} - onKeyDown={(event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - handleMessageClick(timelineItem); - } - }} - > - handleMessageClick(timelineItem)} - onOpenTaskDetail={onOpenTaskDetail} - onOpenMemberProfile={onOpenMemberProfile} - /> -
- ); - })} + {lane.entries.map(renderLaneEntry)} {lane.overflowCount > 0 ? (