perf: eliminate cascading rerenders with granular Zustand selectors

- Delete useDaemonSession hook that selected entire session object
- Move drafts to top-level store (not nested in sessions)
- Refactor all components to use granular useSessionStore selectors
- Fix infinite loop bugs from selector fallbacks (use stable constants)

Before: 15-20 component rerenders per keystroke
After: ~1-2 rerenders per keystroke

🤖 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 16:37:23 +07:00
co-authored by Claude
parent 74c82a312e
commit 21d43bc249
11 changed files with 280 additions and 392 deletions
@@ -65,7 +65,6 @@ import type { WSInboundMessage, SessionOutboundMessage } from "@server/server/me
import { formatConnectionStatus } from "@/utils/daemons";
import { trackAnalyticsEvent } from "@/utils/analytics";
import type { SessionContextValue } from "@/contexts/session-context";
import { useDaemonSession } from "@/hooks/use-daemon-session";
import type { UseWebSocketReturn } from "@/hooks/use-websocket";
import { useSessionStore } from "@/stores/session-store";
@@ -263,26 +262,29 @@ function AgentFlowModal({
}, [serverId, daemonEntries]);
const [selectedServerId, setSelectedServerId] = useState<string | null>(initialServerId);
// Use useDaemonSession to get both state AND APIs
const fullSession = useDaemonSession(selectedServerId, { allowUnavailable: true, suppressUnavailableAlert: true });
// [INVESTIGATION] Using useSessionStore directly with granular selectors
// Get the complete session state for the selected server
const sessionState = useSessionStore((state) =>
selectedServerId ? state.sessions[selectedServerId] : undefined
);
// Extract only what we need for CreateAgentSessionSlice
const session = useMemo<CreateAgentSessionSlice | null>(() => {
if (!fullSession) {
if (!selectedServerId || !sessionState || !sessionState.methods) {
return null;
}
const slice: CreateAgentSessionSlice = {
serverId: fullSession.serverId,
ws: fullSession.ws,
createAgent: fullSession.createAgent,
resumeAgent: fullSession.resumeAgent,
sendAgentAudio: fullSession.sendAgentAudio,
agents: fullSession.agents,
providerModels: fullSession.providerModels,
requestProviderModels: fullSession.requestProviderModels,
serverId: selectedServerId,
ws: sessionState.ws,
createAgent: sessionState.methods.createAgent,
resumeAgent: sessionState.methods.resumeAgent,
sendAgentAudio: sessionState.methods.sendAgentAudio,
agents: sessionState.agents,
providerModels: sessionState.providerModels,
requestProviderModels: sessionState.methods.requestProviderModels,
};
return slice;
}, [fullSession]);
}, [selectedServerId, sessionState]);
// Helper to get session state for background operations
// Note: This only returns state, not APIs. For calling methods, need to send WS messages directly.
@@ -324,7 +326,6 @@ function AgentFlowModal({
}
}, [daemonEntries, initialServerId, isVisible, selectedServerId]);
const ws = session?.ws ?? null;
const inertWebSocket = useMemo<UseWebSocketReturn>(
() => ({
isConnected: false,
@@ -340,6 +341,7 @@ function AgentFlowModal({
}),
[]
);
const ws = session?.ws ?? null;
const effectiveWs: UseWebSocketReturn = ws ?? inertWebSocket;
const createAgent = session?.createAgent;
const resumeAgent = session?.resumeAgent;