Make the workspace right pane a first-class tab host (#3287)
* feat(app): add persistent workspace side panes Keep the common review and file-browsing workflow beside the active agent without tearing down pane contents. Workspace tabs now share one compact chip language, assistant file links route into the retained right pane, and diff/file trees share stable geometry. * fix(app): localize pull request empty state * test(app): update explorer pane coverage * fix(app): preserve explorer pane workflows * test(app): make explorer tab selection idempotent * fix(app): persist workspace review pins * fix(app): preserve sidebar scrim drag hook * fix(app): restore companion pane interactions * test(app): follow selected sidebar surface
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import { expect, type Locator, type Page } from "@playwright/test";
|
||||
|
||||
export async function getWorkspaceTabTestIds(page: Page): Promise<string[]> {
|
||||
const tabs = page.locator('[data-testid^="workspace-tab-"]');
|
||||
@@ -17,15 +17,89 @@ function visibleTestId(page: Page, testId: string) {
|
||||
return page.getByTestId(testId).filter({ visible: true });
|
||||
}
|
||||
|
||||
function explorerPaneTabRow(page: Page) {
|
||||
return visibleTestId(page, "workspace-tabs-row")
|
||||
.filter({ has: page.getByTestId("workspace-tab-working_diff") })
|
||||
.first();
|
||||
}
|
||||
|
||||
async function selectWorkspaceTab(tab: Locator): Promise<void> {
|
||||
if ((await tab.getAttribute("aria-selected")) !== "true") {
|
||||
// The close action overlays the chip's trailing edge on hover. Click the
|
||||
// leading icon area so Playwright does not target that separate control.
|
||||
await tab.click({ position: { x: 12, y: 13 } });
|
||||
}
|
||||
await expect(tab).toHaveAttribute("aria-selected", "true");
|
||||
}
|
||||
|
||||
export async function ensureExplorerPane(page: Page) {
|
||||
const toggle = page.getByTestId("workspace-explorer-toggle").first();
|
||||
await expect(toggle).toBeVisible({ timeout: 30_000 });
|
||||
const tabRow = explorerPaneTabRow(page);
|
||||
if ((await tabRow.count()) === 0) {
|
||||
await toggle.click();
|
||||
}
|
||||
await expect(tabRow).toBeVisible({ timeout: 30_000 });
|
||||
return tabRow;
|
||||
}
|
||||
|
||||
export async function openChangesPanel(page: Page): Promise<void> {
|
||||
const tabRow = await ensureExplorerPane(page);
|
||||
await selectWorkspaceTab(tabRow.getByTestId("workspace-tab-working_diff"));
|
||||
await expect(visibleTestId(page, "working-diff-panel").first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openFilesPanel(page: Page): Promise<void> {
|
||||
const tabRow = await ensureExplorerPane(page);
|
||||
const filesTab = tabRow.getByTestId("workspace-tab-files");
|
||||
if ((await filesTab.count()) === 0) {
|
||||
await tabRow.getByTestId("workspace-new-tab-menu-trigger").click();
|
||||
await visibleTestId(page, "workspace-new-tab-menu-files").first().click();
|
||||
}
|
||||
await selectWorkspaceTab(filesTab);
|
||||
await expect(visibleTestId(page, "file-explorer-tree-scroll").first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openPullRequestPanel(page: Page): Promise<void> {
|
||||
const existingTab = visibleTestId(page, "workspace-tab-pull_request").first();
|
||||
if ((await existingTab.count()) > 0) {
|
||||
await selectWorkspaceTab(existingTab);
|
||||
await expect(visibleTestId(page, "pr-pane").first()).toBeVisible({ timeout: 15_000 });
|
||||
return;
|
||||
}
|
||||
const tabRow = await ensureExplorerPane(page);
|
||||
const pullRequestTab = tabRow.getByTestId("workspace-tab-pull_request");
|
||||
if ((await pullRequestTab.count()) === 0) {
|
||||
await tabRow.getByTestId("workspace-new-tab-menu-trigger").click();
|
||||
await visibleTestId(page, "workspace-new-tab-menu-pull-request").first().click();
|
||||
}
|
||||
await selectWorkspaceTab(pullRequestTab);
|
||||
await expect(visibleTestId(page, "pr-pane").first()).toBeVisible({ timeout: 15_000 });
|
||||
}
|
||||
|
||||
export async function waitForWorkspaceTabsVisible(page: Page): Promise<void> {
|
||||
await expect(visibleTestId(page, "workspace-tabs-row").first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(visibleTestId(page, "workspace-new-agent-tab-inline").first()).toBeVisible({
|
||||
await expect(visibleTestId(page, "workspace-new-tab-menu-trigger").first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** Open the `+` menu in the tab row and pick "New agent". */
|
||||
export async function createAgentTabFromMenu(page: Page): Promise<void> {
|
||||
const trigger = visibleTestId(page, "workspace-new-tab-menu-trigger").first();
|
||||
await expect(trigger).toBeVisible({ timeout: 10_000 });
|
||||
await trigger.click();
|
||||
const item = visibleTestId(page, "workspace-new-tab-menu-agent").first();
|
||||
await expect(item).toBeVisible({ timeout: 10_000 });
|
||||
await item.click();
|
||||
}
|
||||
|
||||
export async function getVisibleWorkspaceAgentTabIds(page: Page): Promise<string[]> {
|
||||
const tabs = page.locator('[data-testid^="workspace-tab-agent_"]').filter({ visible: true });
|
||||
const count = await tabs.count();
|
||||
|
||||
Reference in New Issue
Block a user