fix: prevent agent progress layout jitter

The previous popLayout stabilization kept margin-based spacing, so exiting rows temporarily added 6px to bounded Agent lists.

Use flex gaps and a geometry regression check so exited rows no longer change container height.
This commit is contained in:
2026-07-21 10:15:19 +08:00
parent 7fbd8a5618
commit 21791b9cee
2 changed files with 57 additions and 4 deletions
+54 -1
View File
@@ -4,6 +4,7 @@ import { expect, test } from "playwright/test";
const STREAM_CHUNK_SIZE = 18;
const STREAM_CHUNK_DELAY_MS = 20;
const PROGRESS_GEOMETRY_TOLERANCE_PX = 0.1;
let streamServer: Server;
let streamServerUrl: string;
@@ -267,19 +268,54 @@ test("stream completion and the plan card remain positionally stable", async ({
const liveProgress = page.getByRole("list", { name: "最近执行进度" });
await expect(liveProgress.locator("li")).toHaveCount(3);
await expect(liveProgress.getByText("正在分析运行上下文", { exact: true })).toBeVisible();
await expect(liveProgress).toHaveCSS("height", "156px");
await expect.poll(() => liveProgress.evaluate((list) => list.style.height)).toBe("auto");
await page.evaluate(() => {
const state = window as typeof window & {
__agentMinVisibleProgressRows?: number;
__agentProgressGeometry?: {
maxContainerHeight: number;
maxListHeight: number;
minContainerHeight: number;
minListHeight: number;
};
__agentProgressWindowSampleDone?: boolean;
};
const startedAt = performance.now();
state.__agentMinVisibleProgressRows = Number.POSITIVE_INFINITY;
state.__agentProgressGeometry = {
maxContainerHeight: Number.NEGATIVE_INFINITY,
maxListHeight: Number.NEGATIVE_INFINITY,
minContainerHeight: Number.POSITIVE_INFINITY,
minListHeight: Number.POSITIVE_INFINITY
};
state.__agentProgressWindowSampleDone = false;
const sample = () => {
const rows = document.querySelectorAll('ol[aria-label="最近执行进度"] > li').length;
const list = document.querySelector<HTMLOListElement>('ol[aria-label="最近执行进度"]');
const rows = list?.children.length ?? 0;
state.__agentMinVisibleProgressRows = Math.min(state.__agentMinVisibleProgressRows ?? rows, rows);
if (list && state.__agentProgressGeometry) {
const listHeight = list.getBoundingClientRect().height;
const containerHeight = list.parentElement?.getBoundingClientRect().height ?? 0;
state.__agentProgressGeometry.minListHeight = Math.min(
state.__agentProgressGeometry.minListHeight,
listHeight
);
state.__agentProgressGeometry.maxListHeight = Math.max(
state.__agentProgressGeometry.maxListHeight,
listHeight
);
state.__agentProgressGeometry.minContainerHeight = Math.min(
state.__agentProgressGeometry.minContainerHeight,
containerHeight
);
state.__agentProgressGeometry.maxContainerHeight = Math.max(
state.__agentProgressGeometry.maxContainerHeight,
containerHeight
);
}
if (performance.now() - startedAt < 400) {
requestAnimationFrame(sample);
} else {
@@ -303,6 +339,23 @@ test("stream completion and the plan card remain positionally stable", async ({
(window as typeof window & { __agentMinVisibleProgressRows?: number }).__agentMinVisibleProgressRows
)
).toBeGreaterThanOrEqual(3);
const progressGeometry = await page.evaluate(() =>
(window as typeof window & {
__agentProgressGeometry?: {
maxContainerHeight: number;
maxListHeight: number;
minContainerHeight: number;
minListHeight: number;
};
}).__agentProgressGeometry
);
expect(progressGeometry).toBeDefined();
expect(progressGeometry!.maxListHeight - progressGeometry!.minListHeight).toBeLessThanOrEqual(
PROGRESS_GEOMETRY_TOLERANCE_PX
);
expect(progressGeometry!.maxContainerHeight - progressGeometry!.minContainerHeight).toBeLessThanOrEqual(
PROGRESS_GEOMETRY_TOLERANCE_PX
);
const liveProgressRowHeights = await liveProgress.locator("li").evaluateAll((rows) =>
rows.map((row) => row.getBoundingClientRect().height)