perf: use shallow selectors in useAggregatedAgents to reduce re-renders

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 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-03 20:47:10 +07:00
co-authored by Claude
parent 56e2af2308
commit 5f645d6b7a
@@ -1,7 +1,9 @@
import { useMemo, useCallback } from "react"; import { useMemo, useCallback } from "react";
import { useShallow } from "zustand/shallow";
import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { useDaemonConnections } from "@/contexts/daemon-connections-context";
import { useSessionStore } from "@/stores/session-store"; import { useSessionStore } from "@/stores/session-store";
import type { AgentDirectoryEntry } from "@/types/agent-directory"; import type { AgentDirectoryEntry } from "@/types/agent-directory";
import type { Agent } from "@/stores/session-store";
export interface AggregatedAgent extends AgentDirectoryEntry { export interface AggregatedAgent extends AgentDirectoryEntry {
serverId: string; serverId: string;
@@ -18,28 +20,47 @@ export interface AggregatedAgentsResult {
export function useAggregatedAgents(): AggregatedAgentsResult { export function useAggregatedAgents(): AggregatedAgentsResult {
const { connectionStates } = useDaemonConnections(); const { connectionStates } = useDaemonConnections();
const sessions = useSessionStore((state) => state.sessions);
const sessionAgents = useSessionStore(
useShallow((state) => {
const result: Record<string, Map<string, Agent> | undefined> = {};
for (const [serverId, session] of Object.entries(state.sessions)) {
result[serverId] = session.agents;
}
return result;
})
);
const sessionMethods = useSessionStore(
useShallow((state) => {
const result: Record<string, NonNullable<typeof state.sessions[string]["methods"]> | undefined> = {};
for (const [serverId, session] of Object.entries(state.sessions)) {
result[serverId] = session.methods ?? undefined;
}
return result;
})
);
const refreshAll = useCallback(() => { const refreshAll = useCallback(() => {
console.log('[useAggregatedAgents] Manual refresh triggered for all sessions'); console.log('[useAggregatedAgents] Manual refresh triggered for all sessions');
for (const [serverId, session] of Object.entries(sessions)) { for (const [serverId, methods] of Object.entries(sessionMethods)) {
if (session?.methods?.refreshSession) { if (methods?.refreshSession) {
console.log(`[useAggregatedAgents] Refreshing session ${serverId}`); console.log(`[useAggregatedAgents] Refreshing session ${serverId}`);
session.methods.refreshSession(); methods.refreshSession();
} }
} }
}, [sessions]); }, [sessionMethods]);
const result = useMemo(() => { const result = useMemo(() => {
const allAgents: AggregatedAgent[] = []; const allAgents: AggregatedAgent[] = [];
// Derive agent directory from all sessions // Derive agent directory from all sessions
for (const [serverId, session] of Object.entries(sessions)) { for (const [serverId, agents] of Object.entries(sessionAgents)) {
if (!session?.agents || session.agents.size === 0) { if (!agents || agents.size === 0) {
continue; continue;
} }
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId; 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 // Use agent's own lastActivityAt field directly
allAgents.push({ allAgents.push({
id: agent.id, id: agent.id,
@@ -135,7 +156,7 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
isInitialLoad, isInitialLoad,
isRevalidating, isRevalidating,
}; };
}, [sessions, connectionStates]); }, [sessionAgents, connectionStates]);
return { return {
...result, ...result,