Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/junior-dashboard/e2e/conversation-details.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 14 additions & 0 deletions packages/junior-dashboard/e2e/conversations-attention.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions packages/junior-dashboard/e2e/conversations-mobile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -527,7 +533,10 @@ export function ConversationStats(props: {
<dl className="m-0 grid min-w-0 gap-2 text-xs leading-snug">
{stats.map((item) => (
<div
className="flex min-w-0 items-baseline justify-between gap-4"
className={cn(
"flex min-w-0 items-baseline justify-between gap-4",
item.mobileOnly && "md:hidden",
)}
key={item.key}
>
<dt className="shrink-0 text-dashboard-text-muted">{item.label}</dt>
Expand Down Expand Up @@ -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<ConversationStatItem | undefined> = [
// The desktop title bar shows the location. Mobile hides that bar.
location
? {
content: location,
key: "location",
label: "Location",
mobileOnly: true,
}
: undefined,
sourceTask
? {
content: <SourceTask sourceTask={sourceTask} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<DetailsRow
Expand Down
Loading