perf(team): pause hidden graph projection
This commit is contained in:
parent
364c5c6471
commit
67826eb275
2 changed files with 98 additions and 46 deletions
|
|
@ -21,9 +21,38 @@ import { TeamGraphAdapter } from '../adapters/TeamGraphAdapter';
|
||||||
|
|
||||||
import type { TeamGraphData } from '../adapters/TeamGraphAdapter';
|
import type { TeamGraphData } from '../adapters/TeamGraphAdapter';
|
||||||
import type { GraphDataPort } from '@claude-teams/agent-graph';
|
import type { GraphDataPort } from '@claude-teams/agent-graph';
|
||||||
|
import type { InboxMessage, ResolvedTeamMember, ToolApprovalRequest } from '@shared/types/team';
|
||||||
|
|
||||||
export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
interface UseTeamGraphAdapterOptions {
|
||||||
|
active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_MEMBERS: ResolvedTeamMember[] = [];
|
||||||
|
const EMPTY_MESSAGES: InboxMessage[] = [];
|
||||||
|
const EMPTY_PENDING_APPROVALS: ToolApprovalRequest[] = [];
|
||||||
|
const EMPTY_PENDING_APPROVAL_AGENTS = new Set<string>();
|
||||||
|
const EMPTY_COMMENT_READ_STATE: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
function getEmptyCommentReadState(): Record<string, unknown> {
|
||||||
|
return EMPTY_COMMENT_READ_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeNoop(): () => void {
|
||||||
|
return () => undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyGraphData(teamName: string): GraphDataPort {
|
||||||
|
return { nodes: [], edges: [], particles: [], teamName, isAlive: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTeamGraphAdapter(
|
||||||
|
teamName: string,
|
||||||
|
options?: UseTeamGraphAdapterOptions
|
||||||
|
): GraphDataPort {
|
||||||
|
const isActive = options?.active ?? true;
|
||||||
const adapterRef = useRef<TeamGraphAdapter>(TeamGraphAdapter.create());
|
const adapterRef = useRef<TeamGraphAdapter>(TeamGraphAdapter.create());
|
||||||
|
const inactiveGraphData = useMemo(() => emptyGraphData(teamName), [teamName]);
|
||||||
|
const lastActiveGraphDataRef = useRef<GraphDataPort>(inactiveGraphData);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
teamSnapshot,
|
teamSnapshot,
|
||||||
|
|
@ -45,27 +74,32 @@ export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
||||||
ensureTeamGraphSlotAssignments,
|
ensureTeamGraphSlotAssignments,
|
||||||
} = useStore(
|
} = useStore(
|
||||||
useShallow((s) => ({
|
useShallow((s) => ({
|
||||||
teamSnapshot: selectTeamDataForName(s, teamName),
|
teamSnapshot: isActive ? selectTeamDataForName(s, teamName) : null,
|
||||||
members: selectResolvedMembersForTeamName(s, teamName),
|
members: isActive ? selectResolvedMembersForTeamName(s, teamName) : EMPTY_MEMBERS,
|
||||||
messages: selectTeamMessages(s, teamName),
|
messages: isActive ? selectTeamMessages(s, teamName) : EMPTY_MESSAGES,
|
||||||
spawnStatuses: teamName ? s.memberSpawnStatusesByTeam[teamName] : undefined,
|
spawnStatuses: isActive && teamName ? s.memberSpawnStatusesByTeam[teamName] : undefined,
|
||||||
leadActivity: teamName ? s.leadActivityByTeam[teamName] : undefined,
|
leadActivity: isActive && teamName ? s.leadActivityByTeam[teamName] : undefined,
|
||||||
leadContext: teamName ? s.leadContextByTeam[teamName] : undefined,
|
leadContext: isActive && teamName ? s.leadContextByTeam[teamName] : undefined,
|
||||||
pendingApprovals: s.pendingApprovals,
|
pendingApprovals: isActive ? s.pendingApprovals : EMPTY_PENDING_APPROVALS,
|
||||||
activeTools: teamName ? s.activeToolsByTeam[teamName] : undefined,
|
activeTools: isActive && teamName ? s.activeToolsByTeam[teamName] : undefined,
|
||||||
finishedVisible: teamName ? s.finishedVisibleByTeam[teamName] : undefined,
|
finishedVisible: isActive && teamName ? s.finishedVisibleByTeam[teamName] : undefined,
|
||||||
toolHistory: teamName ? s.toolHistoryByTeam[teamName] : undefined,
|
toolHistory: isActive && teamName ? s.toolHistoryByTeam[teamName] : undefined,
|
||||||
provisioningProgress: teamName ? getCurrentProvisioningProgressForTeam(s, teamName) : null,
|
provisioningProgress:
|
||||||
memberSpawnSnapshot: teamName ? s.memberSpawnSnapshotsByTeam[teamName] : undefined,
|
isActive && teamName ? getCurrentProvisioningProgressForTeam(s, teamName) : null,
|
||||||
graphLayoutMode: teamName ? s.graphLayoutModeByTeam[teamName] : undefined,
|
memberSpawnSnapshot:
|
||||||
gridOwnerOrder: teamName ? s.gridOwnerOrderByTeam[teamName] : undefined,
|
isActive && teamName ? s.memberSpawnSnapshotsByTeam[teamName] : undefined,
|
||||||
slotAssignments: teamName ? s.slotAssignmentsByTeam[teamName] : undefined,
|
graphLayoutMode: isActive && teamName ? s.graphLayoutModeByTeam[teamName] : undefined,
|
||||||
graphLayoutSession: teamName ? s.graphLayoutSessionByTeam[teamName] : undefined,
|
gridOwnerOrder: isActive && teamName ? s.gridOwnerOrderByTeam[teamName] : undefined,
|
||||||
|
slotAssignments: isActive && teamName ? s.slotAssignmentsByTeam[teamName] : undefined,
|
||||||
|
graphLayoutSession: isActive && teamName ? s.graphLayoutSessionByTeam[teamName] : undefined,
|
||||||
ensureTeamGraphSlotAssignments: s.ensureTeamGraphSlotAssignments,
|
ensureTeamGraphSlotAssignments: s.ensureTeamGraphSlotAssignments,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
const pendingApprovalAgents = useMemo(() => {
|
const pendingApprovalAgents = useMemo(() => {
|
||||||
|
if (!isActive) {
|
||||||
|
return EMPTY_PENDING_APPROVAL_AGENTS;
|
||||||
|
}
|
||||||
const agents = new Set<string>();
|
const agents = new Set<string>();
|
||||||
for (const a of pendingApprovals) {
|
for (const a of pendingApprovals) {
|
||||||
if (a.teamName === teamName) {
|
if (a.teamName === teamName) {
|
||||||
|
|
@ -73,7 +107,7 @@ export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return agents;
|
return agents;
|
||||||
}, [pendingApprovals, teamName]);
|
}, [isActive, pendingApprovals, teamName]);
|
||||||
|
|
||||||
const teamData = useMemo<TeamGraphData | null>(() => {
|
const teamData = useMemo<TeamGraphData | null>(() => {
|
||||||
if (!teamSnapshot) {
|
if (!teamSnapshot) {
|
||||||
|
|
@ -86,7 +120,10 @@ export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
||||||
};
|
};
|
||||||
}, [members, messages, teamSnapshot]);
|
}, [members, messages, teamSnapshot]);
|
||||||
|
|
||||||
const commentReadState = useSyncExternalStore(subscribe, getSnapshot);
|
const commentReadState = useSyncExternalStore(
|
||||||
|
isActive ? subscribe : subscribeNoop,
|
||||||
|
isActive ? getSnapshot : getEmptyCommentReadState
|
||||||
|
);
|
||||||
|
|
||||||
const effectiveSlotAssignments = useMemo(() => {
|
const effectiveSlotAssignments = useMemo(() => {
|
||||||
if (!teamData) {
|
if (!teamData) {
|
||||||
|
|
@ -127,32 +164,17 @@ export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
||||||
}, [graphLayoutSession, slotAssignments, teamData]);
|
}, [graphLayoutSession, slotAssignments, teamData]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!teamName || !teamData) {
|
if (!isActive || !teamName || !teamData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ensureTeamGraphSlotAssignments(teamName, teamData.members, teamData.config.members ?? []);
|
ensureTeamGraphSlotAssignments(teamName, teamData.members, teamData.config.members ?? []);
|
||||||
}, [ensureTeamGraphSlotAssignments, teamData, teamName]);
|
}, [ensureTeamGraphSlotAssignments, isActive, teamData, teamName]);
|
||||||
|
|
||||||
return useMemo(
|
const activeGraphData = useMemo(() => {
|
||||||
() =>
|
if (!isActive) {
|
||||||
adapterRef.current.adapt(
|
return null;
|
||||||
teamData,
|
}
|
||||||
teamName,
|
return adapterRef.current.adapt(
|
||||||
spawnStatuses,
|
|
||||||
leadActivity,
|
|
||||||
leadContext,
|
|
||||||
pendingApprovalAgents,
|
|
||||||
activeTools,
|
|
||||||
finishedVisible,
|
|
||||||
toolHistory,
|
|
||||||
commentReadState,
|
|
||||||
provisioningProgress,
|
|
||||||
memberSpawnSnapshot,
|
|
||||||
effectiveSlotAssignments,
|
|
||||||
graphLayoutMode ?? 'radial',
|
|
||||||
gridOwnerOrder
|
|
||||||
),
|
|
||||||
[
|
|
||||||
teamData,
|
teamData,
|
||||||
teamName,
|
teamName,
|
||||||
spawnStatuses,
|
spawnStatuses,
|
||||||
|
|
@ -166,8 +188,38 @@ export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
||||||
provisioningProgress,
|
provisioningProgress,
|
||||||
memberSpawnSnapshot,
|
memberSpawnSnapshot,
|
||||||
effectiveSlotAssignments,
|
effectiveSlotAssignments,
|
||||||
graphLayoutMode,
|
graphLayoutMode ?? 'radial',
|
||||||
gridOwnerOrder,
|
gridOwnerOrder
|
||||||
]
|
);
|
||||||
);
|
}, [
|
||||||
|
isActive,
|
||||||
|
teamData,
|
||||||
|
teamName,
|
||||||
|
spawnStatuses,
|
||||||
|
leadActivity,
|
||||||
|
leadContext,
|
||||||
|
pendingApprovalAgents,
|
||||||
|
activeTools,
|
||||||
|
finishedVisible,
|
||||||
|
toolHistory,
|
||||||
|
commentReadState,
|
||||||
|
provisioningProgress,
|
||||||
|
memberSpawnSnapshot,
|
||||||
|
effectiveSlotAssignments,
|
||||||
|
graphLayoutMode,
|
||||||
|
gridOwnerOrder,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (activeGraphData) {
|
||||||
|
lastActiveGraphDataRef.current = activeGraphData;
|
||||||
|
}
|
||||||
|
}, [activeGraphData]);
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
const lastActiveGraphData = lastActiveGraphDataRef.current;
|
||||||
|
return lastActiveGraphData.teamName === teamName ? lastActiveGraphData : inactiveGraphData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeGraphData ?? inactiveGraphData;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export const TeamGraphTab = ({
|
||||||
isActive = true,
|
isActive = true,
|
||||||
isPaneFocused = false,
|
isPaneFocused = false,
|
||||||
}: TeamGraphTabProps): React.JSX.Element => {
|
}: TeamGraphTabProps): React.JSX.Element => {
|
||||||
const graphData = useTeamGraphAdapter(teamName);
|
const graphData = useTeamGraphAdapter(teamName, { active: isActive });
|
||||||
const { openTeamPage, commitOwnerSlotDrop, commitOwnerGridOrderDrop, setLayoutMode } =
|
const { openTeamPage, commitOwnerSlotDrop, commitOwnerGridOrderDrop, setLayoutMode } =
|
||||||
useTeamGraphSurfaceActions(teamName);
|
useTeamGraphSurfaceActions(teamName);
|
||||||
const [fullscreen, setFullscreen] = useState(false);
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue