Files
Mohamed Boudra d3c76be9c1 Unify Explorer tabs and refine launch controls (#5942)
* feat(app): unify Explorer tabs and refine interface styling

Give Explorer the workspace tab controls while keeping launcher choices separate from drag-and-drop support.

Upgrade Lucide and set a shared icon stroke width for consistent styling. Reduce the desktop composer bottom inset to align the action row with its corners.

* fix(app): show active Explorer tabs in foreground

The shared row coupled foreground styling to keyboard shortcut ownership. Separate those concerns so Explorer can highlight its active tab while Cmd+T remains with the workspace pane.

* fix(app): refine Explorer close controls and launch options

* test(app): respect singleton Files in Explorer plugin workflow

* test(server): trim unused setup from no-track checkout regression

* test(app): count visible main tab rows after pane moves
2026-10-03 01:20:53 +01:00

181 lines
8.1 KiB
TypeScript

import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { expect, type Page, type TestInfo } from "@playwright/test";
import { gotoWorkspace } from "./launcher";
import { buildAgentRoute } from "./mock-agent";
import { connectNewWorkspaceDaemonClient } from "./new-workspace";
import { pluginRequirements } from "./plugin-fixture";
import { seedWorkspace } from "./seed-client";
import { ensureExplorerSidebar } from "./workspace-tabs";
type ExplorerPanelWorkspace = Awaited<ReturnType<typeof seedWorkspace>>;
async function writePanelPlugin(id: string, title: string) {
const directory = await mkdtemp(path.join(tmpdir(), "paseo-explorer-menu-"));
await writeFile(
path.join(directory, "paseo-plugin.json"),
JSON.stringify({ id, requirements: pluginRequirements }),
);
await writeFile(
path.join(directory, "index.client.tsx"),
`
import React from "react";
import { Text } from "react-native";
export default function contribute(client) {
client.addWorkspacePanel({ id: "review", title: ${JSON.stringify(title)}, icon: "Blocks", context: "workspace", locations: ["workspace", "explorer"], Component: ({ workspaceId }) => <Text>{${JSON.stringify(title)} + " workspace " + workspaceId}</Text> });
client.addWorkspacePanel({ id: "summary", title: ${JSON.stringify(`${title} summary`)}, icon: "Blocks", context: "workspace", locations: ["explorer"], Component: () => <Text>Summary content</Text> });
client.addWorkspacePanel({ id: "main", title: ${JSON.stringify(`${title} main only`)}, icon: "Blocks", context: "workspace", Component: () => null });
client.addWorkspacePanel({ id: "agent", title: ${JSON.stringify(`${title} agent`)}, icon: "Blocks", context: "agent", locations: ["explorer"], Component: () => null });
return () => {};
}`,
);
return directory;
}
async function openExplorerMenu(page: Page) {
const explorer = await ensureExplorerSidebar(page);
await explorer.getByRole("button", { name: "New tab", exact: true }).click();
const menu = page.getByTestId("workspace-new-tab-menu").filter({ visible: true });
await expect(menu).toBeVisible();
await expect(menu).toHaveCSS("opacity", "1");
return menu;
}
async function launchExplorerView(page: Page, name: string | RegExp) {
const menu = await openExplorerMenu(page);
await menu.getByRole("menuitem", { name, exact: true }).click();
await expect(menu).not.toBeVisible();
}
export async function withExplorerPanelPlugins(
page: Page,
run: (workspace: ExplorerPanelWorkspace) => Promise<void>,
) {
const client = await connectNewWorkspaceDaemonClient({ ownProjects: false });
const previousConfig = await client.getDaemonConfig();
const workspace = await seedWorkspace({ repoPrefix: "explorer-plugin-menu-" });
const first = await writePanelPlugin("explorer-review", "Review");
const second = await writePanelPlugin("explorer-other", "Other review");
try {
await client.patchDaemonConfig({ pluginsEnabled: true });
await client.installDirectoryPlugin(first);
await client.installDirectoryPlugin(second);
await page.setViewportSize({ width: 1280, height: 900 });
await gotoWorkspace(page, workspace.workspaceId);
await run(workspace);
} finally {
await client.removePlugin("explorer-review");
await client.removePlugin("explorer-other");
await client.patchDaemonConfig({
pluginsEnabled: previousConfig.config.pluginsEnabled ?? false,
});
await client.close();
await workspace.cleanup();
await rm(first, { recursive: true, force: true });
await rm(second, { recursive: true, force: true });
}
}
export async function expectWorkspacePanelsInExplorerLauncher(page: Page) {
const menu = await openExplorerMenu(page);
await expect(menu.getByRole("menuitem", { name: "Review", exact: true })).toBeVisible();
await expect(menu.getByRole("menuitem", { name: "Review agent", exact: true })).toHaveCount(0);
await expect(menu.getByRole("menuitem", { name: "Review main only", exact: true })).toHaveCount(
0,
);
await page.keyboard.press("Escape");
}
export async function openWorkspacePanelFromExplorerMenu(
page: Page,
workspace: ExplorerPanelWorkspace,
testInfo: TestInfo,
) {
const menu = await openExplorerMenu(page);
await testInfo.attach("explorer-panel-menu", {
body: await page.screenshot({ path: testInfo.outputPath("explorer-panel-menu.png") }),
contentType: "image/png",
});
await expect(menu.getByRole("menuitem", { name: "Review", exact: true })).toBeVisible();
await expect(menu.getByRole("menuitem", { name: "Other review", exact: true })).toBeVisible();
await expect(menu.getByRole("menuitem", { name: "Review summary", exact: true })).toBeVisible();
await expect(
menu.getByRole("menuitem", { name: "Other review summary", exact: true }),
).toBeVisible();
await menu.getByRole("menuitem", { name: "Review", exact: true }).click();
await expect(
page.getByText(`Review workspace ${workspace.workspaceId}`, { exact: true }),
).toBeVisible();
}
export async function expectIndependentExplorerPanelTabs(
page: Page,
workspace: ExplorerPanelWorkspace,
) {
await launchExplorerView(page, "Other review");
await expect(
page.getByText(`Other review workspace ${workspace.workspaceId}`, { exact: true }),
).toBeVisible();
await launchExplorerView(page, "Review summary");
const tabs = page.getByTestId("workspace-explorer-sidebar").getByTestId("workspace-tabs-row");
await expect(tabs.getByRole("button", { name: "Review", exact: true })).toBeVisible();
await expect(tabs.getByRole("button", { name: "Other review", exact: true })).toBeVisible();
await expect(tabs.getByRole("button", { name: "Review summary", exact: true })).toBeVisible();
await tabs
.getByRole("button", { name: "Review", exact: true })
.click({ button: "right", position: { x: 12, y: 13 } });
await page.getByRole("menuitem", { name: "Close", exact: true }).click();
await expect(tabs.getByRole("button", { name: "Review", exact: true })).toHaveCount(0);
await expect(tabs.getByRole("button", { name: "Other review", exact: true })).toBeVisible();
await expect(tabs.getByRole("button", { name: "Review summary", exact: true })).toBeVisible();
const files = tabs.getByRole("button", { name: "Browse workspace files", exact: true });
await expect(files).toHaveCount(1);
const menu = await openExplorerMenu(page);
await expect(menu.getByRole("menuitem", { name: /^Files/ })).toHaveCount(0);
await page.keyboard.press("Escape");
await files.click({ button: "right", position: { x: 12, y: 13 } });
await page.getByRole("menuitem", { name: "Close", exact: true }).click();
await expect(files).toHaveCount(0);
await launchExplorerView(page, /^Files/);
await expect(files).toHaveCount(1);
const restoredMenu = await openExplorerMenu(page);
await expect(restoredMenu.getByRole("menuitem", { name: /^Files/ })).toHaveCount(0);
await page.keyboard.press("Escape");
}
export async function expectExplorerPanelWithFocusedAgent(
page: Page,
workspace: ExplorerPanelWorkspace,
testInfo: TestInfo,
) {
const agent = await workspace.client.createAgent({
provider: "mock",
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: "Focused agent",
model: "ten-second-stream",
modeId: "load-test",
});
await page.goto(buildAgentRoute(workspace.workspaceId, agent.id));
await expect(
page.getByTestId(`workspace-tab-agent_${agent.id}`).filter({ visible: true }),
).toHaveAttribute("aria-selected", "true");
const menu = await openExplorerMenu(page);
await expect(menu.getByRole("menuitem", { name: "Review agent", exact: true })).toHaveCount(0);
await expect(menu.getByRole("menuitem", { name: "Other review agent", exact: true })).toHaveCount(
0,
);
await menu.getByRole("menuitem", { name: "Review", exact: true }).click();
await expect(
page.getByText(`Review workspace ${workspace.workspaceId}`, { exact: true }),
).toBeVisible();
await testInfo.attach("workspace-panel-with-focused-agent", {
body: await page.screenshot({
path: testInfo.outputPath("workspace-panel-with-focused-agent.png"),
}),
contentType: "image/png",
});
}