refactor(ui): show the log drawer's sidebar toggle only where it has a row

Putting the toggle in the drawer header unconditionally stranded it on its own
line: the model row renders empty for a log that names no model or provider, so
the chevron sat alone above the request id.

The sidebar keeps the toggle whenever it is open, in its own header. Collapsed,
the toggle moves into the drawer header and joins the model row, or the request
id row when there is no model to join. Shared between both through
SidebarToggle so the two call sites cannot drift.
This commit is contained in:
Yuneng Jiang 2026-08-29 11:48:04 -07:00
parent b27a1a13a2
commit 20e6d6457a
No known key found for this signature in database
4 changed files with 108 additions and 13 deletions

View file

@ -0,0 +1,67 @@
import { screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { render } from "../../../../tests/test-utils";
import type { LogEntry } from "../columns";
import { DrawerHeader } from "./DrawerHeader";
const logEntry = (overrides: Partial<LogEntry>): LogEntry =>
({
request_id: "170d64ea-69f0-431a-be72-332f8f78c18a",
api_key: "key-1",
team_id: "team-1",
model: "gpt-4o",
model_id: "model-1",
custom_llm_provider: "openai",
call_type: "acompletion",
spend: 0.01,
total_tokens: 10,
prompt_tokens: 5,
completion_tokens: 5,
startTime: "2026-07-07T09:50:13Z",
endTime: "2026-07-07T09:50:14Z",
cache_hit: "false",
messages: [],
response: {},
...overrides,
}) as LogEntry;
const renderHeader = (log: LogEntry, isSidebarCollapsed: boolean) =>
render(
<DrawerHeader
log={log}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebar={vi.fn()}
statusLabel="Failure"
statusColor="error"
environment="default"
/>,
);
const expandToggle = () => screen.getByLabelText("Expand trace sidebar");
describe("DrawerHeader sidebar toggle", () => {
it("stays out of the header while the sidebar owns it", () => {
renderHeader(logEntry({}), false);
expect(screen.queryByLabelText("Expand trace sidebar")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Collapse trace sidebar")).not.toBeInTheDocument();
});
it("shares the model row once the sidebar is collapsed", () => {
renderHeader(logEntry({}), true);
const row = expandToggle().parentElement as HTMLElement;
expect(within(row).getByText("gpt-4o")).toBeInTheDocument();
});
it("falls back to the request id row when the log names no model", () => {
renderHeader(logEntry({ model: "", custom_llm_provider: "" }), true);
const row = expandToggle().parentElement as HTMLElement;
expect(within(row).getByText("170d64ea-69f0-431a-be72-332f8f78c18a")).toBeInTheDocument();
});
});

View file

@ -1,5 +1,5 @@
import { useState } from "react";
import { Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Copy, X } from "lucide-react";
import { Check, ChevronDown, ChevronUp, Copy, X } from "lucide-react";
import moment from "moment";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@ -7,6 +7,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
import { LogEntry } from "../columns";
import { AutoRouterTag } from "@/components/shared/table_cells";
import { ClassifyTag } from "./ClassifyTag";
import { SidebarToggle } from "./SidebarToggle";
import { getProviderLogoAndName } from "../../provider_info_helpers";
import {
DRAWER_HEADER_PADDING,
@ -47,6 +48,8 @@ export function DrawerHeader({
}: DrawerHeaderProps) {
const provider = log.custom_llm_provider || "";
const providerInfo = provider ? getProviderLogoAndName(provider) : null;
const showToggleWithProvider = isSidebarCollapsed && Boolean(providerInfo || log.model);
const showToggleWithRequestId = isSidebarCollapsed && !showToggleWithProvider;
return (
<div
@ -61,15 +64,7 @@ export function DrawerHeader({
>
{/* Row 0: Model + Provider with Logo */}
<div className="flex items-center gap-2" style={{ marginBottom: SPACING_MEDIUM }}>
<Button
variant="ghost"
size="icon-xs"
onClick={onToggleSidebar}
className="shrink-0"
aria-label={isSidebarCollapsed ? "Expand trace sidebar" : "Collapse trace sidebar"}
>
{isSidebarCollapsed ? <ChevronRight className="size-4" /> : <ChevronLeft className="size-4" />}
</Button>
{showToggleWithProvider && <SidebarToggle isCollapsed onToggle={onToggleSidebar} />}
<ModelProviderSection
model={log.model}
modelGroup={log.model_group}
@ -81,8 +76,15 @@ export function DrawerHeader({
{/* Row 1: Request ID + Actions */}
<div
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: SPACING_MEDIUM }}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 4,
marginBottom: SPACING_MEDIUM,
}}
>
{showToggleWithRequestId && <SidebarToggle isCollapsed onToggle={onToggleSidebar} />}
<RequestIdSection requestId={log.request_id} />
<NavigationSection onPrevious={onPrevious} onNext={onNext} onClose={onClose} />
</div>

View file

@ -8,6 +8,7 @@ import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "../constants";
import { getEventDisplayName } from "../utils";
import { ClassifyTag } from "./ClassifyTag";
import { DrawerHeader } from "./DrawerHeader";
import { SidebarToggle } from "./SidebarToggle";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { LogDetailContent, GuardrailJumpLink } from "./LogDetailContent";
import { sessionSpendLogsCall } from "../../networking";
@ -314,8 +315,12 @@ export function LogDetailsDrawer({
<div style={{ height: "100%" }} className="flex">
{!isSidebarCollapsed && (
<div className="border-r border-border bg-muted flex flex-col shrink-0" style={{ width: SIDEBAR_WIDTH_PX }}>
<div className="px-3 py-2 border-b border-border bg-card">
<div className="min-w-0">
<div className="flex items-start gap-1 py-2 pl-1 pr-3 border-b border-border bg-card">
<SidebarToggle
isCollapsed={isSidebarCollapsed}
onToggle={() => setIsSidebarCollapsed((collapsed) => !collapsed)}
/>
<div className="min-w-0 flex-1">
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
{isSessionMode ? "Session" : "Trace"}
</div>

View file

@ -0,0 +1,21 @@
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
export interface SidebarToggleProps {
isCollapsed: boolean;
onToggle: () => void;
}
export function SidebarToggle({ isCollapsed, onToggle }: SidebarToggleProps) {
return (
<Button
variant="ghost"
size="icon-xs"
onClick={onToggle}
className="shrink-0"
aria-label={isCollapsed ? "Expand trace sidebar" : "Collapse trace sidebar"}
>
{isCollapsed ? <ChevronRight className="size-4" /> : <ChevronLeft className="size-4" />}
</Button>
);
}