perf: move drafts to top-level store to prevent session rerenders
Previously, drafts were nested inside each session object, which caused the entire session to be recreated on every keystroke. This triggered rerenders in all components subscribed to the session. Now drafts are stored at the top level of the Zustand store, keyed by agentId (not serverId:agentId since agent IDs are globally unique). This means draft updates only trigger rerenders in components that subscribe to drafts specifically. Changes: - Move drafts from SessionState to SessionStoreState (top-level) - Add createModalDraft for future use by the create agent modal - Update getDraftInput/saveDraftInput to work with agentId only - Update session-context to clear drafts using new API - Remove investigation console.log statements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -230,9 +230,6 @@ export interface SessionState {
|
||||
// Provider models
|
||||
providerModels: Map<AgentProvider, ProviderModelState>;
|
||||
|
||||
// Draft inputs (non-serializable, stored in Map)
|
||||
draftInputs: Map<string, DraftInput>;
|
||||
|
||||
// Queued messages
|
||||
queuedMessages: Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>>;
|
||||
}
|
||||
@@ -240,6 +237,12 @@ export interface SessionState {
|
||||
// Global store state
|
||||
interface SessionStoreState {
|
||||
sessions: Record<string, SessionState>;
|
||||
|
||||
// Top-level drafts (keyed by agentId, not serverId:agentId since agent IDs are globally unique)
|
||||
drafts: Map<string, DraftInput>;
|
||||
|
||||
// Single draft for the create agent modal
|
||||
createModalDraft: DraftInput | null;
|
||||
}
|
||||
|
||||
// Action types
|
||||
@@ -284,9 +287,13 @@ interface SessionStoreActions {
|
||||
// Provider models
|
||||
setProviderModels: (serverId: string, models: Map<AgentProvider, ProviderModelState> | ((prev: Map<AgentProvider, ProviderModelState>) => Map<AgentProvider, ProviderModelState>)) => void;
|
||||
|
||||
// Draft inputs
|
||||
getDraftInput: (serverId: string, agentId: string) => DraftInput | undefined;
|
||||
saveDraftInput: (serverId: string, agentId: string, draft: DraftInput) => void;
|
||||
// Draft inputs (top-level, not in sessions)
|
||||
getDraftInput: (agentId: string) => DraftInput | undefined;
|
||||
saveDraftInput: (agentId: string, draft: DraftInput) => void;
|
||||
|
||||
// Create modal draft
|
||||
getCreateModalDraft: () => DraftInput | null;
|
||||
saveCreateModalDraft: (draft: DraftInput | null) => void;
|
||||
|
||||
// Queued messages
|
||||
setQueuedMessages: (serverId: string, value: Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>> | ((prev: Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>>) => Map<string, Array<{ id: string; text: string; images?: Array<{ uri: string; mimeType: string }> }>>)) => void;
|
||||
@@ -347,7 +354,6 @@ function createInitialSessionState(serverId: string, ws: UseWebSocketReturn, aud
|
||||
gitDiffs: new Map(),
|
||||
fileExplorer: new Map(),
|
||||
providerModels: new Map(),
|
||||
draftInputs: new Map(),
|
||||
queuedMessages: new Map(),
|
||||
};
|
||||
}
|
||||
@@ -355,6 +361,8 @@ function createInitialSessionState(serverId: string, ws: UseWebSocketReturn, aud
|
||||
export const useSessionStore = create<SessionStore>()(
|
||||
subscribeWithSelector((set, get) => ({
|
||||
sessions: {},
|
||||
drafts: new Map(),
|
||||
createModalDraft: null,
|
||||
|
||||
// Session management
|
||||
initializeSession: (serverId, ws, audioPlayer) => {
|
||||
@@ -675,34 +683,34 @@ export const useSessionStore = create<SessionStore>()(
|
||||
});
|
||||
},
|
||||
|
||||
// Draft inputs
|
||||
getDraftInput: (serverId, agentId) => {
|
||||
const session = get().sessions[serverId];
|
||||
if (!session) {
|
||||
return undefined;
|
||||
}
|
||||
return session.draftInputs.get(agentId);
|
||||
// Draft inputs (top-level, keyed by agentId only)
|
||||
getDraftInput: (agentId) => {
|
||||
return get().drafts.get(agentId);
|
||||
},
|
||||
|
||||
saveDraftInput: (serverId, agentId, draft) => {
|
||||
saveDraftInput: (agentId, draft) => {
|
||||
set((prev) => {
|
||||
const session = prev.sessions[serverId];
|
||||
if (!session) {
|
||||
return prev;
|
||||
}
|
||||
const nextDraftInputs = new Map(session.draftInputs);
|
||||
nextDraftInputs.set(agentId, draft);
|
||||
logSessionStoreUpdate("saveDraftInput", serverId, { agentId });
|
||||
const nextDrafts = new Map(prev.drafts);
|
||||
nextDrafts.set(agentId, draft);
|
||||
return {
|
||||
...prev,
|
||||
sessions: {
|
||||
...prev.sessions,
|
||||
[serverId]: { ...session, draftInputs: nextDraftInputs },
|
||||
},
|
||||
drafts: nextDrafts,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// Create modal draft
|
||||
getCreateModalDraft: () => {
|
||||
return get().createModalDraft;
|
||||
},
|
||||
|
||||
saveCreateModalDraft: (draft) => {
|
||||
set((prev) => ({
|
||||
...prev,
|
||||
createModalDraft: draft,
|
||||
}));
|
||||
},
|
||||
|
||||
// Queued messages
|
||||
setQueuedMessages: (serverId, value) => {
|
||||
set((prev) => {
|
||||
|
||||
Reference in New Issue
Block a user