From da853826861107c8b2ffd9136ab00cb9b20e01e0 Mon Sep 17 00:00:00 2001 From: ClawSweeper Date: Sun, 16 Aug 2026 14:04:47 -0700 Subject: [PATCH] improve(ui): make chat disclosures compact (#124832) Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com> --- ui/src/e2e/chat-tool-turn-outcome.e2e.test.ts | 31 ++++ .../chat/chat-responsive.browser.test.ts | 32 ++-- .../chat/components/chat-message-bubble.ts | 5 +- .../chat/components/chat-message-group.ts | 6 +- .../chat/components/chat-message-stream.ts | 6 +- .../chat/components/chat-tool-cards.test.ts | 3 +- .../pages/chat/components/chat-tool-cards.ts | 9 +- ui/src/styles/chat/tool-cards.css | 173 ++++++------------ ui/src/styles/components.css | 23 --- 9 files changed, 126 insertions(+), 162 deletions(-) diff --git a/ui/src/e2e/chat-tool-turn-outcome.e2e.test.ts b/ui/src/e2e/chat-tool-turn-outcome.e2e.test.ts index 68d6ec1616a1..aed30c6f1d06 100644 --- a/ui/src/e2e/chat-tool-turn-outcome.e2e.test.ts +++ b/ui/src/e2e/chat-tool-turn-outcome.e2e.test.ts @@ -108,9 +108,16 @@ suite.define(() => { }); it("pairs a canonical parallel batch and renders per-file patch sections", async () => { + const artifactDir = process.env.OPENCLAW_CONTROL_UI_E2E_ARTIFACT_DIR?.trim(); + if (artifactDir) { + await fs.mkdir(artifactDir, { recursive: true }); + } const context = await suite.browser.newContext({ locale: "en-US", viewport: { height: 900, width: 1200 }, + ...(artifactDir + ? { recordVideo: { dir: artifactDir, size: { height: 900, width: 1200 } } } + : {}), }); const page = await context.newPage(); await installMockGateway(page, { @@ -165,12 +172,36 @@ suite.define(() => { const activity = page.locator(".chat-group--activity .chat-activity-group__summary"); await activity.waitFor(); expect(await activity.textContent()).toContain("Read a file, edited a file, created a file"); + const activityGeometry = await activity.evaluate((node) => { + const container = node.closest(".chat-activity-group"); + const label = node.querySelector(".chat-activity-group__label"); + const chevron = node.querySelector(".chat-inline-disclosure__chevron"); + if (!container || !label || !chevron) { + throw new Error("Expected compact activity disclosure parts"); + } + const containerRect = container.getBoundingClientRect(); + const summaryRect = node.getBoundingClientRect(); + const labelRect = label.getBoundingClientRect(); + const chevronRect = chevron.getBoundingClientRect(); + return { + containerWidth: containerRect.width, + summaryWidth: summaryRect.width, + chevronGap: chevronRect.left - labelRect.right, + }; + }); + expect(activityGeometry.summaryWidth).toBeLessThan(activityGeometry.containerWidth); + expect(activityGeometry.chevronGap).toBeLessThanOrEqual(8); + await activity.hover(); + expect(await activity.evaluate((node) => getComputedStyle(node).backgroundColor)).toBe( + "rgba(0, 0, 0, 0)", + ); if ((await activity.getAttribute("aria-expanded")) !== "true") { await activity.click(); } const rows = page.locator(".chat-activity-group__body .chat-tool-msg-summary"); expect(await rows.count()).toBe(2); + expect(await rows.locator(".chat-inline-disclosure__chevron").count()).toBe(2); expect(await page.locator(".chat-tool-msg-summary__label", { hasText: "Tool" }).count()).toBe( 0, ); diff --git a/ui/src/pages/chat/chat-responsive.browser.test.ts b/ui/src/pages/chat/chat-responsive.browser.test.ts index 6ee189f788b0..cb08bcf48b45 100644 --- a/ui/src/pages/chat/chat-responsive.browser.test.ts +++ b/ui/src/pages/chat/chat-responsive.browser.test.ts @@ -206,28 +206,31 @@ function activityAlignmentHtml() {
A
-
-
-
@@ -1171,7 +1174,7 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { it.each([ [430, 720], [1366, 900], - ] as const)("right-aligns activity rows with call bubbles at %sx%s", async (width, height) => { + ] as const)("keeps activity disclosures compact at %sx%s", async (width, height) => { const page = await openBrowserPage(width, height); try { await page.setContent( @@ -1179,24 +1182,27 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { ); await expectNoHorizontalOverflow(page); - const callRow = await getRect(page, "[data-activity-call-row]"); + const activityGroup = await getRect(page, ".chat-activity-group"); + const activitySummary = await getRect(page, ".chat-activity-group__summary"); const failedSummary = await getRect(page, "[data-failed-call-row]"); - expect(Math.abs(callRow.right - failedSummary.right)).toBeLessThanOrEqual(1); - expect(Math.abs(callRow.height - failedSummary.height)).toBeLessThanOrEqual(1); + expect(activitySummary.width).toBeLessThan(activityGroup.width); + expect(failedSummary.width).toBeLessThan(activityGroup.width); const styles = await page.evaluate(() => { - const call = document.querySelector("[data-activity-call-row]")!; + const activity = document.querySelector(".chat-activity-group__summary")!; + const label = activity.querySelector(".chat-activity-group__label")!; + const chevron = activity.querySelector(".chat-inline-disclosure__chevron")!; return { - activity: getComputedStyle( - document.querySelector(".chat-activity-group__summary")!, - ).userSelect, - callBackground: getComputedStyle(call).backgroundColor, + activity: getComputedStyle(activity).userSelect, + activityBackground: getComputedStyle(activity).backgroundColor, + chevronGap: chevron.getBoundingClientRect().left - label.getBoundingClientRect().right, tool: getComputedStyle(document.querySelector(".chat-tool-msg-summary")!) .userSelect, }; }); expect(styles).toEqual({ activity: "text", - callBackground: "rgba(0, 0, 0, 0)", + activityBackground: "rgba(0, 0, 0, 0)", + chevronGap: 8, tool: "text", }); } finally { diff --git a/ui/src/pages/chat/components/chat-message-bubble.ts b/ui/src/pages/chat/components/chat-message-bubble.ts index 35d841cb02a5..67cdf3cb42ae 100644 --- a/ui/src/pages/chat/components/chat-message-bubble.ts +++ b/ui/src/pages/chat/components/chat-message-bubble.ts @@ -463,7 +463,7 @@ export function renderGroupedMessage( : ""}" > diff --git a/ui/src/pages/chat/components/chat-message-stream.ts b/ui/src/pages/chat/components/chat-message-stream.ts index 90c5b36de78b..9cd03f2a28ed 100644 --- a/ui/src/pages/chat/components/chat-message-stream.ts +++ b/ui/src/pages/chat/components/chat-message-stream.ts @@ -169,7 +169,7 @@ export function renderWorkGroupSummary(
diff --git a/ui/src/pages/chat/components/chat-tool-cards.test.ts b/ui/src/pages/chat/components/chat-tool-cards.test.ts index 70f6d928c00d..53319252b7d8 100644 --- a/ui/src/pages/chat/components/chat-tool-cards.test.ts +++ b/ui/src/pages/chat/components/chat-tool-cards.test.ts @@ -692,7 +692,8 @@ describe("tool-cards", () => { expect(container.querySelector(".chat-tool-card__preview-frame")).toBeNull(); expect(rawToggle).toBeInstanceOf(HTMLButtonElement); expect(rawBody).toBeInstanceOf(HTMLElement); - expect([...rawToggle!.classList]).toEqual(["chat-tool-card__raw-toggle"]); + expect(rawToggle!.classList).toContain("chat-inline-disclosure"); + expect(rawToggle!.classList).toContain("chat-tool-card__raw-toggle"); expect(rawToggle!.textContent?.trim()).toBe("Raw details"); expect(rawToggle!.getAttribute("aria-expanded")).toBe("false"); expect(rawBody!.hidden).toBe(true); diff --git a/ui/src/pages/chat/components/chat-tool-cards.ts b/ui/src/pages/chat/components/chat-tool-cards.ts index a040b2849678..51894f448662 100644 --- a/ui/src/pages/chat/components/chat-tool-cards.ts +++ b/ui/src/pages/chat/components/chat-tool-cards.ts @@ -192,13 +192,13 @@ export function renderRawOutputToggle(text: string) { return html`