diff --git a/packages/junior-dashboard/e2e/conversation-details.spec.ts b/packages/junior-dashboard/e2e/conversation-details.spec.ts index 0b4845545e..8259cb8122 100644 --- a/packages/junior-dashboard/e2e/conversation-details.spec.ts +++ b/packages/junior-dashboard/e2e/conversation-details.spec.ts @@ -131,6 +131,16 @@ test("opens conversation details beside the chat", async ({ await page.keyboard.press("Escape"); await expect(details).toBeHidden(); await expect(detailsButton).toBeFocused(); + // With the `⋯` menu open, Escape closes the menu and leaves details open. + await detailsButton.click(); + await expect(details).toBeVisible(); + await page.getByRole("button", { name: "Conversation menu" }).click(); + await expect(page.getByRole("button", { name: "Event log" })).toBeVisible(); + await page.keyboard.press("Escape"); + await expect(page.getByRole("button", { name: "Event log" })).toBeHidden(); + await expect(details).toBeVisible(); + await detailsButton.click(); + await expect(details).toBeHidden(); await page.setViewportSize({ width: 390, height: 844 }); await page.getByRole("button", { name: "Conversation menu" }).click(); diff --git a/packages/junior-dashboard/e2e/conversations-attention.spec.ts b/packages/junior-dashboard/e2e/conversations-attention.spec.ts index 21e7ae12a5..3f656f1f76 100644 --- a/packages/junior-dashboard/e2e/conversations-attention.spec.ts +++ b/packages/junior-dashboard/e2e/conversations-attention.spec.ts @@ -225,6 +225,20 @@ test.describe("conversation time sections", () => { await expect(details).toHaveCount(0); await expect(page).not.toHaveURL(/review=/); + // With details already open, the chip still expands the question. + await page + .getByRole("button", { name: "Conversation details", exact: true }) + .click(); + const question = details + .getByRole("region", { name: "Needs you" }) + .getByText("Merge the checkout fix?"); + await expect(question).toBeHidden(); + await chip.click(); + await expect(question).toBeVisible(); + await details + .getByRole("button", { name: "Close conversation details" }) + .click(); + const today = sidebar.getByRole("button", { name: "Today" }); await today.click(); await expect(today).toHaveAttribute("aria-expanded", "false"); diff --git a/packages/junior-dashboard/e2e/conversations-mobile.spec.ts b/packages/junior-dashboard/e2e/conversations-mobile.spec.ts index ff9cc50224..32fd694180 100644 --- a/packages/junior-dashboard/e2e/conversations-mobile.spec.ts +++ b/packages/junior-dashboard/e2e/conversations-mobile.spec.ts @@ -264,6 +264,8 @@ test("opens and closes a conversation on mobile", async ({ // The sheet moves focus inside it. await expect(details.locator(":focus")).toHaveCount(1); await page.keyboard.press("Shift+Tab"); + // Mobile hides the title bar, so the sheet names the location. + await expect(details.getByText("Location", { exact: true })).toBeVisible(); await expect(details.locator(":focus")).toHaveCount(1); await screenshot(page, "conversation-details-sheet", { lightTheme: true, diff --git a/packages/junior-dashboard/src/client/conversations/ConversationMeta.tsx b/packages/junior-dashboard/src/client/conversations/ConversationMeta.tsx index a9b96f928e..5c10dc37ea 100644 --- a/packages/junior-dashboard/src/client/conversations/ConversationMeta.tsx +++ b/packages/junior-dashboard/src/client/conversations/ConversationMeta.tsx @@ -514,7 +514,13 @@ export function hasConversationStats(props: { return conversationStatItems(props).length > 0; } -type ConversationStatItem = { content: ReactNode; key: string; label: string }; +type ConversationStatItem = { + content: ReactNode; + key: string; + label: string; + /** Shown below `md` only, where the title bar is hidden. */ + mobileOnly?: boolean; +}; /** Render runtime, source, token, and cost metadata as a labeled list. */ export function ConversationStats(props: { @@ -527,7 +533,10 @@ export function ConversationStats(props: {
{stats.map((item) => (
{item.label}
@@ -579,7 +588,17 @@ function conversationStatItems(props: { const childAgentTotals = props.detail?.childAgentTotals ?? props.conversation.childAgentTotals; const lastActivityAt = props.conversation.lastSeenAt; + const location = conversationLocation(props.conversation, props.detail); const rawStats: Array = [ + // The desktop title bar shows the location. Mobile hides that bar. + location + ? { + content: location, + key: "location", + label: "Location", + mobileOnly: true, + } + : undefined, sourceTask ? { content: , diff --git a/packages/junior-dashboard/src/client/conversations/ConversationOverflowMenu.tsx b/packages/junior-dashboard/src/client/conversations/ConversationOverflowMenu.tsx index faf7aafec4..be2823f86c 100644 --- a/packages/junior-dashboard/src/client/conversations/ConversationOverflowMenu.tsx +++ b/packages/junior-dashboard/src/client/conversations/ConversationOverflowMenu.tsx @@ -22,15 +22,19 @@ export function ConversationOverflowMenu(props: { if (!menuRef.current?.contains(event.target as Node)) setOpen(false); } + // Capture runs before the details panel's document listener, and + // preventDefault tells it that this Escape is taken. function closeOnEscape(event: KeyboardEvent) { - if (event.key === "Escape") setOpen(false); + if (event.key !== "Escape" || event.defaultPrevented) return; + event.preventDefault(); + setOpen(false); } document.addEventListener("pointerdown", closeOnOutsideClick); - document.addEventListener("keydown", closeOnEscape); + document.addEventListener("keydown", closeOnEscape, true); return () => { document.removeEventListener("pointerdown", closeOnOutsideClick); - document.removeEventListener("keydown", closeOnEscape); + document.removeEventListener("keydown", closeOnEscape, true); }; }, [open]); diff --git a/packages/junior-dashboard/src/client/review-desk/ConversationReviewItems.tsx b/packages/junior-dashboard/src/client/review-desk/ConversationReviewItems.tsx index 569975cde4..9c82227870 100644 --- a/packages/junior-dashboard/src/client/review-desk/ConversationReviewItems.tsx +++ b/packages/junior-dashboard/src/client/review-desk/ConversationReviewItems.tsx @@ -69,6 +69,10 @@ function ReviewItemRow(props: { item: ReviewItemPayload; }) { const [expanded, setExpanded] = useState(props.defaultExpanded); + // Details can already be open when a sidebar review chip highlights it. + useEffect(() => { + if (props.defaultExpanded) setExpanded(true); + }, [props.defaultExpanded]); return ( <>