mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-04 21:47:43 +00:00
Fix session processing state leaking across session switches
Root cause: - Processing ownership was derived from UI view state in ChatInterface. - While switching sessions, an in-flight isLoading=true could stamp the newly selected session as processing, even if a different session was actually running. - session-status handling only promoted isProcessing=true and did not clear stale processing state on isProcessing=false, which could leave sessions blocked. Changes: - ChatInterface: removed implicit processing propagation effect that called onSessionProcessing(selectedSession?.id || currentSessionId) when isLoading was true. This decouples processing ownership from transient view/session transitions. - useChatComposerState: added onSessionProcessing callback usage and explicitly marks processing at submit time for the concrete effectiveSessionId (non-temporary IDs only). This ties processing to the session that actually started work. - useChatRealtimeHandlers: expanded session-status handling to support both states. - isProcessing=true: mark session as processing; set loading/abort only when it is the currently viewed session. - isProcessing=false: clear active+processing markers and clear loading indicators for the current session view. Behavioral outcome: - Running session A no longer blocks session B after navigation. - Users can work in multiple sessions concurrently, and processing badges/loading state stay session-scoped. Verification: - npm run typecheck - npm run build
This commit is contained in:
@@ -47,6 +47,7 @@ interface UseChatComposerStateArgs {
|
|||||||
sendMessage: (message: unknown) => void;
|
sendMessage: (message: unknown) => void;
|
||||||
sendByCtrlEnter?: boolean;
|
sendByCtrlEnter?: boolean;
|
||||||
onSessionActive?: (sessionId?: string | null) => void;
|
onSessionActive?: (sessionId?: string | null) => void;
|
||||||
|
onSessionProcessing?: (sessionId?: string | null) => void;
|
||||||
onInputFocusChange?: (focused: boolean) => void;
|
onInputFocusChange?: (focused: boolean) => void;
|
||||||
onFileOpen?: (filePath: string, diffInfo?: unknown) => void;
|
onFileOpen?: (filePath: string, diffInfo?: unknown) => void;
|
||||||
onShowSettings?: () => void;
|
onShowSettings?: () => void;
|
||||||
@@ -98,6 +99,7 @@ export function useChatComposerState({
|
|||||||
sendMessage,
|
sendMessage,
|
||||||
sendByCtrlEnter,
|
sendByCtrlEnter,
|
||||||
onSessionActive,
|
onSessionActive,
|
||||||
|
onSessionProcessing,
|
||||||
onInputFocusChange,
|
onInputFocusChange,
|
||||||
onFileOpen,
|
onFileOpen,
|
||||||
onShowSettings,
|
onShowSettings,
|
||||||
@@ -569,6 +571,9 @@ export function useChatComposerState({
|
|||||||
pendingViewSessionRef.current = { sessionId: null, startedAt: Date.now() };
|
pendingViewSessionRef.current = { sessionId: null, startedAt: Date.now() };
|
||||||
}
|
}
|
||||||
onSessionActive?.(sessionToActivate);
|
onSessionActive?.(sessionToActivate);
|
||||||
|
if (effectiveSessionId && !isTemporarySessionId(effectiveSessionId)) {
|
||||||
|
onSessionProcessing?.(effectiveSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
const getToolsSettings = () => {
|
const getToolsSettings = () => {
|
||||||
try {
|
try {
|
||||||
@@ -666,6 +671,7 @@ export function useChatComposerState({
|
|||||||
executeCommand,
|
executeCommand,
|
||||||
isLoading,
|
isLoading,
|
||||||
onSessionActive,
|
onSessionActive,
|
||||||
|
onSessionProcessing,
|
||||||
pendingViewSessionRef,
|
pendingViewSessionRef,
|
||||||
permissionMode,
|
permissionMode,
|
||||||
provider,
|
provider,
|
||||||
|
|||||||
@@ -956,12 +956,26 @@ export function useChatRealtimeHandlers({
|
|||||||
|
|
||||||
case 'session-status': {
|
case 'session-status': {
|
||||||
const statusSessionId = latestMessage.sessionId;
|
const statusSessionId = latestMessage.sessionId;
|
||||||
|
if (!statusSessionId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const isCurrentSession =
|
const isCurrentSession =
|
||||||
statusSessionId === currentSessionId || (selectedSession && statusSessionId === selectedSession.id);
|
statusSessionId === currentSessionId || (selectedSession && statusSessionId === selectedSession.id);
|
||||||
if (isCurrentSession && latestMessage.isProcessing) {
|
|
||||||
setIsLoading(true);
|
if (latestMessage.isProcessing) {
|
||||||
setCanAbortSession(true);
|
|
||||||
onSessionProcessing?.(statusSessionId);
|
onSessionProcessing?.(statusSessionId);
|
||||||
|
if (isCurrentSession) {
|
||||||
|
setIsLoading(true);
|
||||||
|
setCanAbortSession(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSessionInactive?.(statusSessionId);
|
||||||
|
onSessionNotProcessing?.(statusSessionId);
|
||||||
|
if (isCurrentSession) {
|
||||||
|
clearLoadingIndicators();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ function ChatInterface({
|
|||||||
sendMessage,
|
sendMessage,
|
||||||
sendByCtrlEnter,
|
sendByCtrlEnter,
|
||||||
onSessionActive,
|
onSessionActive,
|
||||||
|
onSessionProcessing,
|
||||||
onInputFocusChange,
|
onInputFocusChange,
|
||||||
onFileOpen,
|
onFileOpen,
|
||||||
onShowSettings,
|
onShowSettings,
|
||||||
@@ -238,13 +239,6 @@ function ChatInterface({
|
|||||||
};
|
};
|
||||||
}, [canAbortSession, handleAbortSession, isLoading]);
|
}, [canAbortSession, handleAbortSession, isLoading]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const processingSessionId = selectedSession?.id || currentSessionId;
|
|
||||||
if (processingSessionId && isLoading && onSessionProcessing) {
|
|
||||||
onSessionProcessing(processingSessionId);
|
|
||||||
}
|
|
||||||
}, [currentSessionId, isLoading, onSessionProcessing, selectedSession?.id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
resetStreamingState();
|
resetStreamingState();
|
||||||
|
|||||||
Reference in New Issue
Block a user