From 16be1d0f7b3a434c3e94a16cd8d6930960eb248b Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:20:34 +0300 Subject: [PATCH] fix(chat): preserve rehydrated permission prompts Session navigation restores pending approvals through chat.subscribe. Provider synchronization could clear that restored state afterward. This made banner visibility depend on response timing. Keep cleanup session-scoped and match acknowledgments against the current view. --- src/components/chat/hooks/useChatProviderState.ts | 11 ++--------- src/components/chat/hooks/useChatRealtimeHandlers.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/chat/hooks/useChatProviderState.ts b/src/components/chat/hooks/useChatProviderState.ts index a2910b0d..ea49d841 100644 --- a/src/components/chat/hooks/useChatProviderState.ts +++ b/src/components/chat/hooks/useChatProviderState.ts @@ -114,7 +114,6 @@ export function useChatProviderState({ selectedSession, selectedProject }: UseCh const [providerModelsLoading, setProviderModelsLoading] = useState(true); const [providerModelsRefreshing, setProviderModelsRefreshing] = useState(false); - const lastProviderRef = useRef(provider); const providerModelsRequestIdRef = useRef(0); const setStoredProviderModel = useCallback((targetProvider: LLMProvider, model: string) => { @@ -344,14 +343,8 @@ export function useChatProviderState({ selectedSession, selectedProject }: UseCh localStorage.setItem('selected-provider', selectedSession.__provider); }, [provider, selectedSession]); - useEffect(() => { - if (lastProviderRef.current === provider) { - return; - } - setPendingPermissionRequests([]); - lastProviderRef.current = provider; - }, [provider]); - + // Permission prompts belong to a session, not to the transient provider + // selection that is synchronized after navigation. useEffect(() => { setPendingPermissionRequests((previous) => previous.filter((request) => !request.sessionId || request.sessionId === selectedSession?.id), diff --git a/src/components/chat/hooks/useChatRealtimeHandlers.ts b/src/components/chat/hooks/useChatRealtimeHandlers.ts index 9fceecd4..01af4bb0 100644 --- a/src/components/chat/hooks/useChatRealtimeHandlers.ts +++ b/src/components/chat/hooks/useChatRealtimeHandlers.ts @@ -72,6 +72,13 @@ export function useChatRealtimeHandlers({ onWebSocketReconnect, sessionStore, }: UseChatRealtimeHandlersArgs) { + // Session switches can send `chat.subscribe` before this effect has a chance + // to rebind the websocket listener. Read the visible session id from a ref + // so a fast `chat_subscribed` ack is matched against the current view, not + // the previous render's closed-over selection. + const activeViewSessionIdRef = useRef(selectedSession?.id || currentSessionId || null); + activeViewSessionIdRef.current = selectedSession?.id || currentSessionId || null; + // Keep the latest pending-permission snapshot available to the websocket // listener so back-to-back permission events can dedupe and re-arm the // notification sound before React finishes a rerender. @@ -87,7 +94,7 @@ export function useChatRealtimeHandlers({ return; } - const activeViewSessionId = selectedSession?.id || currentSessionId || null; + const activeViewSessionId = activeViewSessionIdRef.current; const sid = (typeof msg.sessionId === 'string' && msg.sessionId) || activeViewSessionId; // Record replay progress for every sequenced live event.