From 5f645d6b7a5ef0270bef98cfae7f1eeabe563f45 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 3 Dec 2025 20:47:10 +0700 Subject: [PATCH] perf: use shallow selectors in useAggregatedAgents to reduce re-renders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace greedy sessions selector with granular useShallow selectors that only extract agent Maps and methods. This prevents HomeScreen from re-rendering on every agent stream update. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../app/src/hooks/use-aggregated-agents.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/app/src/hooks/use-aggregated-agents.ts b/packages/app/src/hooks/use-aggregated-agents.ts index 5c35cdf0e..a499c86b3 100644 --- a/packages/app/src/hooks/use-aggregated-agents.ts +++ b/packages/app/src/hooks/use-aggregated-agents.ts @@ -1,7 +1,9 @@ import { useMemo, useCallback } from "react"; +import { useShallow } from "zustand/shallow"; import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { useSessionStore } from "@/stores/session-store"; import type { AgentDirectoryEntry } from "@/types/agent-directory"; +import type { Agent } from "@/stores/session-store"; export interface AggregatedAgent extends AgentDirectoryEntry { serverId: string; @@ -18,28 +20,47 @@ export interface AggregatedAgentsResult { export function useAggregatedAgents(): AggregatedAgentsResult { const { connectionStates } = useDaemonConnections(); - const sessions = useSessionStore((state) => state.sessions); + + const sessionAgents = useSessionStore( + useShallow((state) => { + const result: Record | undefined> = {}; + for (const [serverId, session] of Object.entries(state.sessions)) { + result[serverId] = session.agents; + } + return result; + }) + ); + + const sessionMethods = useSessionStore( + useShallow((state) => { + const result: Record | undefined> = {}; + for (const [serverId, session] of Object.entries(state.sessions)) { + result[serverId] = session.methods ?? undefined; + } + return result; + }) + ); const refreshAll = useCallback(() => { console.log('[useAggregatedAgents] Manual refresh triggered for all sessions'); - for (const [serverId, session] of Object.entries(sessions)) { - if (session?.methods?.refreshSession) { + for (const [serverId, methods] of Object.entries(sessionMethods)) { + if (methods?.refreshSession) { console.log(`[useAggregatedAgents] Refreshing session ${serverId}`); - session.methods.refreshSession(); + methods.refreshSession(); } } - }, [sessions]); + }, [sessionMethods]); const result = useMemo(() => { const allAgents: AggregatedAgent[] = []; // Derive agent directory from all sessions - for (const [serverId, session] of Object.entries(sessions)) { - if (!session?.agents || session.agents.size === 0) { + for (const [serverId, agents] of Object.entries(sessionAgents)) { + if (!agents || agents.size === 0) { continue; } const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId; - for (const agent of session.agents.values()) { + for (const agent of agents.values()) { // Use agent's own lastActivityAt field directly allAgents.push({ id: agent.id, @@ -135,7 +156,7 @@ export function useAggregatedAgents(): AggregatedAgentsResult { isInitialLoad, isRevalidating, }; - }, [sessions, connectionStates]); + }, [sessionAgents, connectionStates]); return { ...result,