From 1ce466de377c3947b48e6e3db09e85aff4d6d8de Mon Sep 17 00:00:00 2001 From: Contributor Date: Sun, 11 Oct 2026 22:22:10 +0000 Subject: [PATCH 1/2] fix(widget): prevent thread drawer children from shrinking The chat-history drawer's flex children (.thread-drawer-head, .thread-new, and .thread-item) left flex-shrink at its CSS default of 1, so the browser compressed them to fit the panel once the list overflowed. Rows shrank below their natural height, clipping titles and hiding the scrollbar. Opt each drawer child out of shrinking with flex: 0 0 auto so the scrollable .thread-list keeps its rows at natural height and scrolls. Add a Playwright regression test asserting row height is independent of the number of conversations and that the list overflows. Closes the thread-drawer squashing bug. --- src/agent_manager/api/static/widget.js | 6 +-- .../api/static/widget/styles/styles.ts | 6 +-- tests/e2e/widget.spec.ts | 37 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/agent_manager/api/static/widget.js b/src/agent_manager/api/static/widget.js index 7651b89e..e80ae718 100644 --- a/src/agent_manager/api/static/widget.js +++ b/src/agent_manager/api/static/widget.js @@ -54505,11 +54505,11 @@ function styles(config) { .thread-drawer.open { transform: none; opacity: 1; pointer-events: auto; } .thread-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px 10px 18px; font-weight: 600; font-size: 14px; color: #18181b; - border-bottom: 1px solid #f0f0f1; } + border-bottom: 1px solid #f0f0f1; flex: 0 0 auto; } .thread-new { display: flex; align-items: center; gap: 8px; margin: 12px 14px 4px; padding: 10px 14px; border: 1px solid #e4e4e7; border-radius: 12px; background: #fff; color: #18181b; font-size: 14px; font-weight: 500; cursor: pointer; font-family: inherit; - transition: background .12s; } + transition: background .12s; flex: 0 0 auto; } .thread-new:hover { background: #f4f4f5; } .thread-new svg { width: 16px; height: 16px; flex: 0 0 auto; } .thread-list { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 10px 14px; @@ -54518,7 +54518,7 @@ function styles(config) { .thread-item { text-align: left; border: 0; background: transparent; cursor: pointer; padding: 10px 12px; border-radius: 10px; font-size: 13.5px; color: #3f3f46; font-family: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - transition: background .12s; } + transition: background .12s; flex: 0 0 auto; } .thread-item:hover { background: #f4f4f5; } .thread-item.active { background: #f4f4f5; color: #18181b; font-weight: 600; } .thread-empty { color: #a1a1aa; font-size: 13px; text-align: center; padding: 24px 12px; margin: 0; } diff --git a/src/agent_manager/api/static/widget/styles/styles.ts b/src/agent_manager/api/static/widget/styles/styles.ts index d9b22a18..c413a33c 100644 --- a/src/agent_manager/api/static/widget/styles/styles.ts +++ b/src/agent_manager/api/static/widget/styles/styles.ts @@ -66,11 +66,11 @@ export function styles(config: AgentChatConfig): string { .thread-drawer.open { transform: none; opacity: 1; pointer-events: auto; } .thread-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px 10px 18px; font-weight: 600; font-size: 14px; color: #18181b; - border-bottom: 1px solid #f0f0f1; } + border-bottom: 1px solid #f0f0f1; flex: 0 0 auto; } .thread-new { display: flex; align-items: center; gap: 8px; margin: 12px 14px 4px; padding: 10px 14px; border: 1px solid #e4e4e7; border-radius: 12px; background: #fff; color: #18181b; font-size: 14px; font-weight: 500; cursor: pointer; font-family: inherit; - transition: background .12s; } + transition: background .12s; flex: 0 0 auto; } .thread-new:hover { background: #f4f4f5; } .thread-new svg { width: 16px; height: 16px; flex: 0 0 auto; } .thread-list { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 10px 14px; @@ -79,7 +79,7 @@ export function styles(config: AgentChatConfig): string { .thread-item { text-align: left; border: 0; background: transparent; cursor: pointer; padding: 10px 12px; border-radius: 10px; font-size: 13.5px; color: #3f3f46; font-family: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - transition: background .12s; } + transition: background .12s; flex: 0 0 auto; } .thread-item:hover { background: #f4f4f5; } .thread-item.active { background: #f4f4f5; color: #18181b; font-weight: 600; } .thread-empty { color: #a1a1aa; font-size: 13px; text-align: center; padding: 24px 12px; margin: 0; } diff --git a/tests/e2e/widget.spec.ts b/tests/e2e/widget.spec.ts index 35a6da56..93217497 100644 --- a/tests/e2e/widget.spec.ts +++ b/tests/e2e/widget.spec.ts @@ -1501,6 +1501,43 @@ test("thread drawer paginates and appends next pages on scroll", async ({ page } await expect.poll(() => shadowText(page, ".thread-drawer")).toContain("Thread Three"); }); +test("thread drawer keeps natural row height with many conversations and scrolls", async ({ page }) => { + const threads = Array.from({ length: 25 }, (_, i) => ({ + conversation_id: `conv-${i}`, + title: `Conversation number ${i + 1}`, + last_message_at: "2026-06-28T00:00:00Z", + })); + await mockConversationApi(page, { threads }); + await page.route("**/conversations/*/messages", async (route: Route) => { + if (route.request().method() !== "GET") return route.fallback(); + await route.fulfill({ status: 200, contentType: "application/json", body: "[]" }); + }); + + await page.goto("/widget-demo.html"); + await shadowClick(page, ".launcher"); + await shadowClick(page, '[aria-label="Conversations"]'); + await expect.poll(() => shadowText(page, ".thread-drawer")).toContain("Conversation number 25"); + + // Every row must render at the same natural height — the count must not + // compress them. (The bug: flex-shrink squashed rows to ~25px once the list + // overflowed, clipping titles and hiding the scrollbar.) + const heights = await (await widget(page)).evaluate((el) => { + const rows = Array.from(el.shadowRoot?.querySelectorAll(".thread-item") ?? []); + return rows.map((row) => row.getBoundingClientRect().height); + }); + const first = heights[0]; + expect(first).toBeGreaterThan(20); + for (const height of heights) expect(Math.abs(height - first)).toBeLessThan(0.5); + + // The list itself must overflow so the scrollbar is usable. + const scrollMetrics = await (await widget(page)).evaluate((el) => { + const list = el.shadowRoot?.querySelector(".thread-list"); + return list ? { scrollHeight: list.scrollHeight, clientHeight: list.clientHeight } : null; + }); + expect(scrollMetrics).not.toBeNull(); + expect(scrollMetrics.scrollHeight).toBeGreaterThan(scrollMetrics.clientHeight); +}); + test("visitor pass cached, cookie login hand-off merges history and first thread list request observes merged threads", async ({ page }) => { const calls: string[] = []; await pinVisitorPass(page); From a287b499ab25c567f50639719084b6daf2bea489 Mon Sep 17 00:00:00 2001 From: Tamaghna Sarkar Date: Sat, 17 Oct 2026 01:56:22 +0530 Subject: [PATCH 2/2] test: make scrollMetrics non-null so typecheck:e2e passes (TS18047) --- tests/e2e/widget.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/widget.spec.ts b/tests/e2e/widget.spec.ts index 93217497..d41923cb 100644 --- a/tests/e2e/widget.spec.ts +++ b/tests/e2e/widget.spec.ts @@ -1532,9 +1532,9 @@ test("thread drawer keeps natural row height with many conversations and scrolls // The list itself must overflow so the scrollbar is usable. const scrollMetrics = await (await widget(page)).evaluate((el) => { const list = el.shadowRoot?.querySelector(".thread-list"); - return list ? { scrollHeight: list.scrollHeight, clientHeight: list.clientHeight } : null; + if (!list) throw new Error("thread-list not found"); + return { scrollHeight: list.scrollHeight, clientHeight: list.clientHeight }; }); - expect(scrollMetrics).not.toBeNull(); expect(scrollMetrics.scrollHeight).toBeGreaterThan(scrollMetrics.clientHeight); });