Skip to content

Commit 933dbfd

Browse files
authored
fix(app): restore unfocused composer typing (anomalyco#35249)
1 parent b44bc0a commit 933dbfd

4 files changed

Lines changed: 90 additions & 42 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { base64Encode } from "@opencode-ai/core/util/encode"
2+
import { expect, test } from "@playwright/test"
3+
import { mockOpenCodeServer } from "../utils/mock-server"
4+
import { expectSessionTitle } from "../utils/waits"
5+
6+
const directory = "C:/OpenCode/TerminalComposerFocus"
7+
const projectID = "proj_terminal_composer_focus"
8+
const sessionID = "ses_terminal_composer_focus"
9+
const ptyID = "pty_terminal_composer_focus"
10+
11+
test.use({ viewport: { width: 1440, height: 900 } })
12+
13+
test("routes typing to the composer unless the open terminal is focused", async ({ page }) => {
14+
await mockOpenCodeServer(page, {
15+
directory,
16+
project: {
17+
id: projectID,
18+
worktree: directory,
19+
vcs: "git",
20+
name: "terminal-composer-focus",
21+
time: { created: 1700000000000, updated: 1700000000000 },
22+
sandboxes: [],
23+
},
24+
provider: {
25+
all: [
26+
{
27+
id: "opencode",
28+
name: "OpenCode",
29+
models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } },
30+
},
31+
],
32+
connected: ["opencode"],
33+
default: { providerID: "opencode", modelID: "test" },
34+
},
35+
sessions: [
36+
{
37+
id: sessionID,
38+
slug: "terminal-composer-focus",
39+
projectID,
40+
directory,
41+
title: "Terminal composer focus",
42+
version: "dev",
43+
time: { created: 1700000000000, updated: 1700000000000 },
44+
},
45+
],
46+
pageMessages: () => ({ items: [] }),
47+
})
48+
await page.route("**/pty", (route) =>
49+
route.fulfill({
50+
status: 200,
51+
contentType: "application/json",
52+
body: JSON.stringify({ id: ptyID, title: "Terminal 1" }),
53+
}),
54+
)
55+
await page.route(`**/pty/${ptyID}`, (route) =>
56+
route.fulfill({ status: 200, contentType: "application/json", body: "{}" }),
57+
)
58+
await page.route(`**/pty/${ptyID}/connect-token*`, (route) =>
59+
route.fulfill({
60+
status: 200,
61+
contentType: "application/json",
62+
headers: { "access-control-allow-origin": "*" },
63+
body: JSON.stringify({ ticket: "e2e-ticket" }),
64+
}),
65+
)
66+
await page.routeWebSocket(new RegExp(`/pty/${ptyID}/connect`), () => undefined)
67+
await page.addInitScript(() => {
68+
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
69+
})
70+
71+
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
72+
await expectSessionTitle(page, "Terminal composer focus")
73+
74+
const composer = page.locator('[data-component="prompt-input"]')
75+
const terminal = page.locator('[data-component="terminal"]')
76+
await page.keyboard.press("Control+Backquote")
77+
await expect(terminal).toBeVisible()
78+
await expect.poll(() => terminal.evaluate((element) => element.contains(document.activeElement))).toBe(true)
79+
80+
await page.keyboard.type("x")
81+
await expect(composer).toHaveText("")
82+
83+
await page.waitForTimeout(300)
84+
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur())
85+
await page.keyboard.type("a")
86+
87+
await expect(composer).toBeFocused()
88+
await expect(composer).toHaveText("a")
89+
})

packages/app/src/pages/session.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,7 @@ import {
6464
createSessionComposerRegionController,
6565
SessionComposerRegion,
6666
} from "@/pages/session/composer"
67-
import {
68-
createOpenReviewFile,
69-
createSessionTabs,
70-
createSizing,
71-
focusTerminalById,
72-
shouldFocusTerminalOnKeyDown,
73-
shouldShowFileTree,
74-
} from "@/pages/session/helpers"
67+
import { createOpenReviewFile, createSessionTabs, createSizing, shouldShowFileTree } from "@/pages/session/helpers"
7568
import { MessageTimeline } from "@/pages/session/timeline/message-timeline"
7669
import { createTimelineModel } from "@/pages/session/timeline/model"
7770
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
@@ -942,12 +935,6 @@ export default function Page() {
942935
return
943936
}
944937

945-
// Prefer the open terminal over the composer when it can take focus
946-
if (view().terminal.opened()) {
947-
const id = terminal.active()
948-
if (id && shouldFocusTerminalOnKeyDown(event) && focusTerminalById(id)) return
949-
}
950-
951938
const key = scrollKey(event)
952939
if (key) {
953940
if (!scroller || !isScrollKeyTarget(target ?? null, key)) return

packages/app/src/pages/session/helpers.test.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
createSessionTabs,
88
focusTerminalById,
99
getTabReorderIndex,
10-
shouldFocusTerminalOnKeyDown,
1110
shouldShowFileTree,
1211
} from "./helpers"
1312

@@ -95,26 +94,6 @@ describe("focusTerminalById", () => {
9594
})
9695
})
9796

98-
describe("shouldFocusTerminalOnKeyDown", () => {
99-
test("skips pure modifier keys", () => {
100-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Meta", metaKey: true }))).toBe(false)
101-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Control", ctrlKey: true }))).toBe(false)
102-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Alt", altKey: true }))).toBe(false)
103-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Shift", shiftKey: true }))).toBe(false)
104-
})
105-
106-
test("skips shortcut key combos", () => {
107-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "c", metaKey: true }))).toBe(false)
108-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "c", ctrlKey: true }))).toBe(false)
109-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "ArrowLeft", altKey: true }))).toBe(false)
110-
})
111-
112-
test("keeps plain typing focused on terminal", () => {
113-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "a" }))).toBe(true)
114-
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "A", shiftKey: true }))).toBe(true)
115-
})
116-
})
117-
11897
describe("getTabReorderIndex", () => {
11998
test("returns target index for valid drag reorder", () => {
12099
expect(getTabReorderIndex(["a", "b", "c"], "a", "c")).toBe(2)

packages/app/src/pages/session/helpers.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,6 @@ export const focusTerminalById = (id: string) => {
9898
return true
9999
}
100100

101-
const skip = new Set(["Alt", "Control", "Meta", "Shift"])
102-
103-
export const shouldFocusTerminalOnKeyDown = (event: Pick<KeyboardEvent, "key" | "ctrlKey" | "metaKey" | "altKey">) => {
104-
if (skip.has(event.key)) return false
105-
return !(event.ctrlKey || event.metaKey || event.altKey)
106-
}
107-
108101
export const createOpenReviewFile = (input: {
109102
showAllFiles: () => void
110103
tabForPath: (path: string) => string

0 commit comments

Comments
 (0)