From ad78dfd55aa64f850ee649be82c99a90ccf79db3 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 02:38:30 +0000 Subject: [PATCH 1/4] test(preview): keep tab-row tools visible beside Preview Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- webviews/test/summary-header-surface.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/webviews/test/summary-header-surface.test.ts b/webviews/test/summary-header-surface.test.ts index 82cec0c7bf34..5bfbfd52a94e 100644 --- a/webviews/test/summary-header-surface.test.ts +++ b/webviews/test/summary-header-surface.test.ts @@ -115,10 +115,19 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { const actual = await page.evaluate(() => { const shell = document.querySelector(".acpmux-shell")!.getBoundingClientRect(); const panel = document.querySelector(".acpmux-artifact")!.getBoundingClientRect(); - return { top: panel.top - shell.top, bottom: panel.bottom - shell.bottom }; + const tools = document.querySelector(".acpmux-header-tools")!.getBoundingClientRect(); + return { + top: panel.top - shell.top, + bottom: panel.bottom - shell.bottom, + toolsWidth: tools.width, + toolsRight: tools.right, + shellRight: shell.right, + }; }); expect(actual.top).toBe(height); expect(actual.bottom).toBeLessThanOrEqual(0); + expect(actual.toolsWidth).toBeGreaterThan(0); + expect(actual.toolsRight).toBe(actual.shellRight); } finally { await page.close(); } From 596b9dbd79ee95285c96209af5e3efd2061454af Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 02:39:09 +0000 Subject: [PATCH 2/4] fix(agent-pane): keep Preview tab-row tools at pane edge Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- .../agent-session/acpmux/conversation/conversation.css | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/webviews/src/agent-session/acpmux/conversation/conversation.css b/webviews/src/agent-session/acpmux/conversation/conversation.css index 3a7734dd40a1..f5c4b5cce8a6 100644 --- a/webviews/src/agent-session/acpmux/conversation/conversation.css +++ b/webviews/src/agent-session/acpmux/conversation/conversation.css @@ -474,7 +474,7 @@ pane's right edge, its own width (data-enter, ui/presence.ts). */ .acpmux-artifact { --ui-enter-distance: 100%; - flex: 0 0 clamp(320px, 46%, 760px); + flex: 0 0 var(--acpmux-artifact-width, clamp(320px, 46vw, 760px)); min-width: 0; display: flex; flex-direction: column; @@ -489,6 +489,11 @@ margin-top: var(--acpmux-tab-row-height); max-height: calc(100% - var(--acpmux-tab-row-height)); } +/* 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))); +} @media (max-width: 720px) { .acpmux-artifact { position: absolute; @@ -496,6 +501,9 @@ z-index: var(--layer-overlay); box-shadow: none; } + .acpmux-shell[data-tab-row]:has(> .acpmux-artifact) .acpmux-header[data-tab-row] { + width: 100%; + } } .acpmux-artifact-bar { flex: 0 0 44px; From 388535c25eaf27e945b7afa9505c9d742c6cbb33 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 02:45:42 +0000 Subject: [PATCH 3/4] test(preview): capture light and dark tab-row states Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- webviews/test/summary-header-surface.test.ts | 148 +++++++++++++------ 1 file changed, 104 insertions(+), 44 deletions(-) diff --git a/webviews/test/summary-header-surface.test.ts b/webviews/test/summary-header-surface.test.ts index 5bfbfd52a94e..23a2cacaf0de 100644 --- a/webviews/test/summary-header-surface.test.ts +++ b/webviews/test/summary-header-surface.test.ts @@ -13,13 +13,19 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { let browser: Browser; beforeAll(async () => { server = await createServer({ - configFile: path.resolve(import.meta.dir, "../vite.config.gallery-dev.ts"), + configFile: path.resolve( + import.meta.dir, + "../vite.config.gallery-dev.ts", + ), server: { port: 0, host: "127.0.0.1", strictPort: false }, logLevel: "error", }); await server.listen(); browser = await chromium.launch({ headless: true }); - await warmGalleryFrame(server, "agent-pane.summary-card", { browser, params: { variant: "shared-tab-row" } }); + await warmGalleryFrame(server, "agent-pane.summary-card", { + browser, + params: { variant: "shared-tab-row" }, + }); }); afterAll(async () => { await browser?.close(); @@ -28,18 +34,27 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { for (const height of [28, 40]) { for (const theme of ["Apple System Colors", "Apple System Colors Light"]) { test(`inline Summary leaves the ${height}px native tools row clear (${theme})`, async () => { - const page = await browser.newPage({ viewport: { width: 720, height: 560 } }); + const page = await browser.newPage({ + viewport: { width: 720, height: 560 }, + }); 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: "shared-tab-row", theme })}`, ); - await page.waitForFunction(() => document.documentElement.dataset.galleryReady === "1"); + 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`); + (node as HTMLElement).style.setProperty( + "--acpmux-tab-row-height", + `${pixels}px`, + ); }, height); if (process.env.CMUX_SUMMARY_CAPTURE_DIR) { - await mkdir(process.env.CMUX_SUMMARY_CAPTURE_DIR, { recursive: true }); + await mkdir(process.env.CMUX_SUMMARY_CAPTURE_DIR, { + recursive: true, + }); await page.screenshot({ path: path.join( process.env.CMUX_SUMMARY_CAPTURE_DIR, @@ -49,23 +64,37 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { } const actual = await page.evaluate(() => { const shell = document.querySelector(".acpmux-shell")!; - const panel = document.querySelector(".acpmux-summary-card")!; - const head = panel.querySelector(".acpmux-summary-card__head")!; - const counts = panel.querySelector(".acpmux-summary-card__counts")!; + const panel = document.querySelector( + ".acpmux-summary-card", + )!; + const head = panel.querySelector( + ".acpmux-summary-card__head", + )!; + const counts = panel.querySelector( + ".acpmux-summary-card__counts", + )!; const rect = panel.getBoundingClientRect(); const header = head.getBoundingClientRect(); const text = counts.getBoundingClientRect(); const style = getComputedStyle(counts); - const tab = document.querySelector(".acpmux-header[data-tab-row]")!.getBoundingClientRect(); + const tab = document + .querySelector(".acpmux-header[data-tab-row]")! + .getBoundingClientRect(); return { top: rect.top - shell.getBoundingClientRect().top, intersectsTabRow: - rect.left < tab.right && rect.right > tab.left && rect.top < tab.bottom && rect.bottom > tab.top, - toolsRight: document.querySelector(".acpmux-header-tools")!.getBoundingClientRect().right, + rect.left < tab.right && + rect.right > tab.left && + rect.top < tab.bottom && + rect.bottom > tab.top, + toolsRight: document + .querySelector(".acpmux-header-tools")! + .getBoundingClientRect().right, shellRight: shell.getBoundingClientRect().right, bottom: rect.bottom - shell.getBoundingClientRect().bottom, text: counts.textContent, - countsInside: text.left >= header.left && text.right <= header.right, + countsInside: + text.left >= header.left && text.right <= header.right, overflow: style.overflowX, truncated: counts.scrollWidth > counts.clientWidth, ellipsis: style.textOverflow, @@ -100,38 +129,69 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { } for (const width of [640, 1000]) { for (const height of [28, 40]) { - test(`Preview leaves the ${height}px native row clear at ${width}px`, async () => { - const page = await browser.newPage({ viewport: { width, 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: "preview-tab-row" })}`, - ); - 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`); - }, height); - await page.locator(".acpmux-artifact").waitFor(); - const actual = await page.evaluate(() => { - const shell = document.querySelector(".acpmux-shell")!.getBoundingClientRect(); - const panel = document.querySelector(".acpmux-artifact")!.getBoundingClientRect(); - const tools = document.querySelector(".acpmux-header-tools")!.getBoundingClientRect(); - return { - top: panel.top - shell.top, - bottom: panel.bottom - shell.bottom, - toolsWidth: tools.width, - toolsRight: tools.right, - shellRight: shell.right, - }; + for (const theme of [ + "Apple System Colors", + "Apple System Colors Light", + ]) { + test(`Preview leaves the ${height}px native row clear at ${width}px (${theme})`, async () => { + const page = await browser.newPage({ + viewport: { width, height: 560 }, + reducedMotion: "reduce", }); - expect(actual.top).toBe(height); - expect(actual.bottom).toBeLessThanOrEqual(0); - expect(actual.toolsWidth).toBeGreaterThan(0); - expect(actual.toolsRight).toBe(actual.shellRight); - } finally { - await page.close(); - } - }); + 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: "preview-tab-row", theme })}`, + ); + 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`, + ); + }, height); + await page.locator(".acpmux-artifact").waitFor(); + if (process.env.CMUX_SUMMARY_CAPTURE_DIR) { + await mkdir(process.env.CMUX_SUMMARY_CAPTURE_DIR, { + recursive: true, + }); + await page.screenshot({ + path: path.join( + process.env.CMUX_SUMMARY_CAPTURE_DIR, + `preview-${height}-${width}-${theme.endsWith("Light") ? "light" : "dark"}.png`, + ), + }); + } + const actual = await page.evaluate(() => { + const shell = document + .querySelector(".acpmux-shell")! + .getBoundingClientRect(); + const panel = document + .querySelector(".acpmux-artifact")! + .getBoundingClientRect(); + const tools = document + .querySelector(".acpmux-header-tools")! + .getBoundingClientRect(); + return { + top: panel.top - shell.top, + bottom: panel.bottom - shell.bottom, + toolsWidth: tools.width, + toolsRight: tools.right, + shellRight: shell.right, + }; + }); + expect(actual.top).toBe(height); + expect(actual.bottom).toBeLessThanOrEqual(0); + expect(actual.toolsWidth).toBeGreaterThan(0); + expect(actual.toolsRight).toBe(actual.shellRight); + } finally { + await page.close(); + } + }); + } } } }); From 816ee5d4f1d0dee2b79e9a09b2e8011820e5cd2d Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 29 Oct 2026 02:49:22 +0000 Subject: [PATCH 4/4] style(preview): format tab-row gallery regression Agent-Session: https://cmux.com/deeplink/agent?id=aid-6a4ccb23b3f68615576c --- webviews/test/summary-header-surface.test.ts | 71 +++++--------------- 1 file changed, 17 insertions(+), 54 deletions(-) diff --git a/webviews/test/summary-header-surface.test.ts b/webviews/test/summary-header-surface.test.ts index 23a2cacaf0de..f0340de878fb 100644 --- a/webviews/test/summary-header-surface.test.ts +++ b/webviews/test/summary-header-surface.test.ts @@ -13,10 +13,7 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { let browser: Browser; beforeAll(async () => { server = await createServer({ - configFile: path.resolve( - import.meta.dir, - "../vite.config.gallery-dev.ts", - ), + configFile: path.resolve(import.meta.dir, "../vite.config.gallery-dev.ts"), server: { port: 0, host: "127.0.0.1", strictPort: false }, logLevel: "error", }); @@ -42,14 +39,9 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { await page.goto( `http://127.0.0.1:${port}${galleryFrame("agent-pane.summary-card", { variant: "shared-tab-row", theme })}`, ); - await page.waitForFunction( - () => document.documentElement.dataset.galleryReady === "1", - ); + 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`, - ); + (node as HTMLElement).style.setProperty("--acpmux-tab-row-height", `${pixels}px`); }, height); if (process.env.CMUX_SUMMARY_CAPTURE_DIR) { await mkdir(process.env.CMUX_SUMMARY_CAPTURE_DIR, { @@ -64,37 +56,23 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { } const actual = await page.evaluate(() => { const shell = document.querySelector(".acpmux-shell")!; - const panel = document.querySelector( - ".acpmux-summary-card", - )!; - const head = panel.querySelector( - ".acpmux-summary-card__head", - )!; - const counts = panel.querySelector( - ".acpmux-summary-card__counts", - )!; + const panel = document.querySelector(".acpmux-summary-card")!; + const head = panel.querySelector(".acpmux-summary-card__head")!; + const counts = panel.querySelector(".acpmux-summary-card__counts")!; const rect = panel.getBoundingClientRect(); const header = head.getBoundingClientRect(); const text = counts.getBoundingClientRect(); const style = getComputedStyle(counts); - const tab = document - .querySelector(".acpmux-header[data-tab-row]")! - .getBoundingClientRect(); + const tab = document.querySelector(".acpmux-header[data-tab-row]")!.getBoundingClientRect(); return { top: rect.top - shell.getBoundingClientRect().top, intersectsTabRow: - rect.left < tab.right && - rect.right > tab.left && - rect.top < tab.bottom && - rect.bottom > tab.top, - toolsRight: document - .querySelector(".acpmux-header-tools")! - .getBoundingClientRect().right, + rect.left < tab.right && rect.right > tab.left && rect.top < tab.bottom && rect.bottom > tab.top, + toolsRight: document.querySelector(".acpmux-header-tools")!.getBoundingClientRect().right, shellRight: shell.getBoundingClientRect().right, bottom: rect.bottom - shell.getBoundingClientRect().bottom, text: counts.textContent, - countsInside: - text.left >= header.left && text.right <= header.right, + countsInside: text.left >= header.left && text.right <= header.right, overflow: style.overflowX, truncated: counts.scrollWidth > counts.clientWidth, ellipsis: style.textOverflow, @@ -129,29 +107,20 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { } for (const width of [640, 1000]) { for (const height of [28, 40]) { - for (const theme of [ - "Apple System Colors", - "Apple System Colors Light", - ]) { + for (const theme of ["Apple System Colors", "Apple System Colors Light"]) { test(`Preview leaves the ${height}px native row clear at ${width}px (${theme})`, async () => { const page = await browser.newPage({ viewport: { width, height: 560 }, reducedMotion: "reduce", }); try { - const port = (server.httpServer!.address() as { port: number }) - .port; + const port = (server.httpServer!.address() as { port: number }).port; await page.goto( `http://127.0.0.1:${port}${galleryFrame("agent-pane.summary-card", { variant: "preview-tab-row", theme })}`, ); - await page.waitForFunction( - () => document.documentElement.dataset.galleryReady === "1", - ); + 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`, - ); + (node as HTMLElement).style.setProperty("--acpmux-tab-row-height", `${pixels}px`); }, height); await page.locator(".acpmux-artifact").waitFor(); if (process.env.CMUX_SUMMARY_CAPTURE_DIR) { @@ -166,15 +135,9 @@ await requireBrowserLane("summary-header-surface.test.ts", async () => { }); } const actual = await page.evaluate(() => { - const shell = document - .querySelector(".acpmux-shell")! - .getBoundingClientRect(); - const panel = document - .querySelector(".acpmux-artifact")! - .getBoundingClientRect(); - const tools = document - .querySelector(".acpmux-header-tools")! - .getBoundingClientRect(); + const shell = document.querySelector(".acpmux-shell")!.getBoundingClientRect(); + const panel = document.querySelector(".acpmux-artifact")!.getBoundingClientRect(); + const tools = document.querySelector(".acpmux-header-tools")!.getBoundingClientRect(); return { top: panel.top - shell.top, bottom: panel.bottom - shell.bottom,