(feat): Open Child Folders as Apps (port from conductor) (#3615)

* feat(app): open child folders in preferred editor

* fix(app): hide unavailable desktop open targets

* fix(app): test desktop targets without JSDOM

* refactor(app): isolate open-in-editor feature
This commit is contained in:
Caique Maurano
2026-08-27 10:35:17 +00:00
committed by GitHub
parent 7ae5133ed2
commit 3377997f30
19 changed files with 332 additions and 16 deletions
@@ -5,6 +5,7 @@ import {
Copy,
CopyPlus,
Download,
ExternalLink,
FilePlus,
FileText,
FolderMinus,
@@ -51,6 +52,8 @@ interface FileActionsContextMenuContentProps {
fileKind: "file" | "directory";
fileExists?: boolean;
onOpenFile?: () => void;
onOpenInEditor?: () => void;
editorTargetName?: string;
onOpenToSide?: () => void;
onCopyPath?: () => void;
onCopyRelativePath?: () => void;
@@ -76,6 +79,8 @@ export function FileActionsContextMenuContent({
fileKind,
fileExists = true,
onOpenFile,
onOpenInEditor,
editorTargetName,
onOpenToSide,
onCopyPath,
onCopyRelativePath,
@@ -93,6 +98,19 @@ export function FileActionsContextMenuContent({
testIDPrefix,
}: FileActionsContextMenuContentProps): ReactElement | null {
const { t } = useTranslation();
const openInEditorAction = useMemo<FileAction | null>(
() =>
fileKind === "directory" && onOpenInEditor && editorTargetName
? {
key: "open-in-editor",
group: "open",
label: t("workspace.fileActions.openIn", { target: editorTargetName }),
icon: ExternalLink,
onSelect: onOpenInEditor,
}
: null,
[editorTargetName, fileKind, onOpenInEditor, t],
);
const actions = useMemo<FileAction[]>(() => {
const availableFile = fileKind === "file" && fileExists;
const specs: Array<FileAction | null> = [
@@ -132,6 +150,7 @@ export function FileActionsContextMenuContent({
onSelect: onOpenFile,
}
: null,
openInEditorAction,
optionalFileAction(availableFile, onOpenToSide, {
key: "open-to-side",
group: "open",
@@ -242,6 +261,7 @@ export function FileActionsContextMenuContent({
onNewFile,
onNewFolder,
onOpenFile,
openInEditorAction,
onOpenToSide,
onRename,
onReveal,
@@ -59,8 +59,8 @@ import { useSessionStore } from "@/stores/session-store";
import { FileActionsContextMenuContent } from "@/components/file-actions-menu";
import { ContextMenu, ContextMenuTrigger, useContextMenu } from "@/components/ui/context-menu";
import { useFileDownload } from "@/hooks/use-file-download";
import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
import { useFileExplorerActions } from "@/hooks/use-file-explorer-actions";
import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
import { buildWorkspaceExplorerStateKey } from "@/hooks/use-file-explorer-actions";
import { usePanelStore, type ExpandedPathsUpdate, type SortOption } from "@/stores/panel-store";
import { buildAbsoluteExplorerPath } from "@/utils/explorer-paths";
@@ -77,6 +77,7 @@ import { useWorkspaceFileDragSource } from "@/attachments/use-workspace-file-dra
import { confirmDialog } from "@/utils/confirm-dialog";
import { useToast } from "@/contexts/toast-context";
import { openDesktopTarget, useDesktopOpenTargets } from "@/workspace/desktop-open-targets";
import { useOpenDirectoryInEditor } from "@/workspace/open-in-editor/directory";
const SORT_OPTIONS: { value: SortOption }[] = [
{ value: "name" },
@@ -113,6 +114,8 @@ interface TreeRowItemProps {
onSelectEntry: (entry: ExplorerEntry) => void;
onCopyPath: (path: string) => void;
onCopyRelativePath: (path: string) => void;
onOpenInEditor?: (entry: ExplorerEntry) => void;
editorTargetName?: string;
onRevealEntry?: (entry: ExplorerEntry) => void;
revealTargetName?: string;
onDownloadEntry: (entry: ExplorerEntry) => void;
@@ -237,6 +240,8 @@ function TreeRowItem({
onSelectEntry,
onCopyPath,
onCopyRelativePath,
onOpenInEditor,
editorTargetName,
onRevealEntry,
revealTargetName,
onDownloadEntry,
@@ -290,6 +295,10 @@ function TreeRowItem({
onCopyRelativePath(entry.path);
}, [onCopyRelativePath, entry.path]);
const handleOpenInEditor = useCallback(() => {
onOpenInEditor?.(entry);
}, [entry, onOpenInEditor]);
const handleReveal = useCallback(() => {
onRevealEntry?.(entry);
}, [onRevealEntry, entry]);
@@ -366,6 +375,8 @@ function TreeRowItem({
</ContextMenuTrigger>
<FileActionsContextMenuContent
fileKind={entry.kind}
onOpenInEditor={isDirectory && onOpenInEditor ? handleOpenInEditor : undefined}
editorTargetName={editorTargetName}
onCopyPath={handleCopy}
onCopyRelativePath={handleCopyRelativePath}
onReveal={onRevealEntry ? handleReveal : undefined}
@@ -439,6 +450,10 @@ export function FileExplorerPane({
isLocalExecution: isLocalDaemon,
});
const fileManagerTarget = desktopOpenTargets.find((target) => target.kind === "file-manager");
const openDirectoryInEditor = useOpenDirectoryInEditor({
serverId,
workspaceDirectory: normalizedWorkspaceRoot,
});
// COMPAT(fsEntryOps): added in v0.3.0, remove gate after 2027-02-08.
const fsEntryOpsEnabled = useSessionStore(
(state) => state.sessions[serverId]?.serverInfo?.features?.fsEntryOps === true,
@@ -608,6 +623,11 @@ export function FileExplorerPane({
[fileManagerTarget, normalizedWorkspaceRoot, t, toast],
);
const handleOpenDirectoryInEditor = useCallback(
(entry: ExplorerEntry) => openDirectoryInEditor?.open(entry.path),
[openDirectoryInEditor],
);
const handleDownloadEntry = useCallback(
(entry: ExplorerEntry) => {
if (entry.kind !== "file") {
@@ -959,6 +979,8 @@ export function FileExplorerPane({
onSelectEntry={handleSelectEntry}
onCopyPath={handleCopyPath}
onCopyRelativePath={handleCopyRelativePath}
onOpenInEditor={openDirectoryInEditor ? handleOpenDirectoryInEditor : undefined}
editorTargetName={openDirectoryInEditor?.targetName}
onRevealEntry={fileManagerTarget ? handleRevealEntry : undefined}
revealTargetName={fileManagerTarget?.label}
onDownloadEntry={handleDownloadEntry}
@@ -979,6 +1001,7 @@ export function FileExplorerPane({
handleCollapseDirectory,
handleCopyPath,
handleCopyRelativePath,
handleOpenDirectoryInEditor,
handleDeleteEntry,
handleDownloadEntry,
handleDraftCommit,
@@ -992,6 +1015,7 @@ export function FileExplorerPane({
handleSelectEntry,
isDirectoryLoading,
fileManagerTarget,
openDirectoryInEditor,
selectedEntryPath,
onAddToChat,
onOpenFileToSide,
@@ -1424,6 +1448,8 @@ function TreeRowDispatcher({
onSelectEntry,
onCopyPath,
onCopyRelativePath,
onOpenInEditor,
editorTargetName,
onRevealEntry,
revealTargetName,
onDownloadEntry,
@@ -1446,6 +1472,8 @@ function TreeRowDispatcher({
onSelectEntry: (entry: ExplorerEntry) => void;
onCopyPath: (path: string) => void | Promise<void>;
onCopyRelativePath: (path: string) => void | Promise<void>;
onOpenInEditor?: (entry: ExplorerEntry) => void;
editorTargetName?: string;
onRevealEntry?: (entry: ExplorerEntry) => void;
revealTargetName?: string;
onDownloadEntry: (entry: ExplorerEntry) => void;
@@ -1477,6 +1505,8 @@ function TreeRowDispatcher({
onSelectEntry={onSelectEntry}
onCopyPath={onCopyPath}
onCopyRelativePath={onCopyRelativePath}
onOpenInEditor={onOpenInEditor}
editorTargetName={editorTargetName}
onRevealEntry={onRevealEntry}
revealTargetName={revealTargetName}
onDownloadEntry={onDownloadEntry}
+1
View File
@@ -416,6 +416,7 @@ export const ar: TranslationResources = {
},
fileActions: {
openFile: "افتح الملف",
openIn: "فتح في {{target}}",
openToSide: "فتح إلى الجانب",
copyPath: "نسخ المسار",
copyRelativePath: "نسخ المسار النسبي",
+1
View File
@@ -414,6 +414,7 @@ export const en = {
},
fileActions: {
openFile: "Open file",
openIn: "Open in {{target}}",
openToSide: "Open to the side",
copyPath: "Copy path",
copyRelativePath: "Copy relative path",
+1
View File
@@ -420,6 +420,7 @@ export const es: TranslationResources = {
},
fileActions: {
openFile: "Abrir archivo",
openIn: "Abrir en {{target}}",
openToSide: "Abrir al lado",
copyPath: "Copiar ruta",
copyRelativePath: "Copiar ruta relativa",
+1
View File
@@ -420,6 +420,7 @@ export const fr: TranslationResources = {
},
fileActions: {
openFile: "Ouvrir le fichier",
openIn: "Ouvrir dans {{target}}",
openToSide: "Ouvrir sur le côté",
copyPath: "Copier le chemin",
copyRelativePath: "Copier le chemin relatif",
+1
View File
@@ -420,6 +420,7 @@ export const ja: TranslationResources = {
},
fileActions: {
openFile: "ファイルを開く",
openIn: "{{target}}で開く",
openToSide: "横に開く",
copyPath: "パスをコピー",
copyRelativePath: "相対パスをコピー",
+1
View File
@@ -417,6 +417,7 @@ export const ko: TranslationResources = {
},
fileActions: {
openFile: "파일 열기",
openIn: "{{target}}에서 열기",
openToSide: "옆에 열기",
copyPath: "경로 복사",
copyRelativePath: "상대 경로 복사",
+1
View File
@@ -420,6 +420,7 @@ export const ptBR: TranslationResources = {
},
fileActions: {
openFile: "Abrir arquivo",
openIn: "Abrir no {{target}}",
openToSide: "Abrir ao lado",
copyPath: "Copiar caminho",
copyRelativePath: "Copiar caminho relativo",
+1
View File
@@ -419,6 +419,7 @@ export const ru: TranslationResources = {
},
fileActions: {
openFile: "Открыть файл",
openIn: "Открыть в {{target}}",
openToSide: "Открыть сбоку",
copyPath: "Копировать путь",
copyRelativePath: "Копировать относительный путь",
+1
View File
@@ -416,6 +416,7 @@ export const zhCN: TranslationResources = {
},
fileActions: {
openFile: "打开文件",
openIn: "在 {{target}} 中打开",
openToSide: "在侧边打开",
copyPath: "复制路径",
copyRelativePath: "复制相对路径",
@@ -38,7 +38,7 @@ import {
import { SplitContainer } from "@/components/split-container";
import { RetainedPanel } from "@/components/retained-panel";
import { WorkspaceActions } from "@/git/workspace-actions";
import { WorkspaceOpenInEditorButton } from "@/screens/workspace/workspace-open-in-editor-button";
import { WorkspaceOpenInEditorButton } from "@/workspace/open-in-editor/button";
import { WorkspaceScriptsButton } from "@/screens/workspace/workspace-scripts-button";
import { ImportSessionSheet } from "@/components/import-session-sheet";
import { useToast } from "@/contexts/toast-context";
@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { selectDesktopOpenTargets } from "./desktop-open-targets";
const cachedTargets = [
{
id: "vscode",
label: "VS Code",
kind: "editor" as const,
icon: { kind: "symbol" as const, name: "terminal" as const },
},
];
describe("selectDesktopOpenTargets", () => {
it("hides cached targets when listing is unavailable", () => {
expect(
selectDesktopOpenTargets({
canListTargets: false,
targets: cachedTargets,
}),
).toEqual([]);
});
it("returns cached targets when listing is available", () => {
expect(
selectDesktopOpenTargets({
canListTargets: true,
targets: cachedTargets,
}),
).toEqual(cachedTargets);
});
});
@@ -26,6 +26,21 @@ interface AvailableDesktopEditorBridge {
openTarget: NonNullable<DesktopEditorBridge["openTarget"]>;
}
interface SelectDesktopOpenTargetsInput {
canListTargets: boolean;
targets: DesktopOpenTarget[] | undefined;
}
export function selectDesktopOpenTargets({
canListTargets,
targets,
}: SelectDesktopOpenTargetsInput): DesktopOpenTarget[] {
if (!canListTargets) {
return [];
}
return targets ?? [];
}
function getDesktopEditorBridge(): AvailableDesktopEditorBridge | null {
const bridge = getDesktopHost()?.editor;
if (!bridge?.listTargets || !bridge.openTarget) {
@@ -67,9 +82,13 @@ export function useDesktopOpenTargets(input: { isLocalExecution: boolean }) {
retry: false,
queryFn: listDesktopOpenTargets,
});
const targets = selectDesktopOpenTargets({
canListTargets,
targets: query.data,
});
return {
targets: query.data ?? [],
targets,
isAvailable: canListTargets,
};
}
@@ -23,7 +23,7 @@ import { isAbsolutePath } from "@/utils/path";
import { isWeb } from "@/constants/platform";
import { openDesktopTarget, useDesktopOpenTargets } from "@/workspace/desktop-open-targets";
import { resolveWorkspaceFilePaths, type WorkspaceFileLocation } from "@/workspace/file-open";
import { planWorkspaceOpenTargets } from "@/workspace/open-target-planner";
import { planWorkspaceOpenTargets } from "@/workspace/open-in-editor/planner";
import type { Theme } from "@/styles/theme";
import { ForgeBrandIcon } from "@/git/forge-icon";
import { getForgePresentation } from "@/git/forge";
@@ -59,11 +59,11 @@ function renderForgeOpenTargetIcon(icon: string): ReactElement {
interface OpenTargetMenuItemProps {
target: OpenTarget;
isPreferred: boolean;
onOpen: (target: OpenTarget) => void;
onSelect: (target: OpenTarget) => void;
}
function OpenTargetMenuItem({ target, isPreferred, onOpen }: OpenTargetMenuItemProps) {
const handleSelect = useCallback(() => onOpen(target), [onOpen, target]);
function OpenTargetMenuItem({ target, isPreferred, onSelect }: OpenTargetMenuItemProps) {
const handleSelect = useCallback(() => onSelect(target), [onSelect, target]);
const trailing = useMemo(
() => (isPreferred ? <ThemedCheckIcon size={16} uniProps={mutedColorMapping} /> : undefined),
[isPreferred],
@@ -178,10 +178,16 @@ export function WorkspaceOpenInEditorButton({
const handleOpenTarget = useCallback(
(target: OpenTarget) => {
void updatePreferredEditor(target.id).catch(() => undefined);
openMutation.mutate(target);
},
[openMutation, updatePreferredEditor],
[openMutation],
);
const handleSelectTarget = useCallback(
(target: OpenTarget) => {
void updatePreferredEditor(target.id).catch(() => undefined);
},
[updatePreferredEditor],
);
const primaryPressableStyle = useCallback(
@@ -267,7 +273,7 @@ export function WorkspaceOpenInEditorButton({
key={target.id}
target={target}
isPreferred={target.id === effectivePreferredEditorId}
onOpen={handleOpenTarget}
onSelect={handleSelectTarget}
/>
))}
</DropdownMenuContent>
@@ -0,0 +1,68 @@
import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useToast } from "@/contexts/toast-context";
import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
import { resolvePreferredEditorId, usePreferredEditor } from "@/hooks/use-preferred-editor";
import { openDesktopTarget, useDesktopOpenTargets } from "@/workspace/desktop-open-targets";
import { planWorkspaceOpenTargets } from "@/workspace/open-in-editor/planner";
interface UseOpenDirectoryInEditorInput {
serverId: string;
workspaceDirectory: string;
}
interface OpenDirectoryInEditorAction {
targetName: string;
open: (directoryPath: string) => void;
}
export function useOpenDirectoryInEditor({
serverId,
workspaceDirectory,
}: UseOpenDirectoryInEditorInput): OpenDirectoryInEditorAction | null {
const { t } = useTranslation();
const toast = useToast();
const isLocalExecution = useIsLocalDaemon(serverId);
const { preferredEditorId } = usePreferredEditor();
const { targets, isAvailable } = useDesktopOpenTargets({ isLocalExecution });
const editorTargets = useMemo(
() => targets.filter((target) => target.kind === "editor"),
[targets],
);
const preferredTarget = useMemo(() => {
const preferredId = resolvePreferredEditorId(
editorTargets.map((target) => target.id),
preferredEditorId,
);
return editorTargets.find((target) => target.id === preferredId) ?? null;
}, [editorTargets, preferredEditorId]);
const open = useCallback(
(directoryPath: string) => {
if (!preferredTarget) {
return;
}
const target = planWorkspaceOpenTargets({
workspaceDirectory,
directoryPath,
desktopTargets: [preferredTarget],
canUseDesktopBridge: isAvailable,
isLocalExecution,
}).find((candidate) => candidate.source === "desktop");
if (!target) {
return;
}
void openDesktopTarget(target.openInput).catch((cause: unknown) => {
toast.error(
cause instanceof Error ? cause.message : t("sidebar.project.actions.openFolderFailed"),
);
});
},
[isAvailable, isLocalExecution, preferredTarget, t, toast, workspaceDirectory],
);
return useMemo(
() => (preferredTarget ? { targetName: preferredTarget.label, open } : null),
[open, preferredTarget],
);
}
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { planWorkspaceOpenTargets } from "./open-target-planner";
import { planWorkspaceOpenTargets } from "./planner";
const desktopTargets = [
{
@@ -84,6 +84,37 @@ describe("planWorkspaceOpenTargets", () => {
});
});
it("plans a nested directory as the editor workspace", () => {
const targets = planWorkspaceOpenTargets({
workspaceDirectory: "/specs",
directoryPath: "repos/sample-android-app",
desktopTargets: [
{
id: "android-studio",
label: "Android Studio",
kind: "editor",
icon: { kind: "symbol", name: "terminal" },
},
],
canUseDesktopBridge: true,
isLocalExecution: true,
});
expect(targets).toEqual([
{
source: "desktop",
id: "android-studio",
label: "Android Studio",
editorId: "android-studio",
icon: { kind: "symbol", name: "terminal" },
openInput: {
editorId: "android-studio",
workspacePath: "/specs/repos/sample-android-app",
},
},
]);
});
it("passes custom target ids through as strings", () => {
const targets = planWorkspaceOpenTargets({
workspaceDirectory: "/repo",
@@ -33,6 +33,7 @@ export type PlannedWorkspaceOpenTarget = PlannedDesktopOpenTarget | PlannedForge
export interface PlanWorkspaceOpenTargetsInput {
workspaceDirectory: string;
directoryPath?: string | null;
activeFile?: WorkspaceFileLocation | null;
resolvedActiveFile?: ResolvedWorkspaceFilePaths | null;
desktopTargets: readonly DesktopOpenTarget[];
@@ -61,6 +62,7 @@ function resolveActiveFileForOpenTargets(
function planDesktopOpenTargets(input: {
workspaceDirectory: string;
directoryPath?: string | null;
activeFile?: WorkspaceFileLocation | null;
resolvedFile: ResolvedWorkspaceFilePaths | null;
desktopTargets: readonly DesktopOpenTarget[];
@@ -71,6 +73,20 @@ function planDesktopOpenTargets(input: {
return [];
}
const resolvedDirectory =
input.directoryPath === undefined || input.directoryPath === null
? null
: resolveWorkspaceFilePaths({
path: input.directoryPath,
workspaceRoot: input.workspaceDirectory,
});
if (input.directoryPath !== undefined && input.directoryPath !== null) {
if (!resolvedDirectory?.relativePath) {
return [];
}
}
const workspacePath = resolvedDirectory?.absolutePath ?? input.workspaceDirectory;
return input.desktopTargets.map((target) => {
if (!input.resolvedFile) {
return {
@@ -79,7 +95,7 @@ function planDesktopOpenTargets(input: {
label: target.label,
editorId: target.id,
icon: target.icon,
openInput: { editorId: target.id, workspacePath: input.workspaceDirectory },
openInput: { editorId: target.id, workspacePath },
};
}
return {
@@ -90,7 +106,7 @@ function planDesktopOpenTargets(input: {
icon: target.icon,
openInput: {
editorId: target.id,
workspacePath: input.workspaceDirectory,
workspacePath,
filePath: input.resolvedFile.absolutePath,
...(input.activeFile?.lineStart ? { line: input.activeFile.lineStart } : {}),
},
+89 -3
View File
@@ -1,6 +1,8 @@
import { readFile, rm } from "node:fs/promises";
import { mkdir, readFile, rm } from "node:fs/promises";
import path from "node:path";
import { expect, test, type Page } from "../../app/e2e/support/fixtures";
import { gotoAppShell, openSettings } from "../../app/e2e/support/helpers/app";
import { expandFolder, openFileExplorer } from "../../app/e2e/support/helpers/file-explorer";
import { installDesktopRuntime } from "./support/runtime";
import { clickSettingsBackToWorkspace } from "../../app/e2e/support/helpers/settings";
@@ -35,7 +37,7 @@ async function readEditorOpenRecords(recordPath: string): Promise<EditorOpenReco
}
}
async function chooseEditorTarget(page: Page, targetId: "vscode"): Promise<void> {
async function selectEditorTarget(page: Page, targetId: string): Promise<void> {
await expect(page.getByTestId("workspace-open-in-editor-primary")).toBeVisible({
timeout: 30_000,
});
@@ -66,6 +68,89 @@ async function expectEditorOpened(input: {
}
test.describe("Workspace open in editor", () => {
test("opens a nested folder in the preferred editor", async ({ page, withWorkspace }) => {
test.setTimeout(90_000);
const serverId = requireE2EEnv("E2E_SERVER_ID");
const recordPath = requireE2EEnv("E2E_EDITOR_RECORD_PATH");
await rm(recordPath, { force: true });
await installDesktopRuntime(page, {
serverId,
editorTargets: [
{
id: "android-studio",
label: "Android Studio",
kind: "editor",
icon: { kind: "symbol", name: "terminal" },
},
],
editorRecordPath: recordPath,
});
const workspace = await withWorkspace({ prefix: "workspace-nested-editor-target-" });
const childFolderName = "sample-android-app";
const childFolderPath = path.join(workspace.repoPath, "repos", childFolderName);
await mkdir(childFolderPath, { recursive: true });
await workspace.navigateTo();
await openFileExplorer(page);
await expandFolder(page, "repos");
await page.getByText(childFolderName, { exact: true }).click({ button: "right" });
const openInEditor = page.getByTestId(/file-explorer-row-\d+-open-in-editor$/);
await expect(openInEditor).toBeVisible({ timeout: 5_000 });
await openInEditor.click();
await expectEditorOpened({
recordPath,
editorId: "android-studio",
path: childFolderPath,
afterCount: 0,
});
});
test("selects an editor without opening it", async ({ page, withWorkspace }) => {
test.setTimeout(90_000);
const serverId = requireE2EEnv("E2E_SERVER_ID");
const recordPath = requireE2EEnv("E2E_EDITOR_RECORD_PATH");
await rm(recordPath, { force: true });
await installDesktopRuntime(page, {
serverId,
editorTargets: [
{
id: "cursor",
label: "Cursor",
kind: "editor",
icon: { kind: "symbol", name: "terminal" },
},
{
id: "zed",
label: "Zed",
kind: "editor",
icon: { kind: "symbol", name: "terminal" },
},
],
editorRecordPath: recordPath,
});
const workspace = await withWorkspace({ prefix: "workspace-editor-selection-" });
await workspace.navigateTo();
await selectEditorTarget(page, "zed");
const primaryButton = page.getByTestId("workspace-open-in-editor-primary");
await expect(primaryButton).toHaveAccessibleName("Open workspace in Zed");
await expect(primaryButton).toBeEnabled();
expect(await readEditorOpenRecords(recordPath)).toEqual([]);
await primaryButton.click();
await expectEditorOpened({
recordPath,
editorId: "zed",
path: workspace.repoPath,
afterCount: 0,
});
});
test("keeps the selected editor target after leaving and returning to the workspace", async ({
page,
withWorkspace,
@@ -97,7 +182,8 @@ test.describe("Workspace open in editor", () => {
const workspace = await withWorkspace({ prefix: "workspace-editor-target-" });
await workspace.navigateTo();
await chooseEditorTarget(page, "vscode");
await selectEditorTarget(page, "vscode");
await page.getByTestId("workspace-open-in-editor-primary").click();
await expectEditorOpened({
recordPath,
editorId: "vscode",