From e75d6980223f14bfb26764898f7006c98816a693 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 04:38:09 +0000 Subject: [PATCH 1/3] test(agent-pane): cover pushed Preview tab-row tools Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- .../acpmux/summary/SummaryCard.gallery.tsx | 44 ++++++++++-- webviews/test/summary-header-surface.test.ts | 72 ++++++++++++++++++- 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx b/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx index 744a804123dd..80755b510383 100644 --- a/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx +++ b/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx @@ -23,7 +23,7 @@ import { realWorkflowFixture } from "../realWorkflowFixture"; type Props = Pick< ComponentProps, "rows" | "background" | "sessions" | "onAttach" | "workflow" | "workflowActions" -> & { tabRowHeight?: number; preview?: boolean }; +> & { tabRowHeight?: number; preview?: boolean; inspectorPush?: boolean }; const none = () => undefined; /// The inline column, shown and open. @@ -38,12 +38,26 @@ const card: SummaryCardControl = { fold: none, }; -function GallerySummaryCard({ tabRowHeight, preview, ...props }: Props) { +function GallerySummaryCard({ tabRowHeight, preview, inspectorPush, ...props }: Props) { return (
{tabRowHeight && ( @@ -57,12 +71,26 @@ function GallerySummaryCard({ tabRowHeight, preview, ...props }: Props) {
)}
+ {inspectorPush && ( + + )} {preview ? ( - ) : ( + ) : inspectorPush ? null : ( ({ note: "The Preview sibling obeys the same native tab-row clearance, both beside the chat and covering a narrow pane.", props: { tabRowHeight: 32, preview: true, rows: [] }, }, + "preview-pushed-inspector-tab-row": { + note: "Preview and a pushed Session Info card share the pane; the native tools span both side-panel terms.", + props: { tabRowHeight: 32, preview: true, inspectorPush: true, rows: [] }, + }, + "inspector-pushed-tab-row": { + note: "A pushed Session Info card keeps the native tools at the pane edge.", + props: { tabRowHeight: 32, inspectorPush: true, rows: [] }, + }, "shared-tab-row": { note: "The inline Summary column beside the chat, under a 32px native tab row. Two commands and a failed subagent keep long counts visible with a right ellipsis, outside the native header holes.", props: { diff --git a/webviews/test/summary-header-surface.test.ts b/webviews/test/summary-header-surface.test.ts index f0340de878fb..6cc03337a47e 100644 --- a/webviews/test/summary-header-surface.test.ts +++ b/webviews/test/summary-header-surface.test.ts @@ -120,7 +120,10 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { ); await page.waitForFunction(() => document.documentElement.dataset.galleryReady === "1"); await page.locator(".acpmux-shell").evaluate((node, pixels) => { - (node as HTMLElement).style.setProperty("--acpmux-tab-row-height", `${pixels}px`); + const shell = node as HTMLElement; + shell.style.setProperty("--acpmux-tab-row-height", `${pixels}px`); + if (shell.dataset.inspectorChatPlacement === "push") + shell.style.setProperty("--acpmux-inspector-top", `${pixels}px`); }, height); await page.locator(".acpmux-artifact").waitFor(); if (process.env.CMUX_SUMMARY_CAPTURE_DIR) { @@ -157,4 +160,71 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { } } } + for (const variant of ["preview-tab-row", "preview-pushed-inspector-tab-row", "inspector-pushed-tab-row"] as const) { + for (const height of [28, 40]) { + for (const theme of ["Apple System Colors", "Apple System Colors Light"]) { + test(`${variant} keeps tools at the shell edge at ${height}px (${theme})`, async () => { + const page = await browser.newPage({ + viewport: { width: 1000, height: 560 }, + reducedMotion: "reduce", + }); + try { + const port = (server.httpServer!.address() as { port: number }).port; + await page.goto(`http://127.0.0.1:${port}${galleryFrame("agent-pane.summary-card", { variant, theme })}`); + await page.waitForFunction(() => document.documentElement.dataset.galleryReady === "1"); + await page.locator(".acpmux-shell").evaluate((node, pixels) => { + const shell = node as HTMLElement; + shell.style.setProperty("--acpmux-tab-row-height", `${pixels}px`); + if (shell.dataset.inspectorChatPlacement === "push") + shell.style.setProperty("--acpmux-inspector-top", `${pixels}px`); + }, height); + if ( + process.env.CMUX_SUMMARY_CAPTURE_DIR && + variant === "preview-pushed-inspector-tab-row" && + height === 28 + ) { + await mkdir(process.env.CMUX_SUMMARY_CAPTURE_DIR, { recursive: true }); + await page.screenshot({ + path: path.join( + process.env.CMUX_SUMMARY_CAPTURE_DIR, + `preview-pushed-inspector-${theme.endsWith("Light") ? "light" : "dark"}.png`, + ), + }); + } + const actual = await page.evaluate(() => { + const shell = document.querySelector(".acpmux-shell")!; + const shellRect = shell.getBoundingClientRect(); + const tools = document.querySelector(".acpmux-header-tools")!.getBoundingClientRect(); + const panels = [ + ...document.querySelectorAll( + ".acpmux-artifact, .acpmux-chat-inspector[data-placement=push]", + ), + ].map((panel) => { + const rect = panel.getBoundingClientRect(); + return { + top: rect.top - shellRect.top, + intersectsTools: + rect.left < tools.right && + rect.right > tools.left && + rect.top < tools.bottom && + rect.bottom > tools.top, + }; + }); + return { + toolsWidth: tools.width, + toolsRight: tools.right, + shellRight: shellRect.right, + panels, + }; + }); + expect(actual.toolsWidth).toBeGreaterThan(0); + expect(actual.toolsRight).toBe(actual.shellRight); + expect(actual.panels.every((panel) => panel.top >= height && !panel.intersectsTools)).toBe(true); + } finally { + await page.close(); + } + }); + } + } + } }); From fc457e21f39887fc6836c3ec2be2bf5950814c6f Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 04:39:43 +0000 Subject: [PATCH 2/3] fix(agent-pane): sum pushed tab-row panel widths Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- .../acpmux/conversation/conversation.css | 11 +++++++---- .../acpmux/inspector/inspector.css | 19 +++++++++++++++---- .../agent-session/acpmux/summary/summary.css | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/webviews/src/agent-session/acpmux/conversation/conversation.css b/webviews/src/agent-session/acpmux/conversation/conversation.css index e4ed9fbc4f62..55c9b3f5ef0d 100644 --- a/webviews/src/agent-session/acpmux/conversation/conversation.css +++ b/webviews/src/agent-session/acpmux/conversation/conversation.css @@ -503,6 +503,9 @@ color: var(--agent-text); font-family: var(--cv-font); } +.acpmux-shell { + --acpmux-artifact-push: 0px; +} /* A shell sibling must leave the offered native tab row clear, including when the narrow preview covers the chat. Its background belongs below the row, never in its tools hole. */ .acpmux-shell[data-tab-row] > .acpmux-artifact { @@ -511,8 +514,8 @@ } /* The header lives in the chat column while Preview is a shell sibling. Keep its tools at the pane edge, across the Preview column, just as the inline Summary does. */ -.acpmux-shell[data-tab-row]:has(> .acpmux-artifact) .acpmux-header[data-tab-row] { - width: calc(100% + var(--acpmux-artifact-width, clamp(320px, 46vw, 760px))); +.acpmux-shell[data-tab-row]:has(> .acpmux-artifact) { + --acpmux-artifact-push: var(--acpmux-artifact-width, clamp(320px, 46vw, 760px)); } @media (max-width: 720px) { .acpmux-artifact { @@ -521,8 +524,8 @@ z-index: var(--layer-overlay); box-shadow: none; } - .acpmux-shell[data-tab-row]:has(> .acpmux-artifact) .acpmux-header[data-tab-row] { - width: 100%; + .acpmux-shell[data-tab-row]:has(> .acpmux-artifact) { + --acpmux-artifact-push: 0px; } } .acpmux-artifact-bar { diff --git a/webviews/src/agent-session/acpmux/inspector/inspector.css b/webviews/src/agent-session/acpmux/inspector/inspector.css index b16196394d81..58e96a6a0a18 100644 --- a/webviews/src/agent-session/acpmux/inspector/inspector.css +++ b/webviews/src/agent-session/acpmux/inspector/inspector.css @@ -51,10 +51,21 @@ align-self: flex-start; margin: var(--acpmux-inspector-top, 0px) var(--acpmux-inspector-inset) 0 0; } -/* The native tab strip's reserved tool area stays at the pane's trailing edge. Push always - narrows the conversation below it; the header keeps the room measured before that column. */ -.acpmux-shell[data-inspector-chat-placement="push"] .acpmux-header { - width: var(--acpmux-header-room, 100%); +/* Every shell-side panel contributes one term to the native tab row's width. The Inspector's + room is measured before the artifact column by ChatInspector.tsx; subtracting the main column's + 100% leaves the pushed Inspector width and its inset. Keeping each panel as a zero-valued shell + variable makes the shared rule additive as new side panels arrive. */ +.acpmux-shell { + --acpmux-inspector-push: 0px; +} +.acpmux-shell[data-inspector-chat-placement="push"] { + --acpmux-inspector-push: calc(var(--acpmux-header-room, 100%) - 100%); +} +.acpmux-shell[data-tab-row] .acpmux-header[data-tab-row] { + width: calc( + 100% + var(--acpmux-summary-push, 0px) + var(--acpmux-inspector-push, 0px) + + var(--acpmux-artifact-push, 0px) + ); } /* Floating: the same card over the gutter. With the side panel open beside the chat, its gutter ends at that panel's edge (--acpmux-inspector-side, its width), never over the document. The inset is its diff --git a/webviews/src/agent-session/acpmux/summary/summary.css b/webviews/src/agent-session/acpmux/summary/summary.css index 9b8204806b26..f96485b168da 100644 --- a/webviews/src/agent-session/acpmux/summary/summary.css +++ b/webviews/src/agent-session/acpmux/summary/summary.css @@ -9,9 +9,9 @@ .acpmux-main[data-summary=inline]{min-width:0} /* The inline Summary is beside the chat, not inside its stage: it must leave the native shared tab row empty too. The same column width keeps the tools at the pane edge while the chat narrows. */ -.acpmux-shell{--acpmux-summary-column-width:300px} +.acpmux-shell{--acpmux-summary-column-width:300px;--acpmux-summary-push:0px} .acpmux-shell[data-tab-row]>.acpmux-summary-card[data-placement=inline]{margin-top:var(--acpmux-tab-row-height);max-height:calc(100% - var(--acpmux-tab-row-height))} -.acpmux-shell[data-tab-row]:has(>.acpmux-summary-card[data-placement=inline]) .acpmux-header[data-tab-row]{width:calc(100% + var(--acpmux-summary-column-width))} +.acpmux-shell[data-tab-row]:has(>.acpmux-summary-card[data-placement=inline]){--acpmux-summary-push:var(--acpmux-summary-column-width)} /* Under the tab row when the header's tools sit in it (header/tabRow.ts). */ .acpmux-header[data-tab-row]~.acpmux-summary-card{top:calc(var(--acpmux-tab-row-height) + 8px)} .acpmux-reviewing>.acpmux-summary-card,.acpmux-inspecting>.acpmux-summary-card,.acpmux-stage:has(.acpmux-find)>.acpmux-summary-card{visibility:hidden} From ad7deca11e11cf87740a1fee4c0c64b35e863d4a Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 05:17:44 +0000 Subject: [PATCH 3/3] fix(agent-pane): preserve pushed header width without tab row Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- .../acpmux/inspector/inspector.css | 2 +- .../acpmux/summary/SummaryCard.gallery.tsx | 17 ++++++++--- webviews/test/summary-header-surface.test.ts | 30 ++++++++++++++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/webviews/src/agent-session/acpmux/inspector/inspector.css b/webviews/src/agent-session/acpmux/inspector/inspector.css index 58e96a6a0a18..f0fc1ef7e0b7 100644 --- a/webviews/src/agent-session/acpmux/inspector/inspector.css +++ b/webviews/src/agent-session/acpmux/inspector/inspector.css @@ -61,7 +61,7 @@ .acpmux-shell[data-inspector-chat-placement="push"] { --acpmux-inspector-push: calc(var(--acpmux-header-room, 100%) - 100%); } -.acpmux-shell[data-tab-row] .acpmux-header[data-tab-row] { +.acpmux-shell .acpmux-header { width: calc( 100% + var(--acpmux-summary-push, 0px) + var(--acpmux-inspector-push, 0px) + var(--acpmux-artifact-push, 0px) diff --git a/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx b/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx index 80755b510383..5865f11de214 100644 --- a/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx +++ b/webviews/src/agent-session/acpmux/summary/SummaryCard.gallery.tsx @@ -50,7 +50,7 @@ function GallerySummaryCard({ tabRowHeight, preview, inspectorPush, ...props }: "--acpmux-tab-row-height": `${tabRowHeight ?? 0}px`, ...(inspectorPush ? { - "--acpmux-inspector-top": `${tabRowHeight ?? 0}px`, + "--acpmux-inspector-top": tabRowHeight ? `${tabRowHeight}px` : "28px", // ChatInspector.tsx writes this as a measured shell px value. The fixture is 1000px // wide, with Preview's 46vw artifact resolving to 460px at that width. "--acpmux-header-room": preview ? "540px" : "1000px", @@ -60,9 +60,9 @@ function GallerySummaryCard({ tabRowHeight, preview, inspectorPush, ...props }: } >
- {tabRowHeight && ( + {tabRowHeight !== undefined && (
-
+
@@ -77,7 +77,12 @@ function GallerySummaryCard({ tabRowHeight, preview, inspectorPush, ...props }: data-placement="push" data-presence="shown" aria-label="Session Info" - style={{ width: "220px" }} + style={ + { + width: "220px", + "--acpmux-inspector-top": tabRowHeight ? `${tabRowHeight}px` : "28px", + } as CSSProperties + } >
Session Info
@@ -203,6 +208,10 @@ export default componentEntry({ note: "A pushed Session Info card keeps the native tools at the pane edge.", props: { tabRowHeight: 32, inspectorPush: true, rows: [] }, }, + "inspector-pushed-no-tab-row": { + note: "A pushed Session Info card keeps ordinary header tools in place when no native row is active.", + props: { tabRowHeight: 0, inspectorPush: true, rows: [] }, + }, "shared-tab-row": { note: "The inline Summary column beside the chat, under a 32px native tab row. Two commands and a failed subagent keep long counts visible with a right ellipsis, outside the native header holes.", props: { diff --git a/webviews/test/summary-header-surface.test.ts b/webviews/test/summary-header-surface.test.ts index 6cc03337a47e..84ec192711d9 100644 --- a/webviews/test/summary-header-surface.test.ts +++ b/webviews/test/summary-header-surface.test.ts @@ -123,7 +123,7 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { const shell = node as HTMLElement; shell.style.setProperty("--acpmux-tab-row-height", `${pixels}px`); if (shell.dataset.inspectorChatPlacement === "push") - shell.style.setProperty("--acpmux-inspector-top", `${pixels}px`); + shell.style.setProperty("--acpmux-inspector-top", pixels > 0 ? `${pixels}px` : "28px"); }, height); await page.locator(".acpmux-artifact").waitFor(); if (process.env.CMUX_SUMMARY_CAPTURE_DIR) { @@ -160,8 +160,13 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { } } } - for (const variant of ["preview-tab-row", "preview-pushed-inspector-tab-row", "inspector-pushed-tab-row"] as const) { - for (const height of [28, 40]) { + for (const variant of [ + "preview-tab-row", + "preview-pushed-inspector-tab-row", + "inspector-pushed-tab-row", + "inspector-pushed-no-tab-row", + ] as const) { + for (const height of variant === "inspector-pushed-no-tab-row" ? [0] : [28, 40]) { for (const theme of ["Apple System Colors", "Apple System Colors Light"]) { test(`${variant} keeps tools at the shell edge at ${height}px (${theme})`, async () => { const page = await browser.newPage({ @@ -178,6 +183,10 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { if (shell.dataset.inspectorChatPlacement === "push") shell.style.setProperty("--acpmux-inspector-top", `${pixels}px`); }, height); + await page.evaluate((pixels) => { + const inspector = document.querySelector(".acpmux-chat-inspector[data-placement=push]"); + inspector?.style.setProperty("--acpmux-inspector-top", pixels > 0 ? `${pixels}px` : "28px"); + }, height); if ( process.env.CMUX_SUMMARY_CAPTURE_DIR && variant === "preview-pushed-inspector-tab-row" && @@ -218,7 +227,20 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { }; }); expect(actual.toolsWidth).toBeGreaterThan(0); - expect(actual.toolsRight).toBe(actual.shellRight); + if (variant === "inspector-pushed-no-tab-row") { + // Ordinary headers keep their existing 8px trailing inset; the regression is the + // Inspector narrowing the header, which would move this edge left by the panel width. + expect(actual.toolsRight).toBe(actual.shellRight - 8); + await page.locator(".acpmux-header-tools button").evaluate((button) => { + button.addEventListener("click", () => button.setAttribute("data-test-clicked", "true"), { + once: true, + }); + }); + await page.locator(".acpmux-header-tools button").click(); + expect(await page.locator(".acpmux-header-tools button").getAttribute("data-test-clicked")).toBe("true"); + } else { + expect(actual.toolsRight).toBe(actual.shellRight); + } expect(actual.panels.every((panel) => panel.top >= height && !panel.intersectsTools)).toBe(true); } finally { await page.close();