fix(ui): collapse chat side panel after final tab closes (#130102)

* fix(ui): collapse side panel after final tab closes

* fix(ui): preserve side panel owner state

* fix(ui): preserve projected panel closure

* fix(ui): align projected panel close contract
This commit is contained in:
Vyctor H. Brzezowski
2026-08-26 14:13:42 -03:00
committed by GitHub
parent c02ac37d12
commit 7afda00643
7 changed files with 39 additions and 6 deletions
+4 -2
View File
@@ -815,8 +815,10 @@ suite.define(() => {
.getByRole("button", { name: `Close ${label}`, exact: true })
.click();
}
await sidePanel(page).locator(".side-panel-empty--selector").waitFor();
await sidePanel(page).getByRole("button", { name: "Close", exact: true }).click();
await expect.poll(() => sidePanel(page).count()).toBe(0);
await page.reload();
await page.locator(".chat-group").first().waitFor();
await expect.poll(() => sidePanel(page).count()).toBe(0);
await page.locator(".chat-side-panel-toggle").click();
await sidePanel(page).locator(".side-panel-empty--selector").waitFor();
expect(await sidePanel(page).locator("wa-tab").count()).toBe(0);
@@ -407,6 +407,9 @@ suite.define(() => {
await waitForControlUiGatewayReady(page);
const panel = page.locator(".sidebar-region__right-runtime .side-panel");
await expect.poll(() => panel.count()).toBe(0);
await page.locator(".chat-side-panel-toggle").click();
await panel.locator(".side-panel-empty--selector").waitFor();
expect(await panel.locator("wa-tab").count()).toBe(0);
@@ -197,6 +197,25 @@ describe("chat pane sidebar layout", () => {
}
});
it("closes an empty Board projection and preserves visibility with a real tab", () => {
for (const open of [true, false]) {
const empty = resolveSidebarLayoutForBoard({
board: board("hidden", "chat"),
layout: { ...openSlot({ columns: [] }, "chat"), open },
paneWidth: 1_400,
});
expect(empty).toMatchObject({ columns: [], open: false });
const withDetail = resolveSidebarLayoutForBoard({
board: board("hidden", "chat"),
layout: { ...openSlot(openSlot({ columns: [] }, "chat"), "detail"), open },
paneWidth: 1_400,
});
expect(withDetail.columns[0]?.panels.map((panel) => panel.slot)).toEqual(["detail"]);
expect(withDetail.open).toBe(open);
}
});
it("keeps the detail tab when its transient content is no longer available", () => {
const layout = resolveSidebarLayoutForBoard({
board: board("hidden", "chat"),
+1 -1
View File
@@ -922,7 +922,7 @@ describe("chat pane keyboard shortcuts", () => {
expect(state.sidebarLayout.columns[0]?.panels.map((panel) => panel.slot)).toEqual(["terminal"]);
expect(press().defaultPrevented).toBe(true);
expect(state.sidebarLayout.columns).toEqual([]);
expect(state.sidebarLayout.open).toBe(true);
expect(state.sidebarLayout.open).toBe(false);
});
});
@@ -21,7 +21,6 @@ export type SidebarColumn = {
export type SidebarLayout = {
columns: SidebarColumn[];
dock?: SidebarDock;
/** The panel may stay open as a type picker after its last tab closes. */
open?: boolean;
expanded?: boolean;
};
+9 -2
View File
@@ -71,9 +71,16 @@ describe("sidebar layout", () => {
expect(chat?.slot).toBe("chat");
});
it("keeps the panel open as a type selector after its final tab closes", () => {
it("collapses the panel after its final tab closes", () => {
const closed = closeSlot(openSlot({ columns: [] }, "detail"), "detail");
expect(closed).toEqual({ columns: [], open: true });
expect(closed).toEqual({ columns: [], open: false });
});
it("does not reopen a minimized panel when another tab remains", () => {
const minimized = { ...openAll(), open: false };
const closed = closeSlot(minimized, "detail");
expect(closed.open).toBe(false);
expect(closed.columns[0]?.panels.map((panel) => panel.slot)).toEqual(["discussion", "chat"]);
});
it("minimizes and expands without discarding tabs", () => {
+3
View File
@@ -122,6 +122,9 @@ export function closeSlot(layout: SidebarLayout, slot: SidebarSlotId): SidebarLa
.find((entry) => entry.slot === slot);
if (panel) {
removePanel(next, panel.id);
if (next.columns.length === 0) {
next.open = false;
}
}
return next;
}