From abd68c24c3954e9c060982c3e83af85f542657ad Mon Sep 17 00:00:00 2001 From: arman Date: Wed, 11 Nov 2026 12:39:04 +0000 Subject: [PATCH 1/6] fix(web): Cmd+F in the Files panel focuses its search In the right panel, mod+f resolves to preview.find, which only the browser preview handles, so with focus in the Files tree the key did nothing. The Files panel now claims mod+f itself and focuses and selects its Search files box, like the PDF viewer and diff find do. Co-Authored-By: Claude Opus 5.5 --- .../files/FileBrowserPanel.browser.test.tsx | 22 +++++++++++++++++++ .../src/components/files/FileBrowserPanel.tsx | 20 +++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx b/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx index 1c0e1bc914bed..51bd3c726e5f6 100644 --- a/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx +++ b/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx @@ -4,6 +4,7 @@ import { page, userEvent } from "vite-plus/test/browser"; import { COMPOSER_MENTION_DRAG_TYPE } from "~/components/chat/composerMentionDrag"; import { dismissContextMenu } from "~/contextMenuFallback"; +import { isMacPlatform } from "~/lib/utils"; import { render } from "../../../test/render"; import FileBrowserPanel from "./FileBrowserPanel"; @@ -115,4 +116,25 @@ describe("FileBrowserPanel", () => { row.click(); await vi.waitFor(() => expect(onOpenFile).toHaveBeenCalledTimes(1)); }); + + it("mod+f from a tree row focuses the file search", async () => { + await using _screen = await render( + {}} + workspaceMutationId={null} + />, + ); + await vi.waitFor(() => expect(findTreeRow("README.md")).not.toBeNull()); + findTreeRow("README.md")!.focus(); + + const modifier = isMacPlatform(navigator.platform) ? "Meta" : "Control"; + await userEvent.keyboard(`{${modifier}>}f{/${modifier}}`); + + await expect.element(page.getByRole("searchbox", { name: "Search repo files" })).toHaveFocus(); + }); }); diff --git a/apps/web/src/components/files/FileBrowserPanel.tsx b/apps/web/src/components/files/FileBrowserPanel.tsx index 6b9c912be0a06..76901adcb1f9b 100644 --- a/apps/web/src/components/files/FileBrowserPanel.tsx +++ b/apps/web/src/components/files/FileBrowserPanel.tsx @@ -10,7 +10,7 @@ import { serializeComposerFileLink } from "@t3tools/shared/composerTrigger"; import * as AsyncResult from "effect/reactivity/AsyncResult"; import * as Cause from "effect/Cause"; import { ChevronsDownUpIcon, ChevronsUpDownIcon } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { type Ref, useEffect, useRef, useState } from "react"; import { Button } from "~/components/ui/button"; import { InputGroup, InputGroupInput } from "~/components/ui/input-group"; @@ -33,6 +33,7 @@ import { buildFileTreePathUpdates } from "./fileTreePathReconciliation"; import { loadFileAncestors } from "./loadFileAncestors"; import { useDirectoryEntries } from "./useDirectoryEntries"; import { useProjectPathSearch } from "~/state/queries"; +import { isMacPlatform } from "~/lib/utils"; interface FileBrowserPanelProps { environmentId: EnvironmentId; @@ -73,16 +74,21 @@ function RefreshFilesButton(props: { isPending: boolean; onRefresh: () => void } ); } -function FileSearchField(props: { +function FileSearchField({ + ref, + ...props +}: { ariaLabel: string; name: string; onClose: () => void; onValueChange: (value: string) => void; + ref: Ref; value: string; }) { return ( (null); const expandedPathsRef = useRef(new Set()); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes to the Pierre tree model to load directories as they are expanded useEffect(() => { @@ -482,6 +489,14 @@ export default function FileBrowserPanel({ // Capture dragstart before the tree selects the row so selection sees the drag flag. onDragStartCapture={(event) => dragMention.handleDragStart(event.nativeEvent)} onDragEnd={() => dragMention.handleDragEnd((path) => model.getItem(path)?.deselect())} + onKeyDown={(event) => { + const isModPressed = isMacPlatform(navigator.platform) ? event.metaKey : event.ctrlKey; + if (!isModPressed || event.altKey || event.shiftKey || event.key.toLowerCase() !== "f") + return; + event.preventDefault(); + searchInputRef.current?.focus(); + searchInputRef.current?.select(); + }} >
Date: Wed, 11 Nov 2026 18:04:02 +0000 Subject: [PATCH 2/6] fix(web): a surface picked from the + menu keeps focus instead of returning it to + Co-Authored-By: Claude Opus 5.5 --- .../RightPanelTabs.browser.test.tsx | 133 ++++++++++++------ apps/web/src/components/RightPanelTabs.tsx | 32 +++-- 2 files changed, 111 insertions(+), 54 deletions(-) diff --git a/apps/web/src/components/RightPanelTabs.browser.test.tsx b/apps/web/src/components/RightPanelTabs.browser.test.tsx index 4a7617bb1abd8..98697758d06f3 100644 --- a/apps/web/src/components/RightPanelTabs.browser.test.tsx +++ b/apps/web/src/components/RightPanelTabs.browser.test.tsx @@ -1,9 +1,12 @@ import "~/index.css"; +import { type ComponentProps, useState } from "react"; import { describe, expect, it, vi } from "vite-plus/test"; +import { page, userEvent } from "vite-plus/test/browser"; import type { RightPanelSurface } from "~/rightPanelStore"; import { render, stubGlobal } from "../../test/render"; +import { createWorkspaceFocusController } from "~/lib/workspaceFocus"; import { HistorySwipeNavigation } from "./HistorySwipeNavigation"; import { RightPanelTabs } from "./RightPanelTabs"; @@ -59,6 +62,55 @@ const surfaces: RightPanelSurface[] = Array.from({ length: 16 }, (_, index) => ( activeTerminalId: `${index}`, })); +function Tabs(props: Partial>) { + const noop = () => {}; + return ( + ({ + terminalFocus: false, + terminalOpen: false, + previewFocus: false, + previewOpen: false, + isWeb: true, + isDesktop: false, + })} + surfaces={surfaces} + environmentId={null} + activeSurfaceId={null} + pendingSurfaceIds={new Set()} + previewSessions={{}} + desktopByTabId={{}} + terminalLabelsById={new Map(surfaces.map((surface) => [surface.id, "Terminal"]))} + onActivate={noop} + onCloseSurface={noop} + onCloseOtherSurfaces={noop} + onCloseSurfacesToRight={noop} + onCloseAllSurfaces={noop} + onCopyFilePath={noop} + onAddBrowser={noop} + onAddBrowserInProfile={noop} + onAddTerminal={noop} + onAddDiff={noop} + onAddFiles={noop} + onAddPullRequest={noop} + onAddPullRequests={noop} + onAddDevice={noop} + browserAvailable={false} + terminalAvailable + diffAvailable={false} + filesAvailable={false} + pullRequestAvailable={false} + pullRequestsAvailable={false} + deviceAvailable={false} + {...props} + > + {props.children ?? null} + + ); +} + describe("RightPanelTabs", () => { it("a flick that scrolls the tab strip to its left edge keeps the thread, a swipe from the edge goes back", async () => { navigateHistory.mockReset(); @@ -67,52 +119,10 @@ describe("RightPanelTabs", () => { using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("MacIntel"); const panel = document.createElement("div"); panel.style.cssText = "width: 320px; height: 200px; display: flex"; - const noop = () => {}; await using screen = await render( <> - ({ - terminalFocus: false, - terminalOpen: false, - previewFocus: false, - previewOpen: false, - isWeb: true, - isDesktop: false, - })} - surfaces={surfaces} - environmentId={null} - activeSurfaceId={null} - pendingSurfaceIds={new Set()} - previewSessions={{}} - desktopByTabId={{}} - terminalLabelsById={new Map(surfaces.map((surface) => [surface.id, "Terminal"]))} - onActivate={noop} - onCloseSurface={noop} - onCloseOtherSurfaces={noop} - onCloseSurfacesToRight={noop} - onCloseAllSurfaces={noop} - onCopyFilePath={noop} - onAddBrowser={noop} - onAddBrowserInProfile={noop} - onAddTerminal={noop} - onAddDiff={noop} - onAddFiles={noop} - onAddPullRequest={noop} - onAddPullRequests={noop} - onAddDevice={noop} - browserAvailable={false} - terminalAvailable - diffAvailable={false} - filesAvailable={false} - pullRequestAvailable={false} - pullRequestsAvailable={false} - deviceAvailable={false} - > - {null} - + , { container: panel }, ); @@ -133,4 +143,41 @@ describe("RightPanelTabs", () => { expect(navigateHistory).toHaveBeenCalledExactlyOnceWith("back"); }); + it("a surface picked from the add menu keeps the focus it takes; dismissing the menu returns to +", async () => { + const focus = createWorkspaceFocusController(document); + using _focus = { [Symbol.dispose]: focus.dispose }; + function Panel() { + const [activeSurfaceId, setActiveSurfaceId] = useState("terminal:0"); + return ( + { + setActiveSurfaceId("terminal:1"); + focus.request({ area: "panel" }); + }} + > + + + ); + } + await using _screen = await render(); + const add = page.getByRole("button", { name: "Add panel surface" }); + + await userEvent.click(add); + await userEvent.keyboard("f"); + await expect.element(page.getByRole("menu")).not.toBeInTheDocument(); + await expect + .element(page.getByRole("textbox", { name: "Content of terminal:1" })) + .toHaveFocus(); + + await userEvent.click(add); + await userEvent.keyboard("{Escape}"); + await expect.element(page.getByRole("menu")).not.toBeInTheDocument(); + await expect.element(add).toHaveFocus(); + }); }); diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index f3066201f71ec..bc768c781dae4 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -1035,6 +1035,15 @@ export function RightPanelTabs(props: RightPanelTabsProps) { const addSurfaceTriggerRef = useRef(null); const [renamingDevice, setRenamingDevice] = useState(null); const [addSurfaceMenuOpen, setAddSurfaceMenuOpen] = useState(false); + const [surfacePicked, setSurfacePicked] = useState(false); + const openAddSurfaceMenu = (open: boolean) => { + if (open) setSurfacePicked(false); + setAddSurfaceMenuOpen(open); + }; + const pickSurface = (open: () => void) => () => { + setSurfacePicked(true); + open(); + }; const [tabScrollState, setTabScrollState] = useState({ hasOverflow: false, canScrollLeft: false, @@ -1056,7 +1065,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { event.stopPropagation(); if (!event.repeat) { addSurfaceTriggerRef.current?.focus(); - setAddSurfaceMenuOpen(true); + openAddSurfaceMenu(true); } }); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes a document keydown listener for the new surface shortcut while the panel is open @@ -1101,7 +1110,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "B", available: props.browserAvailable, disabledReason: SURFACE_DISABLED_REASONS.browser, - onClick: props.onAddBrowser, + onClick: pickSurface(props.onAddBrowser), }, { label: "Terminal", @@ -1109,7 +1118,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "T", available: props.terminalAvailable, disabledReason: SURFACE_DISABLED_REASONS.terminal, - onClick: props.onAddTerminal, + onClick: pickSurface(props.onAddTerminal), }, { label: "Files", @@ -1117,7 +1126,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "F", available: props.filesAvailable, disabledReason: SURFACE_DISABLED_REASONS.files, - onClick: props.onAddFiles, + onClick: pickSurface(props.onAddFiles), }, { label: "Diff", @@ -1125,7 +1134,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "D", available: props.diffAvailable, disabledReason: SURFACE_DISABLED_REASONS.diff, - onClick: props.onAddDiff, + onClick: pickSurface(props.onAddDiff), }, { label: "Pull request", @@ -1133,7 +1142,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "P", available: props.pullRequestAvailable, disabledReason: SURFACE_DISABLED_REASONS.pullRequest, - onClick: props.onAddPullRequest, + onClick: pickSurface(props.onAddPullRequest), }, { label: "Linked pull requests", @@ -1141,7 +1150,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "L", available: props.pullRequestsAvailable, disabledReason: SURFACE_DISABLED_REASONS.pullRequests, - onClick: props.onAddPullRequests, + onClick: pickSurface(props.onAddPullRequests), }, { label: "Device", @@ -1149,7 +1158,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "M", available: props.deviceAvailable, disabledReason: SURFACE_DISABLED_REASONS.device, - onClick: props.onAddDevice, + onClick: pickSurface(props.onAddDevice), }, ...(props.onAddSideChat ? [ @@ -1159,7 +1168,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { shortcut: "S", available: props.sideChatAvailable === true && !props.sideChatDisabledReason, disabledReason: props.sideChatDisabledReason ?? SURFACE_DISABLED_REASONS.sideChat, - onClick: props.onAddSideChat, + onClick: pickSurface(props.onAddSideChat), }, ] : []), @@ -1396,7 +1405,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { })} {props.open !== false ? ( - + {addSurfaceActions.map((action) => { @@ -1444,7 +1454,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { {browserProfiles.map((profile) => ( props.onAddBrowserInProfile(profile.id)} + onClick={pickSurface(() => props.onAddBrowserInProfile(profile.id))} > {profile.name} From 5799b63a8fb11f82f45815d3c875c2b830e16cac Mon Sep 17 00:00:00 2001 From: arman Date: Wed, 11 Nov 2026 18:04:14 +0000 Subject: [PATCH 3/6] fix(web): Cmd+F anywhere in the right panel focuses the Files tab search Co-Authored-By: Claude Opus 5.5 --- .../files/FileBrowserPanel.browser.test.tsx | 11 ++++++++-- .../src/components/files/FileBrowserPanel.tsx | 21 ++++++++++++++++--- .../src/components/files/FilePreviewPanel.tsx | 1 + 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx b/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx index 51bd3c726e5f6..7da02e86eac1d 100644 --- a/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx +++ b/apps/web/src/components/files/FileBrowserPanel.browser.test.tsx @@ -7,6 +7,7 @@ import { dismissContextMenu } from "~/contextMenuFallback"; import { isMacPlatform } from "~/lib/utils"; import { render } from "../../../test/render"; import FileBrowserPanel from "./FileBrowserPanel"; +import { dispatchPreviewAction } from "../preview/previewActionBus"; const entries: readonly ProjectEntry[] = [ { path: "src", kind: "directory" }, @@ -117,7 +118,7 @@ describe("FileBrowserPanel", () => { await vi.waitFor(() => expect(onOpenFile).toHaveBeenCalledTimes(1)); }); - it("mod+f from a tree row focuses the file search", async () => { + it("mod+f from a tree row, or the panel find command, focuses the file search", async () => { await using _screen = await render( { selectedPathRevealId={0} onOpenFile={() => {}} workspaceMutationId={null} + answersPanelFind />, ); await vi.waitFor(() => expect(findTreeRow("README.md")).not.toBeNull()); @@ -135,6 +137,11 @@ describe("FileBrowserPanel", () => { const modifier = isMacPlatform(navigator.platform) ? "Meta" : "Control"; await userEvent.keyboard(`{${modifier}>}f{/${modifier}}`); - await expect.element(page.getByRole("searchbox", { name: "Search repo files" })).toHaveFocus(); + const search = page.getByRole("searchbox", { name: "Search repo files" }); + await expect.element(search).toHaveFocus(); + + (search.element() as HTMLElement).blur(); + dispatchPreviewAction("find"); + await expect.element(search).toHaveFocus(); }); }); diff --git a/apps/web/src/components/files/FileBrowserPanel.tsx b/apps/web/src/components/files/FileBrowserPanel.tsx index 76901adcb1f9b..292535e7bb1d0 100644 --- a/apps/web/src/components/files/FileBrowserPanel.tsx +++ b/apps/web/src/components/files/FileBrowserPanel.tsx @@ -10,7 +10,7 @@ import { serializeComposerFileLink } from "@t3tools/shared/composerTrigger"; import * as AsyncResult from "effect/reactivity/AsyncResult"; import * as Cause from "effect/Cause"; import { ChevronsDownUpIcon, ChevronsUpDownIcon } from "lucide-react"; -import { type Ref, useEffect, useRef, useState } from "react"; +import { type Ref, useEffect, useEffectEvent, useRef, useState } from "react"; import { Button } from "~/components/ui/button"; import { InputGroup, InputGroupInput } from "~/components/ui/input-group"; @@ -34,6 +34,7 @@ import { loadFileAncestors } from "./loadFileAncestors"; import { useDirectoryEntries } from "./useDirectoryEntries"; import { useProjectPathSearch } from "~/state/queries"; import { isMacPlatform } from "~/lib/utils"; +import { type PreviewAction, subscribePreviewAction } from "../preview/previewActionBus"; interface FileBrowserPanelProps { environmentId: EnvironmentId; @@ -47,6 +48,7 @@ interface FileBrowserPanelProps { hostFolder?: boolean; onRefreshSelectedFile?: () => void; workspaceMutationId: string | null; + answersPanelFind?: boolean; } function treePath(entry: ProjectEntry): string { @@ -148,6 +150,7 @@ export default function FileBrowserPanel({ onRefreshSelectedFile, workspaceMutationId, hostFolder = false, + answersPanelFind = false, }: FileBrowserPanelProps) { const panelPath = (relativePath: string) => hostFolder ? joinHostPath(cwd, relativePath) : relativePath; @@ -327,6 +330,19 @@ export default function FileBrowserPanel({ search.close(); }; const searchInputRef = useRef(null); + const focusSearch = () => { + searchInputRef.current?.focus(); + searchInputRef.current?.select(); + }; + const onPanelAction = useEffectEvent((action: PreviewAction) => { + // Hidden panel tabs stay mounted; only the visible Files tab answers. + if (action === "find" && searchInputRef.current?.getClientRects().length) focusSearch(); + }); + // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes to the panel find command, which fires while focus is anywhere in the right panel + useEffect(() => { + if (!answersPanelFind) return; + return subscribePreviewAction(onPanelAction); + }, [answersPanelFind]); const expandedPathsRef = useRef(new Set()); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes to the Pierre tree model to load directories as they are expanded useEffect(() => { @@ -494,8 +510,7 @@ export default function FileBrowserPanel({ if (!isModPressed || event.altKey || event.shiftKey || event.key.toLowerCase() !== "f") return; event.preventDefault(); - searchInputRef.current?.focus(); - searchInputRef.current?.select(); + focusSearch(); }} >
Date: Wed, 11 Nov 2026 18:26:48 +0000 Subject: [PATCH 4/6] fix(web): opening a panel tab restores focus inside its content, not to the tab strip Co-Authored-By: Claude Opus 5.5 --- apps/web/src/lib/workspaceFocus.browser.test.ts | 15 +++++++++++++++ apps/web/src/lib/workspaceFocus.ts | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/web/src/lib/workspaceFocus.browser.test.ts b/apps/web/src/lib/workspaceFocus.browser.test.ts index d157f9068b378..04d23ead35bdf 100644 --- a/apps/web/src/lib/workspaceFocus.browser.test.ts +++ b/apps/web/src/lib/workspaceFocus.browser.test.ts @@ -259,6 +259,21 @@ describe("workspace area focus", () => { expect(document.activeElement).toBe(clickedRow); }); + it("restores a panel tab inside its content, not to the tab strip", () => { + using fixture = setup(); + const panel = fixture.area("panel", 3); + const content = document.createElement("div"); + content.setAttribute("data-right-panel-surface-content", ""); + const search = document.createElement("input"); + search.setAttribute("data-workspace-focus-default", ""); + content.append(search); + panel.element.append(content); + panel.element.dataset.workspaceFocusKey = "panel:files"; + panel.button.focus(); + expect(fixture.controller.focus("panel")).toBe(true); + expect(document.activeElement).toBe(search); + }); + it("focuses a side chat's message input when the panel opens on it", () => { using fixture = setup(); const panel = fixture.area("panel", 3); diff --git a/apps/web/src/lib/workspaceFocus.ts b/apps/web/src/lib/workspaceFocus.ts index 5790067ce664f..509674236855f 100644 --- a/apps/web/src/lib/workspaceFocus.ts +++ b/apps/web/src/lib/workspaceFocus.ts @@ -25,6 +25,12 @@ export interface WorkspaceFocusTarget { restore?: boolean; } +function containsComposed(container: Element, element: Element): boolean { + if (container.contains(element)) return true; + const root = element.getRootNode(); + return root instanceof ShadowRoot && containsComposed(container, root.host); +} + function workspaceArea(element: Element): HTMLElement | null { const area = element.closest(AREA_SELECTOR); if (area) return area; @@ -135,10 +141,15 @@ export function createWorkspaceFocusController(doc: Document) { const focusArea = (area: HTMLElement, restore = true, nested = true) => { const key = area.dataset.workspaceFocusKey ?? area.dataset.workspaceArea!; + const surface = area.querySelector("[data-right-panel-surface-content]"); const previous = restore ? lastFocused.get(key) : undefined; const usable = (element: HTMLElement) => isWorkspaceElementVisible(element) && !element.matches(":disabled"); const belongs = (element: HTMLElement) => workspaceArea(element) === area && usable(element); + // A panel tab restores focus inside its content, never to the tab strip or "+" that the + // shortcut or menu passed through on the way. + const restorable = (element: HTMLElement) => + belongs(element) && (!surface || containsComposed(surface, element)); // A side chat's message input sits in its own area inside the panel. Opening the // panel focuses it, but F6 visits that area on its own, so it moves past it here. const preferred = Array.from( @@ -155,7 +166,7 @@ export function createWorkspaceFocusController(doc: Document) { previous && !previous.isConnected ? Array.from(area.querySelectorAll(FOCUSABLE_SELECTOR)).find( (element) => - belongs(element) && + restorable(element) && element.tagName === previous.tagName && ((previous.id && element.id === previous.id) || (previous.getAttribute("aria-label") && @@ -165,7 +176,6 @@ export function createWorkspaceFocusController(doc: Document) { element.textContent === previous.textContent)), ) : undefined; - const surface = area.querySelector("[data-right-panel-surface-content]"); const surfaceControl = surface ? Array.from(surface.querySelectorAll(FOCUSABLE_SELECTOR)).find( (element) => belongs(element) && element.tabIndex >= 0, @@ -175,7 +185,7 @@ export function createWorkspaceFocusController(doc: Document) { // so the conversation reuses a control only while it is still in view. const isConversation = area.dataset.workspaceArea === "conversation"; const targets = new Set([ - previous && belongs(previous) && (!isConversation || isInScrollView(previous)) + previous && restorable(previous) && (!isConversation || isInScrollView(previous)) ? previous : undefined, isConversation ? undefined : restored, From e0d91a51145478f89400532257213527aacf6cd8 Mon Sep 17 00:00:00 2001 From: arman Date: Wed, 11 Nov 2026 18:26:49 +0000 Subject: [PATCH 5/6] fix(web): picking a surface from the + menu always moves focus into the panel Co-Authored-By: Claude Opus 5.5 --- .../RightPanelTabs.browser.test.tsx | 55 +++++++++++-------- apps/web/src/components/RightPanelTabs.tsx | 2 + 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/RightPanelTabs.browser.test.tsx b/apps/web/src/components/RightPanelTabs.browser.test.tsx index 98697758d06f3..6ca4024a868cb 100644 --- a/apps/web/src/components/RightPanelTabs.browser.test.tsx +++ b/apps/web/src/components/RightPanelTabs.browser.test.tsx @@ -6,7 +6,7 @@ import { page, userEvent } from "vite-plus/test/browser"; import type { RightPanelSurface } from "~/rightPanelStore"; import { render, stubGlobal } from "../../test/render"; -import { createWorkspaceFocusController } from "~/lib/workspaceFocus"; +import { startWorkspaceFocus, workspaceAreaProps } from "~/lib/workspaceFocus"; import { HistorySwipeNavigation } from "./HistorySwipeNavigation"; import { RightPanelTabs } from "./RightPanelTabs"; @@ -143,37 +143,44 @@ describe("RightPanelTabs", () => { expect(navigateHistory).toHaveBeenCalledExactlyOnceWith("back"); }); - it("a surface picked from the add menu keeps the focus it takes; dismissing the menu returns to +", async () => { - const focus = createWorkspaceFocusController(document); - using _focus = { [Symbol.dispose]: focus.dispose }; + it("a surface picked from the add menu takes focus, even when already open; dismissing the menu returns to +", async () => { + using _focus = { + [Symbol.dispose]: startWorkspaceFocus({ subscribe: () => () => {} } as never), + }; function Panel() { const [activeSurfaceId, setActiveSurfaceId] = useState("terminal:0"); return ( - { - setActiveSurfaceId("terminal:1"); - focus.request({ area: "panel" }); - }} - > - - + <> +
+