Fix composer toolbar collapse flicker on tab switch (#2937)
Retained tabs stay mounted under display:none while inactive, which reports a zero-width onLayout. ControlledAgentControls fed that straight into its density resolver, so a hidden tab's toolbar collapsed to its tightest density and painted one frame collapsed before re-expanding when the tab became visible again. Extract the zero-width guard that useContainerWidthBelow already had into a shared readMeasuredWidth helper, and use it in the composer toolbar's own layout handler so a display:none measurement can never drive its density. Also bump the chat outline rail's MIN_PANEL_WIDTH breakpoint (818 -> 918) so the rail disappears earlier under a narrower panel, and widen the chat-outline e2e spec's viewport so its panel keeps healthy margin over the new threshold.
This commit is contained in:
@@ -34,7 +34,10 @@ import {
|
||||
type LongTimelineAgent,
|
||||
} from "../support/helpers/timeline-pagination";
|
||||
|
||||
const WIDE_VIEWPORT = { width: 1280, height: 900 };
|
||||
// Wide enough that the timeline panel clears the rail's MIN_PANEL_WIDTH with room
|
||||
// to spare. At 1280 the panel measures 960, which sits too close to the threshold
|
||||
// for chrome-width changes elsewhere to stay out of these tests.
|
||||
const WIDE_VIEWPORT = { width: 1440, height: 900 };
|
||||
const LOADED_TURNS = 16;
|
||||
|
||||
test.describe("desktop chat outline", () => {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { test, expect } from "../support/fixtures";
|
||||
import { openWorkspaceWithAgents } from "../support/helpers/archive-tab";
|
||||
import {
|
||||
expectNoCollapsedComposerToolbarFrame,
|
||||
recordComposerToolbarFrames,
|
||||
} from "../support/helpers/composer-control-density";
|
||||
import { clickNewChat, gotoWorkspace } from "../support/helpers/launcher";
|
||||
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
||||
|
||||
const SETTLE_MS = 1_000;
|
||||
|
||||
async function seedSettledMockAgent(workspace: SeededWorkspace, title: string) {
|
||||
const agent = await workspace.client.createAgent({
|
||||
provider: "mock",
|
||||
model: "ten-second-stream",
|
||||
modeId: "load-test",
|
||||
cwd: workspace.repoPath,
|
||||
workspaceId: workspace.workspaceId,
|
||||
title,
|
||||
});
|
||||
await workspace.client.waitForAgentUpsert(
|
||||
agent.id,
|
||||
(snapshot) => snapshot.status === "idle",
|
||||
30_000,
|
||||
);
|
||||
return { id: agent.id, title, cwd: workspace.repoPath, workspaceId: workspace.workspaceId };
|
||||
}
|
||||
|
||||
function visibleAgentTab(page: import("@playwright/test").Page, agentId: string) {
|
||||
return page.getByTestId(`workspace-tab-agent_${agentId}`).filter({ visible: true }).first();
|
||||
}
|
||||
|
||||
test.describe("Composer control density across tab switches", () => {
|
||||
test.describe.configure({ timeout: 180_000 });
|
||||
|
||||
test("switching between agent tabs never paints a collapsed composer toolbar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "composer-density-agents-" });
|
||||
try {
|
||||
const first = await seedSettledMockAgent(workspace, "First chat");
|
||||
const second = await seedSettledMockAgent(workspace, "Second chat");
|
||||
await openWorkspaceWithAgents(page, [first, second]);
|
||||
|
||||
await recordComposerToolbarFrames(page);
|
||||
await visibleAgentTab(page, first.id).click();
|
||||
await page.waitForTimeout(SETTLE_MS);
|
||||
await visibleAgentTab(page, second.id).click();
|
||||
await page.waitForTimeout(SETTLE_MS);
|
||||
|
||||
await expectNoCollapsedComposerToolbarFrame(page);
|
||||
} finally {
|
||||
await workspace.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("switching between draft tabs never paints a collapsed composer toolbar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "composer-density-drafts-" });
|
||||
try {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
"@paseo:create-agent-preferences",
|
||||
JSON.stringify({
|
||||
provider: "mock",
|
||||
providerPreferences: { mock: { mode: "load-test" } },
|
||||
}),
|
||||
);
|
||||
});
|
||||
await gotoWorkspace(page, workspace.workspaceId);
|
||||
await clickNewChat(page);
|
||||
|
||||
const draftTabs = page
|
||||
.locator('[data-testid^="workspace-tab-draft"]')
|
||||
.filter({ visible: true });
|
||||
await expect(draftTabs).toHaveCount(2, { timeout: 30_000 });
|
||||
await expect(
|
||||
page.locator('[data-testid="mode-control"]').filter({ visible: true }).first(),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await recordComposerToolbarFrames(page);
|
||||
await draftTabs.nth(0).click();
|
||||
await page.waitForTimeout(SETTLE_MS);
|
||||
await draftTabs.nth(1).click();
|
||||
await page.waitForTimeout(SETTLE_MS);
|
||||
|
||||
await expectNoCollapsedComposerToolbarFrame(page);
|
||||
} finally {
|
||||
await workspace.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user