diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx index f5b670cecf0..1e6dd9e923c 100644 --- a/apps/web/src/components/BranchToolbar.tsx +++ b/apps/web/src/components/BranchToolbar.tsx @@ -25,9 +25,15 @@ interface BranchToolbarProps { envMode: "local" | "worktree"; onEnvModeChange: (mode: "local" | "worktree") => void; envLocked: boolean; + onComposerFocusRequest?: () => void; } -export default function BranchToolbar({ envMode, onEnvModeChange, envLocked }: BranchToolbarProps) { +export default function BranchToolbar({ + envMode, + onEnvModeChange, + envLocked, + onComposerFocusRequest, +}: BranchToolbarProps) { const { state, dispatch } = useStore(); const api = useNativeApi(); const queryClient = useQueryClient(); @@ -123,6 +129,7 @@ export default function BranchToolbar({ envMode, onEnvModeChange, envLocked }: B setThreadError(null); setThreadBranch(branch.name, null); setIsBranchMenuOpen(false); + onComposerFocusRequest?.(); return; } @@ -135,6 +142,7 @@ export default function BranchToolbar({ envMode, onEnvModeChange, envLocked }: B // Secondary worktree → point the thread at that worktree path. setThreadBranch(branch.name, isMainWorktree ? null : branch.worktreePath); setIsBranchMenuOpen(false); + onComposerFocusRequest?.(); return; } @@ -143,6 +151,7 @@ export default function BranchToolbar({ envMode, onEnvModeChange, envLocked }: B setThreadError(null); setThreadBranch(branch.name, activeWorktreePath); setIsBranchMenuOpen(false); + onComposerFocusRequest?.(); }, }); }; @@ -156,6 +165,7 @@ export default function BranchToolbar({ envMode, onEnvModeChange, envLocked }: B setThreadBranch(name, activeWorktreePath); setBranchQuery(""); setIsBranchMenuOpen(false); + onComposerFocusRequest?.(); }, }); }; diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 806d26fdc44..24aee68cb9c 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -273,6 +273,11 @@ export default function ChatView() { const cursor = textarea.value.length; textarea.setSelectionRange(cursor, cursor); }, []); + const scheduleComposerFocus = useCallback(() => { + window.requestAnimationFrame(() => { + focusComposer(); + }); + }, [focusComposer]); const toggleTerminalVisibility = useCallback(() => { if (!activeThreadId) return; dispatch({ @@ -344,6 +349,7 @@ export default function ChatView() { const handleRuntimeModeChange = async (mode: "approval-required" | "full-access") => { if (mode === state.runtimeMode) return; dispatch({ type: "SET_RUNTIME_MODE", mode }); + scheduleComposerFocus(); if (!api) return; const sessionIds = state.threads @@ -806,14 +812,32 @@ export default function ChatView() { } }; - const onModelSelect = (model: ModelSlug) => { - if (!activeThread) return; - dispatch({ - type: "SET_THREAD_MODEL", - threadId: activeThread.id, - model: resolveModelSlug(model), - }); - }; + const onModelSelect = useCallback( + (model: ModelSlug) => { + if (!activeThread) return; + dispatch({ + type: "SET_THREAD_MODEL", + threadId: activeThread.id, + model: resolveModelSlug(model), + }); + scheduleComposerFocus(); + }, + [activeThread, dispatch, scheduleComposerFocus], + ); + const onEffortSelect = useCallback( + (effort: ReasoningEffort) => { + setSelectedEffort(effort); + scheduleComposerFocus(); + }, + [scheduleComposerFocus], + ); + const onEnvModeChange = useCallback( + (mode: "local" | "worktree") => { + setEnvMode(mode); + scheduleComposerFocus(); + }, + [scheduleComposerFocus], + ); const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { @@ -1213,7 +1237,7 @@ export default function ChatView() { {/* Reasoning effort */} - + {/* Divider */} @@ -1316,7 +1340,12 @@ export default function ChatView() { {isGitRepo && ( - + )} {activeThread.terminalOpen && api && activeProject && (