diff --git a/mobile/src/session/use-mobile-native-chat-controller.ts b/mobile/src/session/use-mobile-native-chat-controller.ts index d7e6af535c7..d6f22015763 100644 --- a/mobile/src/session/use-mobile-native-chat-controller.ts +++ b/mobile/src/session/use-mobile-native-chat-controller.ts @@ -1,5 +1,6 @@ import { useCallback, + useLayoutEffect, useRef, type Dispatch, type MutableRefObject, @@ -125,9 +126,12 @@ export function useMobileNativeChatController(args: { : null const showNativeChat = activeChatResolution != null const showNativeChatRef = useRef(showNativeChat) - showNativeChatRef.current = showNativeChat - const activeChatAgentRef = useRef(activeChatResolution?.agent ?? null) - activeChatAgentRef.current = activeChatResolution?.agent ?? null + const activeChatAgent = activeChatResolution?.agent ?? null + const activeChatAgentRef = useRef(activeChatAgent) + useLayoutEffect(() => { + showNativeChatRef.current = showNativeChat + activeChatAgentRef.current = activeChatAgent + }, [activeChatAgent, showNativeChat]) const activeChatSessionId = activeChatResolution?.sessionId ?? null const streamIdentity = `${hostId}\0${worktreeId}\0${activeSessionTabId ?? ''}\0${activeChatSessionId ?? ''}\0${activeHandleRef.current ?? ''}` @@ -286,7 +290,9 @@ export function useMobileNativeChatController(args: { dispatchCommand: handleNativeChatDispatchCommand, onAgentPicker: handleAgentPicker }) - recordSessionOptionCommandRef.current = sessionOptions.recordCommand + useLayoutEffect(() => { + recordSessionOptionCommandRef.current = sessionOptions.recordCommand + }, [sessionOptions.recordCommand]) // Card actions retire the route's held failure banner too, not just sends. const answerAsk = useNativeChatAcceptedAction(handleNativeChatAnswerAsk, onSendResolved) const cancelAsk = useNativeChatAcceptedAction(handleNativeChatCancelAsk, onSendResolved) diff --git a/mobile/src/session/use-mobile-native-chat-session-options.test.ts b/mobile/src/session/use-mobile-native-chat-session-options.test.ts index d77f91e9a70..a2b3d80ef27 100644 --- a/mobile/src/session/use-mobile-native-chat-session-options.test.ts +++ b/mobile/src/session/use-mobile-native-chat-session-options.test.ts @@ -136,7 +136,7 @@ describe('useMobileNativeChatSessionOptions', () => { }) it('keeps the latest queued operation pending until it settles', async () => { - const resolvers: Array<(outcome: MobileNativeChatSendOutcome) => void> = [] + const resolvers: ((outcome: MobileNativeChatSendOutcome) => void)[] = [] dispatchCommand.mockImplementation( () => new Promise((resolve) => { diff --git a/mobile/src/session/use-mobile-native-chat-session-options.ts b/mobile/src/session/use-mobile-native-chat-session-options.ts index b173d2c2704..c91e1c9509a 100644 --- a/mobile/src/session/use-mobile-native-chat-session-options.ts +++ b/mobile/src/session/use-mobile-native-chat-session-options.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import { getAgentSessionOptionCatalog } from '../../../src/shared/agent-session-option-catalog' import type { SessionOptionDescriptor, @@ -85,16 +85,15 @@ export function useMobileNativeChatSessionOptions(args: { const pendingId = identity ? (pendingByIdentity[identity]?.id ?? null) : null const bump = useCallback(() => setVersion((current) => current + 1), []) const activeIdentityRef = useRef(identity) - activeIdentityRef.current = identity const applyQueuesRef = useRef(new Map>()) const operationTokenRef = useRef(0) - useEffect( - () => () => { + useLayoutEffect(() => { + activeIdentityRef.current = identity + return () => { activeIdentityRef.current = null - }, - [] - ) + } + }, [identity]) // Seed the current model from live agent status; hook reports are authority // over locally dispatched guesses (desktop 'reported' source parity).