diff --git a/packages/cli/src/config/keyBindings.ts b/packages/cli/src/config/keyBindings.ts
index 9b6a903a4b..efd2c61bda 100644
--- a/packages/cli/src/config/keyBindings.ts
+++ b/packages/cli/src/config/keyBindings.ts
@@ -288,7 +288,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
{ key: 's', ctrl: true },
],
[Command.FOCUS_SHELL_INPUT]: [{ key: 'tab', shift: false }],
- [Command.UNFOCUS_SHELL_INPUT]: [{ key: 'tab' }],
+ [Command.UNFOCUS_SHELL_INPUT]: [{ key: 'tab', shift: true }],
[Command.CLEAR_SCREEN]: [{ key: 'l', ctrl: true }],
[Command.RESTART_APP]: [{ key: 'r' }],
[Command.SUSPEND_APP]: [{ key: 'z', ctrl: true }],
diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx
index 3ee4e89ea5..ff42557a95 100644
--- a/packages/cli/src/ui/AppContainer.test.tsx
+++ b/packages/cli/src/ui/AppContainer.test.tsx
@@ -1940,6 +1940,28 @@ describe('AppContainer State Management', () => {
unmount();
});
});
+
+ describe('Shell Focus', () => {
+ it('should unfocus shell when Shift+Tab is pressed', async () => {
+ mockedUseGeminiStream.mockReturnValue({
+ ...DEFAULT_GEMINI_STREAM_MOCK,
+ activePtyId: 1,
+ });
+
+ await setupKeypressTest();
+
+ act(() => {
+ capturedUIActions.setEmbeddedShellFocused(true);
+ });
+ rerender();
+ expect(capturedUIState.embeddedShellFocused).toBe(true);
+
+ pressKey({ name: 'tab', shift: true });
+
+ expect(capturedUIState.embeddedShellFocused).toBe(false);
+ unmount();
+ });
+ });
});
describe('Copy Mode (CTRL+S)', () => {
diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index 7c10569902..06f2dd903e 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -1500,10 +1500,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
setConstrainHeight(false);
return true;
} else if (
- keyMatchers[Command.FOCUS_SHELL_INPUT](key) &&
+ (keyMatchers[Command.FOCUS_SHELL_INPUT](key) ||
+ keyMatchers[Command.UNFOCUS_SHELL_INPUT](key)) &&
(activePtyId || (isBackgroundShellVisible && backgroundShells.size > 0))
) {
- if (key.name === 'tab' && key.shift) {
+ if (keyMatchers[Command.UNFOCUS_SHELL_INPUT](key)) {
// Always change focus
setEmbeddedShellFocused(false);
return true;
diff --git a/packages/cli/src/ui/components/ShellInputPrompt.test.tsx b/packages/cli/src/ui/components/ShellInputPrompt.test.tsx
index 5a204b0580..57e9c017b7 100644
--- a/packages/cli/src/ui/components/ShellInputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/ShellInputPrompt.test.tsx
@@ -111,4 +111,22 @@ describe('ShellInputPrompt', () => {
expect(mockWriteToPty).not.toHaveBeenCalled();
});
+
+ it('bubbles up Shift+Tab for unfocusing', () => {
+ render();
+
+ const handler = mockUseKeypress.mock.calls[0][0];
+
+ const result = handler({
+ name: 'tab',
+ shift: true,
+ alt: false,
+ ctrl: false,
+ cmd: false,
+ sequence: '\x1b[Z',
+ });
+
+ expect(result).toBe(false);
+ expect(mockWriteToPty).not.toHaveBeenCalled();
+ });
});
diff --git a/packages/cli/src/ui/components/ShellInputPrompt.tsx b/packages/cli/src/ui/components/ShellInputPrompt.tsx
index 4f956ae262..976831f1f4 100644
--- a/packages/cli/src/ui/components/ShellInputPrompt.tsx
+++ b/packages/cli/src/ui/components/ShellInputPrompt.tsx
@@ -40,6 +40,11 @@ export const ShellInputPrompt: React.FC = ({
return false;
}
+ // Allow unfocus to bubble up
+ if (keyMatchers[Command.UNFOCUS_SHELL_INPUT](key)) {
+ return false;
+ }
+
if (key.ctrl && key.shift && key.name === 'up') {
ShellExecutionService.scrollPty(activeShellPtyId, -1);
return true;