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:
Haileyesus
2026-02-20 11:18:40 +03:00
parent 200332f4f5
commit d486ff91a2
3 changed files with 24 additions and 10 deletions

View File

@@ -47,6 +47,7 @@ interface UseChatComposerStateArgs {
sendMessage: (message: unknown) => void;
sendByCtrlEnter?: boolean;
onSessionActive?: (sessionId?: string | null) => void;
onSessionProcessing?: (sessionId?: string | null) => void;
onInputFocusChange?: (focused: boolean) => void;
onFileOpen?: (filePath: string, diffInfo?: unknown) => void;
onShowSettings?: () => void;
@@ -98,6 +99,7 @@ export function useChatComposerState({
sendMessage,
sendByCtrlEnter,
onSessionActive,
onSessionProcessing,
onInputFocusChange,
onFileOpen,
onShowSettings,
@@ -569,6 +571,9 @@ export function useChatComposerState({
pendingViewSessionRef.current = { sessionId: null, startedAt: Date.now() };
}
onSessionActive?.(sessionToActivate);
if (effectiveSessionId && !isTemporarySessionId(effectiveSessionId)) {
onSessionProcessing?.(effectiveSessionId);
}
const getToolsSettings = () => {
try {
@@ -666,6 +671,7 @@ export function useChatComposerState({
executeCommand,
isLoading,
onSessionActive,
onSessionProcessing,
pendingViewSessionRef,
permissionMode,
provider,

View File

@@ -956,12 +956,26 @@ export function useChatRealtimeHandlers({
case 'session-status': {
const statusSessionId = latestMessage.sessionId;
if (!statusSessionId) {
break;
}
const isCurrentSession =
statusSessionId === currentSessionId || (selectedSession && statusSessionId === selectedSession.id);
if (isCurrentSession && latestMessage.isProcessing) {
setIsLoading(true);
setCanAbortSession(true);
if (latestMessage.isProcessing) {
onSessionProcessing?.(statusSessionId);
if (isCurrentSession) {
setIsLoading(true);
setCanAbortSession(true);
}
break;
}
onSessionInactive?.(statusSessionId);
onSessionNotProcessing?.(statusSessionId);
if (isCurrentSession) {
clearLoadingIndicators();
}
break;
}

View File

@@ -180,6 +180,7 @@ function ChatInterface({
sendMessage,
sendByCtrlEnter,
onSessionActive,
onSessionProcessing,
onInputFocusChange,
onFileOpen,
onShowSettings,
@@ -238,13 +239,6 @@ function ChatInterface({
};
}, [canAbortSession, handleAbortSession, isLoading]);
useEffect(() => {
const processingSessionId = selectedSession?.id || currentSessionId;
if (processingSessionId && isLoading && onSessionProcessing) {
onSessionProcessing(processingSessionId);
}
}, [currentSessionId, isLoading, onSessionProcessing, selectedSession?.id]);
useEffect(() => {
return () => {
resetStreamingState();