import { memo, useCallback, useMemo } from 'react'; import { MessageList } from './MessageList'; import { ChatInput } from './ChatInput'; import { StatusBar } from './StatusBar'; import { PermissionDialog } from './PermissionDialog'; import { useSessionManager } from '../contexts/SessionContext'; import { Bot } from 'lucide-react'; // Wrapper to compute placeholder inside and prevent parent re-renders from affecting input const MemoizedChatInput = memo(function MemoizedChatInput({ onSend, onStop, disabled, isProcessing, sessionId, connected, active }) { const placeholder = !connected ? 'Connecting...' : !active ? 'Start session to begin' : 'Type your message...'; return ( ); }, (prevProps, nextProps) => { // Custom comparison - only re-render if these specific props change return ( prevProps.disabled === nextProps.disabled && prevProps.isProcessing === nextProps.isProcessing && prevProps.sessionId === nextProps.sessionId && prevProps.connected === nextProps.connected && prevProps.active === nextProps.active && prevProps.onSend === nextProps.onSend && prevProps.onStop === nextProps.onStop ); }); // Welcome screen when no messages const WelcomeScreen = memo(function WelcomeScreen({ session, onStart }) { const hostName = session.host.charAt(0).toUpperCase() + session.host.slice(1); const projectName = session.project.split('/').pop() || session.project; return (

{hostName}: {projectName}

{session.active ? 'Session is active. Start chatting with Claude.' : 'Click "Start Session" in the sidebar or press the button below to begin.'}

{!session.active && ( )}
); }); // Error banner const ErrorBanner = memo(function ErrorBanner({ error, onClear }) { if (!error) return null; return (
{error}
); }); // Use a separate hook that memoizes everything to prevent unnecessary re-renders function useMemoizedSession(sessionId) { const manager = useSessionManager(); const session = manager.sessions[sessionId]; const messages = manager.sessionMessages[sessionId] || []; // Memoize the combined session object const sessionWithMessages = useMemo(() => { return session ? { ...session, messages } : null; }, [session, messages]); // Memoize all action functions const actions = useMemo(() => ({ start: () => manager.startClaudeSession(sessionId), stop: () => manager.stopClaudeSession(sessionId), send: (msg, attachments) => manager.sendMessage(sessionId, msg, attachments), stopGeneration: () => manager.stopGeneration(sessionId), clearMessages: () => manager.clearMessages(sessionId), changePermissionMode: (mode) => manager.changePermissionMode(sessionId, mode), respondToPermission: (reqId, allow) => manager.respondToPermission(sessionId, reqId, allow), }), [sessionId, manager]); return { session: sessionWithMessages, ...actions }; } export const ChatPanel = memo(function ChatPanel({ sessionId }) { const { session, start, stop, send, stopGeneration, clearMessages, changePermissionMode, respondToPermission, } = useMemoizedSession(sessionId); const handleClearError = useCallback(() => { // We'd need to add this to the session context // For now, errors auto-clear on next action }, []); const handleSendMessage = useCallback((message, attachments = []) => { if (message.trim() || attachments.length > 0) { send(message, attachments); } }, [send]); if (!session) { return (
Session not found
); } const hasMessages = session.messages && session.messages.length > 0; return (
{/* Error Banner */} {/* Messages or Welcome */} {hasMessages || session.active ? ( ) : ( )} {/* Status Bar */} changePermissionMode(mode)} /> {/* Input - memoized props to prevent re-renders during streaming */} {/* Permission Dialog */} respondToPermission(requestId, true)} onDeny={(requestId) => respondToPermission(requestId, false)} />
); });