diff --git a/site/src/modules/terminal/WorkspaceTerminal.tsx b/site/src/modules/terminal/WorkspaceTerminal.tsx
index bf46d5321b1..87a5ea13ce6 100644
--- a/site/src/modules/terminal/WorkspaceTerminal.tsx
+++ b/site/src/modules/terminal/WorkspaceTerminal.tsx
@@ -276,14 +276,25 @@ export const WorkspaceTerminal = ({
}, [isVisible, refit]);
useEffect(() => {
- if (!terminal || !hasBeenVisible) {
+ if (!terminal || !isVisible || !autoFocus) {
return;
}
- terminal.clear();
- if (autoFocus) {
+ const frame = requestAnimationFrame(() => {
terminal.focus();
+ });
+
+ return () => {
+ cancelAnimationFrame(frame);
+ };
+ }, [terminal, isVisible, autoFocus]);
+
+ useEffect(() => {
+ if (!terminal || !hasBeenVisible) {
+ return;
}
+
+ terminal.clear();
terminal.options.disableStdin = true;
if (loading) {
@@ -454,7 +465,6 @@ export const WorkspaceTerminal = ({
}, [
hasBeenVisible,
agentId,
- autoFocus,
baseUrl,
containerName,
containerUser,
diff --git a/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx b/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx
index d5f63899b1a..7e26dfbf6c9 100644
--- a/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx
+++ b/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx
@@ -14,6 +14,7 @@ import {
withAuthProvider,
withDashboardProvider,
withProxyProvider,
+ withWebSocket,
} from "#/testHelpers/storybook";
import {
AgentChatPageLoadingView,
@@ -1208,3 +1209,63 @@ export const ScrollStableAfterEditTruncation: Story = {
).toBeNull();
},
};
+
+/**
+ * Selecting the Terminal tab in the sidebar must move keyboard focus into
+ * the terminal so typing goes there, not the chat input.
+ */
+export const TerminalFocusOnTabSwitch: Story = {
+ parameters: {
+ chromatic: { disableSnapshot: true },
+ webSocket: { "/api/v2/workspaceagents/": [{ event: "message", data: "" }] },
+ },
+ decorators: [withWebSocket],
+ render: () => (
+
+ ),
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ // The sidebar should open on the Git tab by default.
+ const terminalTab = await canvas.findByRole("tab", { name: "Terminal" });
+
+ // 1. Click the Terminal tab.
+ await userEvent.click(terminalTab);
+
+ // Wait for the terminal container to appear.
+ const terminalContainer = await waitFor(() => {
+ const el = canvas.getByTestId("agents-sidebar-terminal");
+ expect(el).toBeVisible();
+ return el;
+ });
+
+ // The xterm focus target is a textarea inside the terminal container.
+ await waitFor(
+ () => {
+ const textarea = terminalContainer.querySelector("textarea");
+ expect(textarea).not.toBeNull();
+ expect(document.activeElement).toBe(textarea);
+ },
+ { timeout: 3000 },
+ );
+
+ // 2. Switch to Git, then back to Terminal.
+ const gitTab = canvas.getByRole("tab", { name: "Git" });
+ await userEvent.click(gitTab);
+ await userEvent.click(terminalTab);
+
+ // Focus should return to the terminal textarea.
+ await waitFor(
+ () => {
+ const textarea = terminalContainer.querySelector("textarea");
+ expect(textarea).not.toBeNull();
+ expect(document.activeElement).toBe(textarea);
+ },
+ { timeout: 3000 },
+ );
+ },
+};
diff --git a/site/src/pages/AgentsPage/components/TerminalPanel.tsx b/site/src/pages/AgentsPage/components/TerminalPanel.tsx
index 9d0cb8fe51a..f07423ebb50 100644
--- a/site/src/pages/AgentsPage/components/TerminalPanel.tsx
+++ b/site/src/pages/AgentsPage/components/TerminalPanel.tsx
@@ -94,7 +94,6 @@ export const TerminalPanel: FC = ({
ref={terminalRef}
agentId={workspaceAgent.id}
operatingSystem={workspaceAgent.operating_system}
- autoFocus={false}
isVisible={isVisible}
onStatusChange={setConnectionStatus}
onError={handleTerminalError}