From bc2240b53b85c41634fc16d812d66959ee9767af Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Tue, 6 Oct 2026 17:40:50 +0545 Subject: [PATCH 1/9] Add tests for centered conversation layout --- e2e/browser/reply.test.ts | 81 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/e2e/browser/reply.test.ts b/e2e/browser/reply.test.ts index 3842f65be..7407fdd9d 100644 --- a/e2e/browser/reply.test.ts +++ b/e2e/browser/reply.test.ts @@ -13,7 +13,9 @@ describeBrowser("worker reply", () => { const aimock = bootAimock([ { match: { predicate: () => true }, - response: { content: `${MOCK_REPLY}\n\nName: Ada` }, + response: { + content: `${MOCK_REPLY}\n\n${"This longer reply keeps the conversation scrolling while its prose stays readable.\n\n".repeat(16)}Name: Ada`, + }, }, ]); const app = bootBrowserApp(); @@ -53,6 +55,82 @@ describeBrowser("worker reply", () => { }); expect(aimock().journal().length).toBeGreaterThan(0); + await page.emulateMediaFeatures([{ name: "prefers-reduced-motion", value: "reduce" }]); + for (const { width, reservedSpace } of [ + { width: 1800, reservedSpace: 0 }, + { width: 1200, reservedSpace: 0 }, + { width: 900, reservedSpace: 0 }, + { width: 800, reservedSpace: 0 }, + { width: 390, reservedSpace: 0 }, + { width: 1800, reservedSpace: 16 }, + { width: 900, reservedSpace: 16 }, + ]) { + await page.setViewport({ width, height: 900 }); + // Simulate reserved scrollbar space on hosts with overlay scrollbars. + await page.$eval( + ".workbench-main-scroll", + (element, space) => { + if (!(element instanceof HTMLElement)) throw new Error("Missing scroll pane"); + element.style.borderInlineEnd = `${space}px solid transparent`; + }, + reservedSpace, + ); + for (const drawerOpen of [false, true]) { + if (drawerOpen) await page.click(".bench-pill"); + const layout = await page.evaluate(() => { + const element = (selector: string) => { + const found = document.querySelector(selector); + if (found === null) throw new Error(`Missing layout element: ${selector}`); + return found; + }; + const bounds = (selector: string) => { + const { left, right, width } = element(selector).getBoundingClientRect(); + return { left, right, width }; + }; + const scroll = element(".workbench-main-scroll"); + const prose = element( + '.timeline-inner .chat-thread-message:not([data-author="me"]) .chat-thread-body', + ); + return { + main: bounds(".workbench-main"), + timeline: bounds(".timeline-inner"), + divider: bounds(".timeline-inner .chat-day"), + composer: bounds(".workbench-main-composer .chat-composer-box"), + worker: bounds(".timeline-inner .chat-thread-avatar"), + user: bounds(".timeline-inner .chat-thread-own-bubble"), + proseWidth: prose.getBoundingClientRect().width, + proseLimit: Number.parseFloat(getComputedStyle(prose).maxWidth), + scrolls: scroll.scrollHeight > scroll.clientHeight, + overflows: scroll.scrollWidth > scroll.clientWidth, + pageOverflows: document.documentElement.scrollWidth > innerWidth, + }; + }); + const context = `${width}px, drawer ${drawerOpen ? "open" : "closed"}, reserved space ${reservedSpace}px`; + for (const box of [layout.timeline, layout.divider]) { + expect(Math.abs(box.left - layout.composer.left), context).toBeLessThanOrEqual(1); + expect(Math.abs(box.right - layout.composer.right), context).toBeLessThanOrEqual(1); + } + const leftGutter = layout.composer.left - layout.main.left; + const rightGutter = layout.main.right - layout.composer.right; + expect(Math.abs(leftGutter - rightGutter), context).toBeLessThanOrEqual(1); + expect(leftGutter, context).toBeGreaterThanOrEqual(32); + expect(layout.composer.width, context).toBeLessThanOrEqual(1200); + expect(Math.abs(layout.worker.left - layout.composer.left), context).toBeLessThanOrEqual( + 1, + ); + expect( + Math.abs(layout.composer.right - layout.user.right - 16), + context, + ).toBeLessThanOrEqual(1); + expect(layout.proseWidth, context).toBeLessThanOrEqual(layout.proseLimit + 1); + expect(layout.scrolls, context).toBe(true); + expect(layout.overflows, context).toBe(false); + expect(layout.pageOverflows, context).toBe(false); + if (drawerOpen) await page.click('#bench-drawer button[aria-label="Close drawer"]'); + } + } + await page.setViewport({ width: 1400, height: 900 }); + // The worker named itself: pill, sidebar and message header show it, // and the marker line is never rendered. const benchUrl = page.url(); @@ -98,6 +176,7 @@ describeBrowser("worker reply", () => { ); throw cause; } + expect(errors).toEqual([]); await page.close(); }, 240_000); }); From 484181dfc72b4b733fcc897b5fb236963ee7e944 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Tue, 6 Oct 2026 17:40:51 +0545 Subject: [PATCH 2/9] Conversation layout: center the timeline and composer --- apps/web/src/chat/thread.css | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/web/src/chat/thread.css b/apps/web/src/chat/thread.css index f65c3202e..997692d24 100644 --- a/apps/web/src/chat/thread.css +++ b/apps/web/src/chat/thread.css @@ -1,29 +1,31 @@ -/* The bench conversation: a left-aligned timeline above the composer. */ +/* The bench conversation: one centered column for the timeline and composer. */ .workbench-layout { display: flex; min-height: 0; flex: 1; } .workbench-main { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: minmax(32px, 1fr) minmax(0, 1200px) minmax(32px, 1fr); + grid-template-rows: minmax(0, 1fr) auto; flex: 1; min-width: 0; min-height: 0; } .workbench-main-scroll { - flex: 1; + display: grid; + grid-column: 1 / -1; + grid-template-columns: subgrid; min-height: 0; overflow-y: auto; - padding: 72px 24px 16px; + padding: 72px 0 16px; } .timeline-inner { - max-width: 1200px; - padding: 0 8px; + grid-column: 2; } .workbench-main-composer { - flex-shrink: 0; - padding: 0 16px 16px; + grid-column: 2; + padding-bottom: 16px; } .wb-corner { @@ -87,6 +89,7 @@ } .chat-thread-message[data-author="me"] { justify-content: flex-end; + padding-inline-end: 16px; } .chat-thread-message[data-author="me"] .chat-thread-body { flex: 0 1 auto; From d0ee7b674196d1a0250b7601321212742e061509 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Tue, 6 Oct 2026 17:40:52 +0545 Subject: [PATCH 3/9] Update docs: centered conversation layout --- DESIGN.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 765a63d2d..c3d1b6d3a 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -51,8 +51,11 @@ card that pushes the chat and composer left rather than covering them. Its tabs: Information (an overview of everything, including active workflows), Artifacts, Tools, Grants, Insights, Members, Workflows. -The thread spans the sheet; prose holds a ~76ch measure and cards cap at -640px. The composer is one lean full-width row: `+`, text, voice, send. +The timeline, day divider, and composer share a centered column capped at +1200px, with at least 32px side gutters. The column centers in the remaining +chat pane when the bench drawer is open. Workers align left and user bubbles +align right with a 16px inset; prose holds a ~76ch measure and cards cap at +640px. The composer is one lean row: `+`, text, voice, send. Voice mode covers the thread with the worker's mark over a level-reactive glow, a speaking/listening state line, and live captions. Controls: From 6bb90821a74d1cf90c294601d13ed337206f23e7 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Wed, 7 Oct 2026 20:39:58 +0545 Subject: [PATCH 4/9] Add tests for reply spacing and drawer borders --- e2e/browser/reply.test.ts | 117 +++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/e2e/browser/reply.test.ts b/e2e/browser/reply.test.ts index 7407fdd9d..0005fb3c2 100644 --- a/e2e/browser/reply.test.ts +++ b/e2e/browser/reply.test.ts @@ -42,7 +42,13 @@ describeBrowser("worker reply", () => { await model[1]?.type("mock-model"); await clickText(page, "button", "Connect"); - await page.waitForSelector("textarea", { timeout: STEP_TIMEOUT }); + await page.waitForFunction( + `(() => { + const input = document.querySelector("textarea"); + return input instanceof HTMLTextAreaElement && !input.disabled; + })()`, + { timeout: STEP_TIMEOUT }, + ); await page.type("textarea", "Say hello"); await page.click("button[aria-label='Start this workbench']"); await page.waitForFunction(`location.pathname.startsWith("/w/")`, { @@ -56,7 +62,15 @@ describeBrowser("worker reply", () => { expect(aimock().journal().length).toBeGreaterThan(0); await page.emulateMediaFeatures([{ name: "prefers-reduced-motion", value: "reduce" }]); + const waitForLayout = () => + page.evaluate( + () => + new Promise((resolve) => + requestAnimationFrame(() => requestAnimationFrame(() => resolve())), + ), + ); for (const { width, reservedSpace } of [ + { width: 2048, reservedSpace: 0 }, { width: 1800, reservedSpace: 0 }, { width: 1200, reservedSpace: 0 }, { width: 900, reservedSpace: 0 }, @@ -64,6 +78,8 @@ describeBrowser("worker reply", () => { { width: 390, reservedSpace: 0 }, { width: 1800, reservedSpace: 16 }, { width: 900, reservedSpace: 16 }, + { width: 1800, reservedSpace: 20 }, + { width: 900, reservedSpace: 20 }, ]) { await page.setViewport({ width, height: 900 }); // Simulate reserved scrollbar space on hosts with overlay scrollbars. @@ -76,7 +92,10 @@ describeBrowser("worker reply", () => { reservedSpace, ); for (const drawerOpen of [false, true]) { - if (drawerOpen) await page.click(".bench-pill"); + if (drawerOpen) { + await page.click(".bench-pill"); + await waitForLayout(); + } const layout = await page.evaluate(() => { const element = (selector: string) => { const found = document.querySelector(selector); @@ -105,6 +124,23 @@ describeBrowser("worker reply", () => { pageOverflows: document.documentElement.scrollWidth > innerWidth, }; }); + if (drawerOpen) { + const drawer = await page.evaluate(() => { + const cell = document.querySelector(".drawer-cell")?.getBoundingClientRect(); + const card = document.querySelector(".drawer")?.getBoundingClientRect(); + if (cell === undefined || card === undefined) throw new Error("Missing drawer"); + return { left: card.left - cell.left, right: cell.right - card.right }; + }); + const inset = width > 860 ? 8 : 0; + expect( + Math.abs(drawer.left - inset), + `${width}px drawer left edge`, + ).toBeLessThanOrEqual(1); + expect( + Math.abs(drawer.right - inset), + `${width}px drawer right edge`, + ).toBeLessThanOrEqual(1); + } const context = `${width}px, drawer ${drawerOpen ? "open" : "closed"}, reserved space ${reservedSpace}px`; for (const box of [layout.timeline, layout.divider]) { expect(Math.abs(box.left - layout.composer.left), context).toBeLessThanOrEqual(1); @@ -126,7 +162,82 @@ describeBrowser("worker reply", () => { expect(layout.scrolls, context).toBe(true); expect(layout.overflows, context).toBe(false); expect(layout.pageOverflows, context).toBe(false); - if (drawerOpen) await page.click('#bench-drawer button[aria-label="Close drawer"]'); + if (drawerOpen) { + await page.click('#bench-drawer button[aria-label="Close drawer"]'); + await waitForLayout(); + } + } + if (width >= 900) { + await page.click('.timeline-inner .chat-thread-message[data-author="me"] button'); + await page.waitForSelector(".workbench-subthread"); + await waitForLayout(); + const replyLayout = await page.evaluate(() => { + const bounds = (selector: string) => { + const element = document.querySelector(selector); + if (element === null) throw new Error(`Missing layout element: ${selector}`); + return element.getBoundingClientRect(); + }; + const composer = bounds(".workbench-main-composer .chat-composer-box"); + const scroll = document.querySelector(".workbench-main-scroll"); + if (scroll === null) throw new Error("Missing scroll pane"); + return { + gap: bounds(".workbench-subthread").left - composer.right, + dividerGap: composer.right - bounds(".timeline-inner .chat-day").right, + userInset: composer.right - bounds(".timeline-inner .chat-thread-own-bubble").right, + reservedWidth: scroll.getBoundingClientRect().width - scroll.clientWidth, + timelineLeft: bounds(".timeline-inner").left - composer.left, + leftGutter: composer.left - bounds(".workbench-main").left, + width: composer.width, + overflows: scroll.scrollWidth > scroll.clientWidth, + pageOverflows: document.documentElement.scrollWidth > innerWidth, + }; + }); + const context = `${width}px, replies open, reserved space ${reservedSpace}px`; + expect(Math.abs(replyLayout.gap - 16), context).toBeLessThanOrEqual(1); + expect(Math.abs(replyLayout.timelineLeft), context).toBeLessThanOrEqual(1); + expect( + Math.abs(replyLayout.dividerGap + 16 - replyLayout.reservedWidth), + context, + ).toBeLessThanOrEqual(1); + expect( + Math.abs(replyLayout.userInset - replyLayout.reservedWidth), + context, + ).toBeLessThanOrEqual(1); + expect(replyLayout.leftGutter, context).toBeGreaterThanOrEqual(32); + expect(replyLayout.width, context).toBeLessThanOrEqual(1200); + expect(replyLayout.overflows, context).toBe(false); + expect(replyLayout.pageOverflows, context).toBe(false); + await page.click(".bench-pill"); + await waitForLayout(); + const drawerWithReplies = await page.evaluate(() => { + const cell = document.querySelector(".drawer-cell")?.getBoundingClientRect(); + const card = document.querySelector(".drawer")?.getBoundingClientRect(); + const body = document.querySelector(".drawer-body"); + if (cell === undefined || card === undefined || body === null) { + throw new Error("Missing drawer"); + } + return { + left: card.left - cell.left, + right: cell.right - card.right, + overflows: body.scrollWidth > body.clientWidth, + }; + }); + expect(Math.abs(drawerWithReplies.left - 8), context).toBeLessThanOrEqual(1); + expect(Math.abs(drawerWithReplies.right - 8), context).toBeLessThanOrEqual(1); + expect(drawerWithReplies.overflows, context).toBe(false); + await page.click('#bench-drawer button[aria-label="Close drawer"]'); + await page.click(".workbench-subthread-head button"); + await page.waitForSelector(".workbench-subthread", { hidden: true }); + await waitForLayout(); + const restoredCenter = await page.evaluate(() => { + const main = document.querySelector(".workbench-main")?.getBoundingClientRect(); + const composer = document + .querySelector(".workbench-main-composer .chat-composer-box") + ?.getBoundingClientRect(); + if (main === undefined || composer === undefined) throw new Error("Missing layout"); + return composer.left - main.left - (main.right - composer.right); + }); + expect(Math.abs(restoredCenter), context).toBeLessThanOrEqual(1); } } await page.setViewport({ width: 1400, height: 900 }); From d5c5d886f79c81d071fea32f6f87fb9848fab760 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Wed, 7 Oct 2026 20:41:01 +0545 Subject: [PATCH 5/9] Conversation layout: inset replies input and preserve drawer borders --- apps/web/src/app.css | 5 +---- apps/web/src/chat/thread.css | 10 ++++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 37a6dd404..883aeda77 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -1845,10 +1845,7 @@ tr.insights-row-clickable:hover { } .drawer { position: absolute; - top: 8px; - right: 8px; - bottom: 8px; - width: calc(min(420px, 42vw) - 8px); + inset: 8px; background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--overlay); diff --git a/apps/web/src/chat/thread.css b/apps/web/src/chat/thread.css index 997692d24..21f631247 100644 --- a/apps/web/src/chat/thread.css +++ b/apps/web/src/chat/thread.css @@ -12,6 +12,9 @@ min-width: 0; min-height: 0; } +.workbench-main:has(+ .workbench-subthread) { + grid-template-columns: minmax(32px, 1fr) minmax(0, 1200px) 0; +} .workbench-main-scroll { display: grid; grid-column: 1 / -1; @@ -20,6 +23,10 @@ overflow-y: auto; padding: 72px 0 16px; } +.workbench-main:has(+ .workbench-subthread) .workbench-main-scroll { + display: block; + padding-inline-start: max(32px, calc(100% - 1200px)); +} .timeline-inner { grid-column: 2; } @@ -27,6 +34,9 @@ grid-column: 2; padding-bottom: 16px; } +.workbench-main:has(+ .workbench-subthread) .workbench-main-composer { + padding-inline-end: 16px; +} .wb-corner { position: absolute; From a79843305f53c331bdcb6f375ada93647ab1a161 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Wed, 7 Oct 2026 20:42:02 +0545 Subject: [PATCH 6/9] Update docs: replies spacing and drawer insets --- DESIGN.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/DESIGN.md b/DESIGN.md index c3d1b6d3a..c9c9cc982 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -47,7 +47,8 @@ thin orange arc orbits a worker while it works, concentric with the mark. No top bar. A frosted pill centered at the top shows the bench's worker, its name, and live status; it opens the bench drawer. The drawer is a floating -card that pushes the chat and composer left rather than covering them. Its +card that pushes the chat and composer left rather than covering them. +On desktop it sits 8px inside its column so its border stays visible. Its tabs: Information (an overview of everything, including active workflows), Artifacts, Tools, Grants, Insights, Members, Workflows. @@ -56,6 +57,10 @@ The timeline, day divider, and composer share a centered column capped at chat pane when the bench drawer is open. Workers align left and user bubbles align right with a 16px inset; prose holds a ~76ch measure and cards cap at 640px. The composer is one lean row: `+`, text, voice, send. +When Replies is open, the timeline's right gutter collapses; the composer +keeps a 16px gap from the reply panel. User bubbles retain their 16px inset +within the timeline, which fits the available scroll width. Closing Replies +restores centering. Voice mode covers the thread with the worker's mark over a level-reactive glow, a speaking/listening state line, and live captions. Controls: From 2f188b18976f388e9bb6d6118c044513167f3697 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Wed, 7 Oct 2026 20:59:38 +0545 Subject: [PATCH 7/9] Add tests for flat drawer and matching panel spacing --- e2e/browser/reply.test.ts | 52 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/e2e/browser/reply.test.ts b/e2e/browser/reply.test.ts index 0005fb3c2..4cc137088 100644 --- a/e2e/browser/reply.test.ts +++ b/e2e/browser/reply.test.ts @@ -74,6 +74,8 @@ describeBrowser("worker reply", () => { { width: 1800, reservedSpace: 0 }, { width: 1200, reservedSpace: 0 }, { width: 900, reservedSpace: 0 }, + { width: 861, reservedSpace: 0 }, + { width: 860, reservedSpace: 0 }, { width: 800, reservedSpace: 0 }, { width: 390, reservedSpace: 0 }, { width: 1800, reservedSpace: 16 }, @@ -119,6 +121,7 @@ describeBrowser("worker reply", () => { user: bounds(".timeline-inner .chat-thread-own-bubble"), proseWidth: prose.getBoundingClientRect().width, proseLimit: Number.parseFloat(getComputedStyle(prose).maxWidth), + reservedWidth: scroll.getBoundingClientRect().width - scroll.clientWidth, scrolls: scroll.scrollHeight > scroll.clientHeight, overflows: scroll.scrollWidth > scroll.clientWidth, pageOverflows: document.documentElement.scrollWidth > innerWidth, @@ -127,35 +130,50 @@ describeBrowser("worker reply", () => { if (drawerOpen) { const drawer = await page.evaluate(() => { const cell = document.querySelector(".drawer-cell")?.getBoundingClientRect(); - const card = document.querySelector(".drawer")?.getBoundingClientRect(); - if (cell === undefined || card === undefined) throw new Error("Missing drawer"); - return { left: card.left - cell.left, right: cell.right - card.right }; + const element = document.querySelector(".drawer"); + if (cell === undefined || element === null) throw new Error("Missing drawer"); + const card = element.getBoundingClientRect(); + const style = getComputedStyle(element); + return { + left: card.left - cell.left, + right: cell.right - card.right, + shadow: style.boxShadow, + radius: Number.parseFloat(style.borderRadius), + divider: Number.parseFloat(style.borderLeftWidth), + }; }); - const inset = width > 860 ? 8 : 0; - expect( - Math.abs(drawer.left - inset), - `${width}px drawer left edge`, - ).toBeLessThanOrEqual(1); - expect( - Math.abs(drawer.right - inset), - `${width}px drawer right edge`, - ).toBeLessThanOrEqual(1); + expect(Math.abs(drawer.left), `${width}px drawer left edge`).toBeLessThanOrEqual(1); + expect(Math.abs(drawer.right), `${width}px drawer right edge`).toBeLessThanOrEqual(1); + if (width > 860) { + expect(drawer.shadow, `${width}px nested drawer surface`).toBe("none"); + expect(drawer.radius, `${width}px flat drawer edge`).toBe(0); + expect(drawer.divider, `${width}px visible drawer divider`).toBe(1); + } } const context = `${width}px, drawer ${drawerOpen ? "open" : "closed"}, reserved space ${reservedSpace}px`; for (const box of [layout.timeline, layout.divider]) { expect(Math.abs(box.left - layout.composer.left), context).toBeLessThanOrEqual(1); - expect(Math.abs(box.right - layout.composer.right), context).toBeLessThanOrEqual(1); + const offset = drawerOpen ? 16 - layout.reservedWidth : 0; + expect( + Math.abs(box.right - layout.composer.right - offset), + context, + ).toBeLessThanOrEqual(1); } const leftGutter = layout.composer.left - layout.main.left; const rightGutter = layout.main.right - layout.composer.right; - expect(Math.abs(leftGutter - rightGutter), context).toBeLessThanOrEqual(1); + expect( + Math.abs(drawerOpen ? rightGutter - 16 : leftGutter - rightGutter), + context, + ).toBeLessThanOrEqual(1); expect(leftGutter, context).toBeGreaterThanOrEqual(32); expect(layout.composer.width, context).toBeLessThanOrEqual(1200); expect(Math.abs(layout.worker.left - layout.composer.left), context).toBeLessThanOrEqual( 1, ); expect( - Math.abs(layout.composer.right - layout.user.right - 16), + Math.abs( + layout.composer.right - layout.user.right - (drawerOpen ? layout.reservedWidth : 16), + ), context, ).toBeLessThanOrEqual(1); expect(layout.proseWidth, context).toBeLessThanOrEqual(layout.proseLimit + 1); @@ -222,8 +240,8 @@ describeBrowser("worker reply", () => { overflows: body.scrollWidth > body.clientWidth, }; }); - expect(Math.abs(drawerWithReplies.left - 8), context).toBeLessThanOrEqual(1); - expect(Math.abs(drawerWithReplies.right - 8), context).toBeLessThanOrEqual(1); + expect(Math.abs(drawerWithReplies.left), context).toBeLessThanOrEqual(1); + expect(Math.abs(drawerWithReplies.right), context).toBeLessThanOrEqual(1); expect(drawerWithReplies.overflows, context).toBe(false); await page.click('#bench-drawer button[aria-label="Close drawer"]'); await page.click(".workbench-subthread-head button"); From 175b9035d32dc2cf8a381ddbdb2a69403833af8a Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Wed, 7 Oct 2026 21:00:37 +0545 Subject: [PATCH 8/9] Conversation layout: match drawer and reply panel spacing --- apps/web/src/app.css | 9 ++++++--- apps/web/src/chat/thread.css | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 883aeda77..0d9546f8a 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -1845,10 +1845,8 @@ tr.insights-row-clickable:hover { } .drawer { position: absolute; - inset: 8px; + inset: 0; background: var(--surface); - border-radius: var(--r-lg); - box-shadow: var(--overlay); display: flex; flex-direction: column; min-height: 0; @@ -1867,6 +1865,11 @@ tr.insights-row-clickable:hover { .drawer-scrim { display: none; } +@media (min-width: 861px) { + .drawer { + border-left: 1px solid var(--border); + } +} .drawer-head { display: flex; align-items: center; diff --git a/apps/web/src/chat/thread.css b/apps/web/src/chat/thread.css index 21f631247..19f1f0159 100644 --- a/apps/web/src/chat/thread.css +++ b/apps/web/src/chat/thread.css @@ -12,7 +12,8 @@ min-width: 0; min-height: 0; } -.workbench-main:has(+ .workbench-subthread) { +.workbench-main:has(+ .workbench-subthread), +.wb[data-drawer="open"] .workbench-main { grid-template-columns: minmax(32px, 1fr) minmax(0, 1200px) 0; } .workbench-main-scroll { @@ -23,7 +24,8 @@ overflow-y: auto; padding: 72px 0 16px; } -.workbench-main:has(+ .workbench-subthread) .workbench-main-scroll { +.workbench-main:has(+ .workbench-subthread) .workbench-main-scroll, +.wb[data-drawer="open"] .workbench-main-scroll { display: block; padding-inline-start: max(32px, calc(100% - 1200px)); } @@ -34,7 +36,8 @@ grid-column: 2; padding-bottom: 16px; } -.workbench-main:has(+ .workbench-subthread) .workbench-main-composer { +.workbench-main:has(+ .workbench-subthread) .workbench-main-composer, +.wb[data-drawer="open"] .workbench-main-composer { padding-inline-end: 16px; } From f430e745acf2735b93a2c829253c56920d80c4a4 Mon Sep 17 00:00:00 2001 From: 0xPratik Date: Wed, 7 Oct 2026 21:01:20 +0545 Subject: [PATCH 9/9] Update docs: flat side panels and input spacing --- DESIGN.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index c9c9cc982..491239416 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -46,21 +46,21 @@ thin orange arc orbits a worker while it works, concentric with the mark. ## Workbench No top bar. A frosted pill centered at the top shows the bench's worker, its -name, and live status; it opens the bench drawer. The drawer is a floating -card that pushes the chat and composer left rather than covering them. -On desktop it sits 8px inside its column so its border stays visible. Its +name, and live status; it opens the bench drawer. On desktop the drawer is +a flush side panel separated by a border, pushing the chat and composer +left. On phones it covers the chat as a full-width overlay. Its tabs: Information (an overview of everything, including active workflows), Artifacts, Tools, Grants, Insights, Members, Workflows. -The timeline, day divider, and composer share a centered column capped at -1200px, with at least 32px side gutters. The column centers in the remaining -chat pane when the bench drawer is open. Workers align left and user bubbles -align right with a 16px inset; prose holds a ~76ch measure and cards cap at -640px. The composer is one lean row: `+`, text, voice, send. -When Replies is open, the timeline's right gutter collapses; the composer -keeps a 16px gap from the reply panel. User bubbles retain their 16px inset -within the timeline, which fits the available scroll width. Closing Replies -restores centering. +With both side panels closed, the timeline, day divider, and composer share +a centered column capped at 1200px, with at least 32px side gutters. +Workers align left and user bubbles align right with a 16px inset; prose +holds a ~76ch measure and cards cap at 640px. The composer is one lean row: +`+`, text, voice, send. +When Replies or the bench drawer is open, the timeline's right gutter +collapses; the composer keeps a 16px gap from the nearest side panel. +User bubbles retain their 16px inset within the timeline, which fits the +available scroll width. Closing both panels restores centering. Voice mode covers the thread with the worker's mark over a level-reactive glow, a speaking/listening state line, and live captions. Controls: