fix(web): reconcile dockview size before restoring layout (#1843) (#1845)

This commit is contained in:
Carlos Florêncio
2026-07-21 19:24:11 +01:00
committed by GitHub
parent 4b6d603f6d
commit d144c16fc2
3 changed files with 102 additions and 1 deletions
@@ -284,9 +284,40 @@ describe("sanitizeLayout - session panel handling", () => {
});
});
describe("tryRestoreLayout - phantom session panel filtering", () => {
describe("tryRestoreLayout - env restore", () => {
beforeEach(() => {
vi.restoreAllMocks();
document.body.replaceChildren();
});
it("reconciles a stale saved grid width with the live container before applying fixups", () => {
const layout = buildLayout({ centerSize: 650, rightSize: 900 });
layout.grid.width = 1900;
vi.spyOn(localStorage, "getEnvLayout").mockReturnValue(layout);
vi.spyOn(localStorage, "getEnvMaximizeState").mockReturnValue(null);
const container = document.createElement("div");
const dockview = document.createElement("div");
dockview.className = "dv-dockview";
container.appendChild(dockview);
document.body.appendChild(container);
Object.defineProperties(container, {
clientWidth: { value: 1260 },
clientHeight: { value: 700 },
});
const api = makeFakeRestoreApi();
Object.defineProperties(api, {
width: { value: 1900, writable: true },
height: { value: 600, writable: true },
});
expect(tryRestoreLayout(api, "env-stale-width", VALID_COMPONENTS)).toBe(true);
expect(api.fromJSON).toHaveBeenCalledOnce();
expect(api.layout).toHaveBeenCalledWith(1260, 700);
expect((api.fromJSON as ReturnType<typeof vi.fn>).mock.invocationCallOrder[0]).toBeLessThan(
(api.layout as ReturnType<typeof vi.fn>).mock.invocationCallOrder[0],
);
});
it("restores the (stripped) layout when every session in the saved env-layout is a phantom", () => {
@@ -185,6 +185,8 @@ function applyFixupsWithMaximize(
if (savedMax) {
applySavedMaximize(api, savedMax, savedRightWidth);
} else {
const { width, height } = measureDockviewContainer(api);
api.layout(width, height);
// Anchor the right column to its per-env saved width (see
// `captureRightTarget`) so a page reload restores the task's remembered
// width instead of resetting it to the default.
@@ -1,5 +1,6 @@
import { test, expect } from "../../fixtures/test-base";
import { computeRightMaxPx } from "../../../lib/state/layout-manager/caps";
import { TERMINAL_DEFAULT_ID } from "../../../lib/state/layout-manager/constants";
import {
WIDE_VIEWPORT,
openWideTask,
@@ -48,6 +49,73 @@ test.describe("Right pane resize — container-proportional cap", () => {
expectApproxWidth(after, before, 12);
});
test("initial restore reconciles a stale saved grid width without a manual resize", async ({
testPage,
apiClient,
seedData,
}) => {
await testPage.setViewportSize({ width: 2200, height: 900 });
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
"Right stale grid restore",
seedData.agentProfileId,
{
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
},
);
if (!task.session_id) throw new Error("created task did not return a session_id");
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
await session.waitForDockviewReady();
expect(await resizeColumnViaSplitview(testPage, "right", 900)).toBeGreaterThan(800);
const staleLayout = await testPage.evaluate((sessionId) => {
type StoreWindow = Window & {
__KANDEV_E2E_STORE__?: {
getState: () => { environmentIdBySessionId: Record<string, string> };
};
};
const store = (window as StoreWindow).__KANDEV_E2E_STORE__;
const envId = store?.getState().environmentIdBySessionId[sessionId];
if (!envId) throw new Error("environment id not hydrated");
const key = `kandev.dockview.env-layout-v3.${envId}`;
const raw = window.sessionStorage.getItem(key);
if (!raw) throw new Error("env layout was not persisted");
const layout = JSON.parse(raw);
const columns = layout.grid?.root?.data;
if (!Array.isArray(columns) || columns.length < 2) {
throw new Error("saved layout does not contain center and right columns");
}
layout.grid.width = 1900;
columns[0].size = 1000;
columns[columns.length - 1].size = 900;
return { key, json: JSON.stringify(layout) };
}, task.session_id);
await testPage.addInitScript(({ key, json }) => {
window.sessionStorage.setItem(key, json);
}, staleLayout);
await testPage.setViewportSize({ width: 1540, height: 900 });
await testPage.reload();
await session.waitForLoad();
await session.waitForDockviewReady();
const dockviewBox = await testPage.locator(".dv-dockview").boundingBox();
expect(dockviewBox).not.toBeNull();
const rightWidth = await getDockviewGroupWidth(testPage, "files");
const terminalWidth = await getDockviewGroupWidth(testPage, TERMINAL_DEFAULT_ID);
const centerWidth = await getDockviewGroupWidth(testPage, `session:${task.session_id}`);
const cap = computeRightMaxPx(dockviewBox?.width ?? 0);
expect(rightWidth).toBeLessThanOrEqual(cap + 10);
expectApproxWidth(terminalWidth, rightWidth, 10);
// Allow 2px of rendering tolerance around the 480px center comfort reserve.
expect(centerWidth).toBeGreaterThanOrEqual(478);
});
test("viewport shrink re-clamps an over-cap pinned width", async ({
testPage,
apiClient,