fix(app): preserve button focus during deferred terminal autofocus

This commit is contained in:
Hannah
2026-10-06 15:05:31 +08:00
parent 5ead2a8811
commit 72d14325e6
4 changed files with 39 additions and 1 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Fixed retained terminals not regaining keyboard focus after switching back to their workspace, including when an overlay or another input delays focus until it is released.
- Fixed retained terminals not regaining keyboard focus after switching back to their workspace, including when an overlay or another input delays focus until it is released, without taking focus from a newly focused button.
## 0.11.0-beta.5 - 2026-10-06
@@ -102,4 +102,23 @@ test.describe("retained terminal tab streams", () => {
output: "OVERLAY_FOCUS_OK",
});
});
test("deferred terminal focus does not steal focus from a button", async ({ page }) => {
const first = await harness.createTerminal({ name: "button-first" });
const other = await harness.createOtherWorkspace();
const second = await other.createTerminal({ name: "button-second" });
await harness.openTerminal(page, { terminalId: first.id });
await other.openTerminal(page, { terminalId: second.id });
await harness.switchToWorkspaceByShortcut(page);
await harness.expectTerminalFocused(page);
await harness.rememberWorkspaceForHistoryReturn(page);
await other.switchToWorkspaceByShortcut(page);
const overlay = await openCommandCenter(page);
await harness.returnToWorkspaceThroughHistory(page);
await expect(overlay.getByTestId("command-center-input")).toBeFocused();
await harness.closeOverlayAndFocusButtonBeforeRetry(page);
await expect(page.getByTestId("sidebar-search")).toBeFocused();
await page.keyboard.press("Enter");
await expect(overlay).toBeVisible();
});
});
@@ -164,6 +164,22 @@ export class TerminalE2EHarness {
await expect(page).toHaveURL(new RegExp(`/workspace/${this.workspaceId}`));
}
async closeOverlayAndFocusButtonBeforeRetry(page: Page): Promise<void> {
await page.clock.install();
await page.clock.pauseAt(new Date());
await page.keyboard.press("Escape");
await expect(page.getByTestId("command-center-panel")).toBeHidden();
await page.getByTestId("sidebar-search").focus();
await page.clock.runFor(100);
await page.clock.resume();
await page.evaluate(
() =>
new Promise<void>((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
),
);
}
async expectTerminalFocused(page: Page): Promise<void> {
await expect(
this.terminalSurface(page).filter({ visible: true }).locator(".xterm-helper-textarea"),
@@ -408,6 +408,7 @@ export function TerminalPane({
let retryFrame: number | null = null;
function stopRetrying(): void {
document.removeEventListener("focusout", scheduleRetry);
document.removeEventListener("focusin", stopRetrying);
unsubscribeOverlayChanges();
if (retryFrame !== null) window.cancelAnimationFrame(retryFrame);
}
@@ -421,6 +422,8 @@ export function TerminalPane({
}
const unsubscribeOverlayChanges = subscribeWebOverlayChanges(scheduleRetry);
document.addEventListener("focusout", scheduleRetry);
// A new focus owner wins over a deferred workspace autofocus request.
document.addEventListener("focusin", stopRetrying);
return stopRetrying;
}, [
isMobile,