From f2038a2022c3ff907d3dce092060ded3ea7c215c Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Thu, 12 Feb 2026 10:17:05 +0000 Subject: [PATCH] feat: auto-focus chat input on session switch and connection --- src/App.tsx | 2 +- src/components/Chat.tsx | 5 +++-- src/components/ChatInput.tsx | 12 +++++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 41aa6e4..2fc9d18 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -86,7 +86,7 @@ export default function App() {
setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} soundEnabled={soundEnabled} onToggleSound={toggleSound} />
Loading…
}> - +
setShortcutsOpen(false)} /> diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index dbd6aa5..df9fbe7 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -12,6 +12,7 @@ interface Props { isGenerating: boolean; isLoadingHistory: boolean; status: ConnectionStatus; + sessionKey?: string; onSend: (text: string, attachments?: Array<{ mimeType: string; fileName: string; content: string }>) => void; onAbort: () => void; } @@ -64,7 +65,7 @@ function getDateKey(ts: number): string { /** Threshold in pixels — if the user is within this distance of the bottom, auto-scroll */ const SCROLL_THRESHOLD = 150; -export function Chat({ messages, isGenerating, isLoadingHistory, status, onSend, onAbort }: Props) { +export function Chat({ messages, isGenerating, isLoadingHistory, status, sessionKey, onSend, onAbort }: Props) { const t = useT(); const bottomRef = useRef(null); const scrollContainerRef = useRef(null); @@ -176,7 +177,7 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, onSend, )} - + ); } diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 7be9575..4891129 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -15,6 +15,7 @@ interface Props { onAbort: () => void; isGenerating: boolean; disabled: boolean; + sessionKey?: string; } const MAX_BASE64_CHARS = 300 * 1024; // ~225KB real, well under 512KB WS limit (JSON overhead + base64 bloat) @@ -79,7 +80,7 @@ function formatSize(bytes: number): string { return `${(bytes / (1024 * 1024)).toFixed(1)}MB`; } -export function ChatInput({ onSend, onAbort, isGenerating, disabled }: Props) { +export function ChatInput({ onSend, onAbort, isGenerating, disabled, sessionKey }: Props) { const t = useT(); const [text, setText] = useState(''); const [files, setFiles] = useState([]); @@ -94,6 +95,15 @@ export function ChatInput({ onSend, onAbort, isGenerating, disabled }: Props) { } }, [text]); + // Auto-focus textarea when session changes or connection becomes active + useEffect(() => { + if (!disabled && textareaRef.current) { + // Small delay to let the DOM settle after session switch + const timer = setTimeout(() => textareaRef.current?.focus(), 50); + return () => clearTimeout(timer); + } + }, [sessionKey, disabled]); + const addFiles = useCallback(async (fileList: FileList | File[]) => { const newFiles: FileAttachment[] = []; for (const file of Array.from(fileList)) {