fix(shell): restore terminal focus when switching to the shell tab

Pass shell activity state from MainContent through StandaloneShell and use it
inside Shell to explicitly focus the xterm instance once the terminal is both
initialized and connected.

Previously, switching to the Shell tab left focus on the tab button because
isActive was being ignored and the terminal never called focus() after the tab
activation lifecycle completed. As a result, users had to click inside the
terminal before keyboard input would be accepted.

This change wires isActive through the shell stack, removes the unused prop
handling in Shell, and adds a focus effect that runs when the shell becomes
active and ready. The effect uses both requestAnimationFrame and a zero-delay
timeout so focus is applied reliably after rendering and connection state
updates settle.

This restores immediate typing when opening the shell tab and also improves the
reconnect path by re-focusing the terminal after the shell connection is ready.
This commit is contained in:
Haileyesus
2026-03-10 21:47:52 +03:00
parent 78601e3bce
commit 3a80be9492
3 changed files with 28 additions and 5 deletions

View File

@@ -146,7 +146,12 @@ function MainContent({
{activeTab === 'shell' && (
<div className="h-full w-full overflow-hidden">
<StandaloneShell project={selectedProject} session={selectedSession} showHeader={false} />
<StandaloneShell
project={selectedProject}
session={selectedSession}
showHeader={false}
isActive={activeTab === 'shell'}
/>
</div>
)}

View File

@@ -40,7 +40,7 @@ export default function Shell({
onProcessComplete = null,
minimal = false,
autoConnect = false,
isActive,
isActive = true,
}: ShellProps) {
const { t } = useTranslation('chat');
const [isRestarting, setIsRestarting] = useState(false);
@@ -48,9 +48,6 @@ export default function Shell({
const promptCheckTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const onOutputRef = useRef<(() => void) | null>(null);
// Keep the public API stable for existing callers that still pass `isActive`.
void isActive;
const {
terminalContainerRef,
terminalRef,
@@ -157,6 +154,24 @@ export default function Shell({
}
}, [isConnected]);
useEffect(() => {
if (!isActive || !isInitialized || !isConnected) {
return;
}
const focusTerminal = () => {
terminalRef.current?.focus();
};
const animationFrameId = window.requestAnimationFrame(focusTerminal);
const timeoutId = window.setTimeout(focusTerminal, 0);
return () => {
window.cancelAnimationFrame(animationFrameId);
window.clearTimeout(timeoutId);
};
}, [isActive, isConnected, isInitialized, terminalRef]);
const sendInput = useCallback(
(data: string) => {
sendSocketMessage(wsRef.current, { type: 'input', data });

View File

@@ -9,6 +9,7 @@ type StandaloneShellProps = {
session?: ProjectSession | null;
command?: string | null;
isPlainShell?: boolean | null;
isActive?: boolean;
autoConnect?: boolean;
onComplete?: ((exitCode: number) => void) | null;
onClose?: (() => void) | null;
@@ -24,6 +25,7 @@ export default function StandaloneShell({
session = null,
command = null,
isPlainShell = null,
isActive = true,
autoConnect = true,
onComplete = null,
onClose = null,
@@ -64,6 +66,7 @@ export default function StandaloneShell({
selectedSession={session}
initialCommand={command}
isPlainShell={shouldUsePlainShell}
isActive={isActive}
onProcessComplete={handleProcessComplete}
minimal={minimal}
autoConnect={minimal ? true : autoConnect}