mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-27 19:52:49 +00:00
fix(app): refocus retained terminals after workspace switches
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed retained terminals not regaining keyboard focus after switching back to their workspace with a number shortcut.
|
||||
|
||||
## 0.11.0-beta.5 - 2026-10-06
|
||||
|
||||
### Added
|
||||
|
||||
@@ -59,4 +59,45 @@ test.describe("retained terminal tab streams", () => {
|
||||
|
||||
expect(await terminalAttachOverlayWasSeen(page)).toBe(false);
|
||||
});
|
||||
|
||||
test("workspace number shortcuts refocus a retained terminal", async ({ page }) => {
|
||||
const first = await harness.createTerminal({ name: "focus-first" });
|
||||
const other = await TerminalE2EHarness.create({ tempPrefix: "terminal-workspace-focus-" });
|
||||
try {
|
||||
const second = await other.createTerminal({ name: "focus-second" });
|
||||
await harness.openTerminal(page, { terminalId: first.id });
|
||||
await other.openTerminal(page, { terminalId: second.id });
|
||||
const rows = page
|
||||
.locator('[data-testid^="sidebar-workspace-row-"]')
|
||||
.filter({ visible: true });
|
||||
const rowIds = await rows.evaluateAll((elements) => {
|
||||
const ids: Array<string | null> = [];
|
||||
for (const element of elements) ids.push(element.getAttribute("data-testid"));
|
||||
return ids;
|
||||
});
|
||||
const firstIndex = rowIds.findIndex((id) => id?.endsWith(`:${harness.workspaceId}`)) + 1;
|
||||
const secondIndex = rowIds.findIndex((id) => id?.endsWith(`:${other.workspaceId}`)) + 1;
|
||||
expect(firstIndex).toBeGreaterThan(0);
|
||||
expect(secondIndex).toBeGreaterThan(0);
|
||||
const terminalInput = page
|
||||
.getByTestId("terminal-surface")
|
||||
.filter({ visible: true })
|
||||
.locator("textarea");
|
||||
await expect(terminalInput).toBeFocused();
|
||||
// Browser Alt+Digit routes the same workspace action as desktop Cmd+Digit.
|
||||
await page.keyboard.press(`Alt+${firstIndex}`);
|
||||
await expect(page).toHaveURL(new RegExp(`/workspace/${harness.workspaceId}`));
|
||||
await expect(terminalInput).toBeFocused();
|
||||
await page.keyboard.press(`Alt+${secondIndex}`);
|
||||
await expect(page).toHaveURL(new RegExp(`/workspace/${other.workspaceId}`));
|
||||
await expect(terminalInput).toBeFocused();
|
||||
await page.keyboard.type("printf 'WORKSPACE_FOCUS_OK\\n'");
|
||||
await page.keyboard.press("Enter");
|
||||
await expect
|
||||
.poll(async () => (await other.client.captureTerminal(second.id)).lines)
|
||||
.toContain("WORKSPACE_FOCUS_OK");
|
||||
} finally {
|
||||
await other.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TerminalFind, type TerminalPaneFindHandle } from "@/terminal/find";
|
||||
import type { TerminalFindResult } from "@/terminal/runtime/terminal-emulator-runtime";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { useRetainedPanelActive } from "@/components/retained-panel";
|
||||
import { hasActiveWebOverlay } from "@/lib/overlay-root";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import {
|
||||
@@ -68,7 +69,7 @@ import {
|
||||
type TerminalResizeRequest,
|
||||
} from "./terminal-resize-debouncer";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { isNative } from "@/constants/platform";
|
||||
import { isNative, isWeb } from "@/constants/platform";
|
||||
import {
|
||||
applyTerminalRendererReadyChange,
|
||||
resolveTerminalStreamTarget,
|
||||
@@ -377,19 +378,39 @@ export function TerminalPane({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile || !isPaneFocused || !terminalId) {
|
||||
if (isMobile || !isPaneFocused || !isWorkspaceFocused || !terminalId) {
|
||||
lastAutoFocusKeyRef.current = null;
|
||||
return;
|
||||
}
|
||||
if (!isWorkspaceFocused) {
|
||||
if (rendererReadyStreamKey !== terminalStreamKey) {
|
||||
return;
|
||||
}
|
||||
const focusKey = `${scopeKey}:${terminalId}`;
|
||||
if (lastAutoFocusKeyRef.current !== focusKey) {
|
||||
lastAutoFocusKeyRef.current = focusKey;
|
||||
if (isWeb) {
|
||||
const active = document.activeElement;
|
||||
const hasFocusedInput =
|
||||
active instanceof HTMLElement &&
|
||||
active.matches('input, textarea, [contenteditable="true"]') &&
|
||||
!active.matches(".xterm-helper-textarea") &&
|
||||
active.getClientRects().length > 0;
|
||||
if (hasActiveWebOverlay() || hasFocusedInput) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
requestTerminalFocus();
|
||||
}
|
||||
}, [isMobile, isPaneFocused, isWorkspaceFocused, requestTerminalFocus, scopeKey, terminalId]);
|
||||
}, [
|
||||
isMobile,
|
||||
isPaneFocused,
|
||||
isWorkspaceFocused,
|
||||
rendererReadyStreamKey,
|
||||
requestTerminalFocus,
|
||||
scopeKey,
|
||||
terminalId,
|
||||
terminalStreamKey,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const canRequest = canRequestFocusClaim({
|
||||
|
||||
Reference in New Issue
Block a user