Skip to content

Commit 467118c

Browse files
committed
Keep a new tab's late focus from taking the caret away
Opening a tab (⌘⇧B) focuses its address bar twice: once when the tab becomes active, and again in an animation frame that newTab queues for when the panel was not yet visible. Chromium can hold frames back for an inactive or busy window. The late focus then landed after the person, or a test, had already moved on, and took the caret out of the composer mid-typing. That failed CI on #293: store-desktop.spec.ts's browser-tools recovery case filled the composer just as the late frame ran. The text went into the address bar and the composer stayed empty (job 117782462586). The same case failed on #294's first run. The late focus now applies only if focus has not moved since the tab opened. The new workspace.spec.ts case holds animation frames, moves to the composer, releases the frames and types. It failed first: the composer lost focus when the frame ran. Coven-Harness: Claude Code / claude-opus-5-5
1 parent 747f793 commit 467118c

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

‎apps/desktop/e2e/workspace.spec.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,27 @@ test("a delayed browser snapshot cannot replace an address being edited", async
182182
await expect(input).toHaveValue("https://example.com/fast");
183183
});
184184

185+
test("a new tab's late focus never takes the caret from where you started typing", async () => {
186+
// Hold animation frames, as Chromium does for an inactive or busy window.
187+
await page.evaluate(() => {
188+
const w = window as unknown as { __frames: FrameRequestCallback[]; __raf: typeof requestAnimationFrame };
189+
w.__frames = []; w.__raf = window.requestAnimationFrame;
190+
window.requestAnimationFrame = (callback) => { w.__frames.push(callback); return w.__frames.length; };
191+
});
192+
await page.keyboard.press("Meta+Shift+b");
193+
await expect(tid(page, "workspace-address")).toBeFocused();
194+
await tid(page, "composer-input").focus();
195+
await page.evaluate(() => {
196+
const w = window as unknown as { __frames: FrameRequestCallback[]; __raf: typeof requestAnimationFrame };
197+
window.requestAnimationFrame = w.__raf;
198+
for (const callback of w.__frames.splice(0)) callback(performance.now());
199+
});
200+
await expect(tid(page, "composer-input")).toBeFocused();
201+
await page.keyboard.type("Keep this draft");
202+
await expect(tid(page, "composer-input")).toHaveValue("Keep this draft");
203+
await expect(tid(page, "workspace-address")).toHaveValue("");
204+
});
205+
185206
test("superseded navigation, overlays, guest shortcuts and window destruction preserve browser lifecycle", async () => {
186207
const { createServer } = await import("node:http");
187208
const server = createServer((req, res) => { if (req.url === "/slow") return; res.writeHead(200, { "Content-Type": "text/html" }); res.end("<title>Fast destination</title><h1>Ready</h1>"); });

‎apps/desktop/src/renderer/components/Workspace.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ export function Workspace({ thread, changes, onRefresh, onRevert, visible, onSho
5454
if (currentRef.current?.kind === "terminal") onHideTerminal();
5555
const tab = makeTab();
5656
setTabs((prev) => [...prev, tab]); setActive(tab.id); setFull(fullView); setAddMenu(false); setError(""); onShow();
57-
requestAnimationFrame(() => addressRef.current?.focus());
57+
// A frame can arrive late (an inactive or busy window). By then the address may already have focus,
58+
// or the person may have moved on to type elsewhere: once focus has moved at all, leave it.
59+
let moved = false;
60+
const mark = () => { moved = true; };
61+
document.addEventListener("focusin", mark, true);
62+
requestAnimationFrame(() => {
63+
document.removeEventListener("focusin", mark, true);
64+
if (!moved) addressRef.current?.focus();
65+
});
5866
}, [onShow, onHideTerminal]);
5967
const openTool = (kind: "review" | "files" | "terminal" | "canvas") => {
6068
const existing = tabs.find((tab) => tab.kind === kind);

0 commit comments

Comments
 (0)