fix(app): show Setup tabs only for failures (#3634)

* fix(app): only surface failed workspace setup

Running and successful setup snapshots were treated as reasons to seed a background Setup tab, adding noise to every workspace with setup commands. Seed only failed setup and classify that tab as main-workspace content so the failure stays visible without stealing focus.

* fix(app): surface failed setup once

Keep the failed-setup surfacing claim with the setup snapshot so closing its background tab remains respected after the workspace remounts. Explicitly prefer the main pane for a new failure tab without moving a user-placed existing tab.

* fix(app): keep setup tabs out of side panel

A preferred main-pane placement can fall back when users remove that pane. Keep that implicit fallback out of the focused Side panel when another workspace pane is available.
This commit is contained in:
Mohamed Boudra
2026-08-21 11:17:04 +02:00
committed by GitHub
parent bc02cfd8cd
commit 04624cb419
10 changed files with 271 additions and 61 deletions
@@ -1,7 +1,10 @@
import { test, expect } from "../support/fixtures";
import { createTempGitRepo } from "../support/helpers/workspace";
import {
closeSetupTab,
waitForWorkspaceTabsVisible,
expectFailedSetupTabSeededInMainPane,
expectSetupTabNotSeeded,
expectNoTerminalTabs,
clickFirstTerminalTab,
expectFirstTerminalTabContains,
@@ -19,6 +22,7 @@ import {
expectSetupPanel,
openHomeWithProject,
navigateToWorkspaceViaSidebar,
returnHomeFromWorkspace,
openWorkspaceScriptsMenu,
startWorkspaceScriptFromMenu,
closeWorkspaceScriptsMenu,
@@ -39,7 +43,7 @@ interface WorkspaceScriptStarter {
}
test.describe("Workspace setup streaming", () => {
test("opens the setup tab when a workspace is created from the sidebar", async ({ page }) => {
test("does not seed the setup tab while workspace setup is running", async ({ page }) => {
const client = await connectWorkspaceSetupClient();
const repo = await createTempGitRepo("setup-open-", {
paseoConfig: {
@@ -60,7 +64,7 @@ test.describe("Workspace setup streaming", () => {
await openHomeWithProject(page, repo.path);
await navigateToWorkspaceViaSidebar(page, workspace.id);
await expectSetupPanel(page);
await expectSetupTabNotSeeded(page, workspace.id);
} finally {
await client.close();
await repo.cleanup();
@@ -99,6 +103,8 @@ test.describe("Workspace setup streaming", () => {
await openHomeWithProject(page, repo.path);
await navigateToWorkspaceViaSidebar(page, workspace.id);
await expectSetupTabNotSeeded(page, workspace.id);
await expectSetupPanel(page);
await waitForWorkspaceTabsVisible(page);
await clickNewChat(page);
await expectComposerVisible(page, { timeout: 30_000 });
@@ -156,7 +162,7 @@ test.describe("Workspace setup streaming", () => {
}
});
test("streams a failed setup snapshot when setup fails", async () => {
test("seeds a failed setup tab once in the main pane", async ({ page }) => {
const client = await connectWorkspaceSetupClient();
const repo = await createTempGitRepo("setup-failure-", {
paseoConfig: {
@@ -173,7 +179,7 @@ test.describe("Workspace setup streaming", () => {
(payload) => payload.status === "failed" && payload.detail.log.includes("setup failed"),
);
await createWorkspaceThroughDaemon(client, {
const workspace = await createWorkspaceThroughDaemon(client, {
cwd: repo.path,
worktreeSlug: "workspace-setup-failure",
});
@@ -182,6 +188,16 @@ test.describe("Workspace setup streaming", () => {
expect(failedPayload.detail.log).toContain("starting setup");
expect(failedPayload.detail.log).toContain("setup failed");
expect(failedPayload.error).toMatch(/failed/i);
await openHomeWithProject(page, repo.path);
await navigateToWorkspaceViaSidebar(page, workspace.id);
await waitForWorkspaceTabsVisible(page);
await expectFailedSetupTabSeededInMainPane(page, workspace.id);
await closeSetupTab(page, workspace.id);
await returnHomeFromWorkspace(page);
await navigateToWorkspaceViaSidebar(page, workspace.id);
await expectSetupTabNotSeeded(page, workspace.id);
} finally {
await client.close();
await repo.cleanup();
@@ -276,6 +276,11 @@ export async function navigateToWorkspaceViaSidebar(
});
}
export async function returnHomeFromWorkspace(page: Page): Promise<void> {
await page.getByRole("button", { name: "Home" }).click();
await expect(page).not.toHaveURL(/\/workspace\//, { timeout: 30_000 });
}
export async function openWorkspaceScriptsMenu(page: Page): Promise<void> {
await page.getByTestId("workspace-scripts-button").click();
await expect(page.getByTestId("workspace-scripts-menu")).toBeVisible({ timeout: 10_000 });
@@ -13,6 +13,46 @@ export async function getWorkspaceTabTestIds(page: Page): Promise<string[]> {
return ids;
}
function setupTabTestId(workspaceId: string): string {
return `workspace-tab-setup_${workspaceId}`;
}
async function waitForSetupToReachWorkspace(page: Page): Promise<void> {
const actionsButton = page.getByTestId("workspace-header-menu-trigger");
await expect(actionsButton).toBeVisible({ timeout: 30_000 });
await actionsButton.click();
await expect(page.getByTestId("workspace-header-show-setup")).toBeVisible({ timeout: 30_000 });
await page.keyboard.press("Escape");
}
export async function expectSetupTabNotSeeded(page: Page, workspaceId: string): Promise<void> {
await waitForSetupToReachWorkspace(page);
const tab = page.getByTestId(setupTabTestId(workspaceId));
for (let attempt = 0; attempt < 5; attempt += 1) {
await expect(tab).toHaveCount(0);
await page.waitForTimeout(100);
}
}
export async function expectFailedSetupTabSeededInMainPane(
page: Page,
workspaceId: string,
): Promise<void> {
const tabId = setupTabTestId(workspaceId);
await expect(page.getByTestId(tabId).filter({ visible: true }).first()).toBeVisible({
timeout: 30_000,
});
const panel = await ensureSidePanel(page);
await expect(panel.getByTestId(tabId)).toHaveCount(0);
}
export async function closeSetupTab(page: Page, workspaceId: string): Promise<void> {
const tabId = setupTabTestId(workspaceId);
await page.getByTestId(tabId).filter({ visible: true }).first().click({ button: "right" });
await page.getByTestId(`workspace-tab-context-setup_${workspaceId}-close`).click();
await expect(page.getByTestId(tabId)).toHaveCount(0);
}
function visibleTestId(page: Page, testId: string) {
return page.getByTestId(testId).filter({ visible: true });
}