fix(ui): give Lens traces a flush toolbar layout (#44972)

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-06 18:38:42 -07:00 • committed by GitHub
parent 950da7c2ec
commit 51c49038ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 70 additions and 50 deletions

View file

@ -631,6 +631,10 @@ describe("AgentTracesPage", () => {
const live = screen.getByRole("button", { name: "Live" });
expect(live).toHaveAttribute("aria-pressed", "true");
expect(trigger).toHaveTextContent("Last 24 hours");
expect(screen.getByRole("combobox", { name: "Filter traces by agent" })).toBeVisible();
expect(screen.getByRole("combobox", { name: "Filter traces by status" })).toBeVisible();
await waitFor(() => expect(screen.getByRole("button", { name: "Refresh traces" })).toBeEnabled());
expect(screen.getByRole("button", { name: "Set up tracing" })).toBeEnabled();
fireEvent.click(trigger);
fireEvent.click(await screen.findByRole("menuitemradio", { name: "Last 7 days" }));
@ -646,6 +650,13 @@ describe("AgentTracesPage", () => {
expect(trigger).not.toHaveTextContent("Last 7 days");
await waitFor(() => expect(vi.mocked(agentTraceListCall).mock.calls.at(-1)?.[0].endMs).toBe(pausedAt));
expect(screen.getByRole("button", { name: "Set up tracing" })).toBeEnabled();
await waitFor(() => expect(screen.getByRole("button", { name: "Refresh traces" })).toBeEnabled());
fireEvent.click(live);
await waitFor(() => expect(live).toHaveAttribute("aria-pressed", "true"));
expect(trigger).toHaveTextContent("Last 7 days");
expect(screen.getByRole("button", { name: "Set up tracing" })).toBeEnabled();
});
it("keeps the time controls on an empty range the user picked, instead of showing onboarding", async () => {

View file

@ -185,8 +185,9 @@ export function AgentTracesSection({
>
<TracingSetupAction available={traces.traces.length > 0} live={live} onSetup={() => setShowSetup(true)} />
<Button
variant="outline"
variant="ghost"
size="icon-sm"
className="h-full w-10 shrink-0 rounded-none border-l"
aria-label="Refresh traces"
title="Refresh traces"
disabled={traces.isFetching}
@ -195,15 +196,13 @@ export function AgentTracesSection({
<RefreshCw className="size-3.5" />
</Button>
{timeControls && (
<div className="h-8 min-w-0 overflow-hidden rounded-md border [&>div]:h-full [&>div]:border-l-0">
<TimeRangeControls
range={range}
zoom={zoom}
onHoursChange={(hours) => changeRange(hours, timeControls.setHours)}
showLive={live}
onLiveChange={timeControls.setLive}
/>
</div>
<TimeRangeControls
range={range}
zoom={zoom}
onHoursChange={(hours) => changeRange(hours, timeControls.setHours)}
showLive={live}
onLiveChange={timeControls.setLive}
/>
)}
</RunsToolbar>
<TraceCounts runs={filtered} />
@ -259,7 +258,13 @@ function TraceFooter({ runs, hasMore }: { runs: readonly TraceSummary[]; hasMore
function TracingSetupAction({ available, live, onSetup }: { available: boolean; live: boolean; onSetup: () => void }) {
if (!available) return null;
return (
<Button variant="outline" size="sm" className="h-8 text-xs" disabled={!live} onClick={onSetup}>
<Button
variant="ghost"
size="sm"
className="h-full shrink-0 rounded-none border-l px-3 text-xs"
disabled={!live}
onClick={onSetup}
>
Set up tracing
</Button>
);

View file

@ -32,10 +32,10 @@ export function RunSearch({ value, onChange, runs, range, busy = false }: RunSea
value={value}
onValueChange={onChange}
label="Search runs"
className="min-w-40 basis-full sm:basis-72 sm:max-w-96"
className="h-10 min-w-0 basis-full sm:w-auto sm:basis-0"
>
<SearchBox.Input
className="h-8 overflow-hidden rounded-md px-3 whitespace-nowrap"
className="h-full overflow-hidden rounded-none border-0 px-3 whitespace-nowrap focus-within:bg-muted/40 focus-within:ring-0 dark:bg-transparent"
placeholder="Search input or trace ID"
busy={busy}
/>

View file

@ -37,43 +37,47 @@ export function RunsToolbar({ query, onQueryChange, runs, range, busy, children
{ value: "error", label: "With errors" },
];
return (
<div className="flex shrink-0 flex-wrap items-center gap-2 border-b bg-card p-2">
<RunSearch value={query} onChange={onQueryChange} runs={runs} range={range} busy={busy} />
<Combobox items={agents} value={agent || null} onValueChange={(name) => setAgent(name ?? "")} autoHighlight>
<ComboboxInput
aria-label="Filter traces by agent"
placeholder="All agents"
showClear={!!agent}
className="h-8 w-48 text-xs"
/>
<ComboboxContent>
<ComboboxEmpty>No matching agents</ComboboxEmpty>
<ComboboxList>
{(name: string) => (
<ComboboxItem key={name} value={name} className="text-xs">
{name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
<Select
items={statuses}
value={status}
onValueChange={(value: "all" | "ok" | "error" | null) => value !== null && setStatus(value)}
>
<SelectTrigger size="sm" className="h-8 min-w-28 text-xs" aria-label="Filter traces by status">
<SelectValue />
</SelectTrigger>
<SelectContent>
{statuses.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
{children && <div className="ml-auto flex max-w-full flex-wrap items-center justify-end gap-2">{children}</div>}
<div className="flex shrink-0 flex-col bg-card">
<div className="flex min-h-10 flex-wrap items-stretch border-b border-border">
<RunSearch value={query} onChange={onQueryChange} runs={runs} range={range} busy={busy} />
{children && <div className="ml-auto flex h-10 max-w-full items-stretch">{children}</div>}
</div>
<div className="flex flex-wrap items-center gap-2 border-b px-3 py-2">
<Combobox items={agents} value={agent || null} onValueChange={(name) => setAgent(name ?? "")} autoHighlight>
<ComboboxInput
aria-label="Filter traces by agent"
placeholder="All agents"
showClear={!!agent}
className="h-8 w-48 max-w-full text-xs"
/>
<ComboboxContent>
<ComboboxEmpty>No matching agents</ComboboxEmpty>
<ComboboxList>
{(name: string) => (
<ComboboxItem key={name} value={name} className="text-xs">
{name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
<Select
items={statuses}
value={status}
onValueChange={(value: "all" | "ok" | "error" | null) => value !== null && setStatus(value)}
>
<SelectTrigger size="sm" className="h-8 min-w-28 text-xs" aria-label="Filter traces by status">
<SelectValue />
</SelectTrigger>
<SelectContent>
{statuses.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
);
}