import { useMemo } from 'react' import { StyleSheet, View } from 'react-native' import { MobileNativeChatView, type MobileNativeChatInputLockReason } from './MobileNativeChatView' import { foldMobileNativeChatMessages } from './mobile-native-chat-render-data' import type { MobileNativeChatImageAttachments } from './use-mobile-native-chat-image-attachments' import type { MobileNativeChatController } from './use-mobile-native-chat-controller' import { useMobileNativeChatStreamingBubble } from './use-mobile-native-chat-streaming-bubble' type Props = { controller: MobileNativeChatController /** Opens a tapped file reference (worktree-relative or absolute, optional * :line(:col) suffix) through the shared tap-to-open flow. */ onOpenFile: (pathText: string) => void /** Native-chat image attachments: picking adds a composer chip, and sending * rides the pending images along with the message text (desktop parity). */ images: MobileNativeChatImageAttachments onMicPress: () => void micActive: boolean dictationMode: 'toggle' | 'hold' onMicPressIn: () => void onMicPressOut: () => void inputLockReason: MobileNativeChatInputLockReason | null /** Latest send failure, rendered inline above the composer. */ sendErrorMessage: string | null /** Drops that failure once a later send succeeds. */ onClearSendError: () => void keyboardInset: number } /** Keeps the terminal mounted underneath chat so its PTY subscription survives * view toggles while the native surface owns the visible composer. Also owns * the streaming gate: this component stays mounted across those toggles, while * the chat list below it does not. */ export function MobileNativeChatOverlay({ controller, onOpenFile, images, onMicPress, micActive, dictationMode, onMicPressIn, onMicPressOut, inputLockReason, sendErrorMessage, onClearSendError, keyboardInset }: Props): React.JSX.Element | null { const session = controller.nativeChatSession const folded = useMemo(() => foldMobileNativeChatMessages(session.messages), [session.messages]) const streaming = useMobileNativeChatStreamingBubble( folded, controller.nativeChatStreamingText, controller.nativeChatStreamScopeKey, controller.nativeChatStreamLive ) if (!controller.showNativeChat) { return null } return ( void images.attachImage('library')} attachments={images.attachments} onRemoveAttachment={images.removeAttachment} isAttaching={images.isAttaching} onMicPress={onMicPress} micActive={micActive} dictationMode={dictationMode} onMicPressIn={onMicPressIn} onMicPressOut={onMicPressOut} inputLockReason={inputLockReason} sendErrorMessage={sendErrorMessage} onClearSendError={onClearSendError} filePaths={controller.nativeChatFilePaths} onNeedFiles={controller.loadNativeChatFiles} sessionOptions={controller.nativeChatSessionOptions} keyboardInset={keyboardInset} /> ) } const styles = StyleSheet.create({ overlay: StyleSheet.absoluteFillObject })