diff --git a/e2e/coach-layout.spec.ts b/e2e/coach-layout.spec.ts
index 07b36d83e..af710b8a4 100644
--- a/e2e/coach-layout.spec.ts
+++ b/e2e/coach-layout.spec.ts
@@ -358,6 +358,12 @@ test.describe("Coach page frame", () => {
const aside = (await panel(page).boundingBox())!;
expect(aside.x).toBeGreaterThanOrEqual(open.threadX + open.thread);
await expectPanelFlush(page, width, 900);
+ // Open, the toggle stands beside the panel's leading edge.
+ const openToggle = (await toggle(page).boundingBox())!;
+ expect(openToggle.x + openToggle.width).toBeLessThanOrEqual(aside.x);
+ expect(aside.x - (openToggle.x + openToggle.width)).toBeLessThanOrEqual(
+ 24,
+ );
await expectNoSidewaysScroll(page);
await shot(page, testInfo, `coach-frame-${width}-open`);
@@ -366,6 +372,13 @@ test.describe("Coach page frame", () => {
await waitForWidth(panel(page), 0);
await expect(toggle(page)).toHaveAttribute("aria-expanded", "false");
const closed = await columnWidths(page);
+ // Closed, it returns to the trailing edge of the top bar.
+ await expect
+ .poll(async () => {
+ const box = (await toggle(page).boundingBox())!;
+ return Math.round(width - (box.x + box.width));
+ })
+ .toBeLessThanOrEqual(24);
expect(Math.abs(closed.thread - open.thread)).toBeLessThanOrEqual(1);
expect(Math.abs(closed.composer - open.composer)).toBeLessThanOrEqual(1);
// Closed, nothing inside the panel is reachable.
diff --git a/src/components/insights/coach-panel/conversations-panel.tsx b/src/components/insights/coach-panel/conversations-panel.tsx
index eda23e3df..c2c323764 100644
--- a/src/components/insights/coach-panel/conversations-panel.tsx
+++ b/src/components/insights/coach-panel/conversations-panel.tsx
@@ -37,7 +37,8 @@ import { useDeleteCoachConversationWithUndo } from "./use-coach";
* Below 1280 px it is a sheet from the right that opens when asked and
* closes again when a conversation is picked or a new chat starts.
*
- * The toggle sits at the trailing edge of the top bar. The panel header
+ * The toggle sits at the trailing edge of the top bar and, while the docked
+ * panel is open, beside the panel's leading edge. The panel header
* carries the title, a link to Plans, the settings gear and, in the sheet,
* a close button. The round New chat button sits at the bottom right.
*
@@ -245,12 +246,27 @@ export function ConversationsPanel({
aria-label={toggleLabel}
title={toggleLabel}
data-slot="coach-panel-toggle"
- className="text-muted-foreground hover:text-foreground size-11"
+ className={cn(
+ "text-muted-foreground hover:text-foreground size-11",
+ // Docked and open, the toggle keeps the panel's width as its
+ // right margin, so it stays beside the panel's leading edge and
+ // travels with it as the panel opens and shuts.
+ "transition-[margin] duration-200 ease-linear motion-reduce:transition-none",
+ docked && dockedOpen && "mr-72",
+ )}
>
+ {/* Mirrored, so the chevron points the way the panel moves on a
+ click: right to shut it, left to open it. */}
{expanded ? (
-
+
) : (
-
+
)}