diff --git a/apps/webapp/app/components/primitives/Headers.tsx b/apps/webapp/app/components/primitives/Headers.tsx
index 53c9d822994..5cd3ec84559 100644
--- a/apps/webapp/app/components/primitives/Headers.tsx
+++ b/apps/webapp/app/components/primitives/Headers.tsx
@@ -1,6 +1,6 @@
import { cn } from "~/utils/cn";
-const headerVariants = {
+export const headerVariants = {
header1: {
text: "font-sans text-2xl leading-5 md:leading-6 lg:leading-7 font-semibold tracking-tight",
spacing: "mb-2",
diff --git a/apps/webapp/app/components/primitives/Table.tsx b/apps/webapp/app/components/primitives/Table.tsx
index dccb26df454..55dd2e4e200 100644
--- a/apps/webapp/app/components/primitives/Table.tsx
+++ b/apps/webapp/app/components/primitives/Table.tsx
@@ -181,7 +181,7 @@ type TableCellBasicProps = {
type TableHeaderCellProps = TableCellBasicProps & {
hiddenLabel?: boolean;
tooltip?: ReactNode;
- /** Extra class merged onto the tooltip content — e.g. widen it past the default max-width. */
+ /** Extra class merged onto the tooltip content. */
tooltipContentClassName?: string;
disableTooltipHoverableContent?: boolean;
/**
diff --git a/apps/webapp/app/components/primitives/Tabs.tsx b/apps/webapp/app/components/primitives/Tabs.tsx
index 1128a9c267f..569e271434f 100644
--- a/apps/webapp/app/components/primitives/Tabs.tsx
+++ b/apps/webapp/app/components/primitives/Tabs.tsx
@@ -3,9 +3,19 @@ import { motion } from "framer-motion";
import { type ReactNode, useRef } from "react";
import { type ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { cn } from "~/utils/cn";
+import { headerVariants } from "./Headers";
import { ShortcutKey } from "./ShortcutKey";
-export type Variants = "underline" | "pipe-divider" | "segmented";
+/** `"title"` names the table below it: header2 text, filter-bar height, underline on the border. */
+export type Variants = "underline" | "pipe-divider" | "segmented" | "title";
+
+/** Shared with `TitleBar` so the tabbed and tab-less bars match. */
+export const TITLE_BAR_CHROME = "flex h-10 shrink-0 gap-x-6 border-b border-grid-bright";
+
+const titleTabLabel = cn(headerVariants.header2.text, "transition duration-200");
+const titleTabIndicator = "h-0.5 w-full bg-indigo-500";
+const titleTabIndicatorIdle =
+ "h-0.5 w-full bg-surface-control-active opacity-0 transition duration-200 group-hover:opacity-100";
export type TabsProps = {
tabs: {
@@ -58,6 +68,10 @@ export function TabContainer({
);
}
+ if (variant === "title") {
+ return
{children}
;
+ }
+
if (variant === "underline") {
return (
{children}
@@ -117,6 +131,39 @@ export function TabLink({
);
}
+ if (variant === "title") {
+ return (
+
+ {({ isActive, isPending }) => {
+ const active = isActive || isPending;
+ return (
+ <>
+
+
+ {children}
+
+
+ {active ? (
+
+ ) : (
+
+ )}
+ >
+ );
+ }}
+
+ );
+ }
+
if (variant === "pipe-divider") {
return (
) {
const ref = useRef(null);
@@ -197,10 +246,13 @@ export function TabButton({
});
}
+ const title = variant === "title";
+
return (
<>
-
-
+
+
{props.children}
{shortcut &&
}
@@ -219,10 +281,15 @@ export function TabButton({
) : (
-
+
)}
>
diff --git a/apps/webapp/app/components/primitives/TitleBar.tsx b/apps/webapp/app/components/primitives/TitleBar.tsx
new file mode 100644
index 00000000000..8b935093b02
--- /dev/null
+++ b/apps/webapp/app/components/primitives/TitleBar.tsx
@@ -0,0 +1,26 @@
+import { type ReactNode } from "react";
+import { cn } from "~/utils/cn";
+import { Header2 } from "./Headers";
+import { TITLE_BAR_CHROME } from "./Tabs";
+
+/**
+ * Names the table below it. Bottom rule only — it doubles as the table's top edge, so render the
+ * table with `showTopBorder={false}`. Use `TabContainer variant="title"` for the tabbed form.
+ */
+export function TitleBar({
+ title,
+ children,
+ className,
+}: {
+ title: ReactNode;
+ /** Right-aligned controls. */
+ children?: ReactNode;
+ className?: string;
+}) {
+ return (
+
+
{title}
+ {children ?
{children}
: null}
+
+ );
+}
diff --git a/apps/webapp/app/components/primitives/Tooltip.tsx b/apps/webapp/app/components/primitives/Tooltip.tsx
index 6f12e408c7f..cb9eaf0364d 100644
--- a/apps/webapp/app/components/primitives/Tooltip.tsx
+++ b/apps/webapp/app/components/primitives/Tooltip.tsx
@@ -42,7 +42,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
- "z-50 max-w-[230px] overflow-hidden animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 focus-visible:outline-hidden",
+ "z-50 overflow-hidden animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 focus-visible:outline-hidden",
variantClasses[variant],
className
)}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents.$agentParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents.$agentParam/route.tsx
index 76beaf297f3..5d9a86e4a25 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents.$agentParam/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents.$agentParam/route.tsx
@@ -234,28 +234,10 @@ export default function Page() {
- {/* Filters — the pinned bar under the NavBar: the TimeFilter and pagination that used to
- be fused with the tabs now live here, above the charts (Queues list pattern). Left and
- right clusters are child divs; the slot's baked justify-between spreads them. */}
-
- {tab === "sessions" ? (
-
-
- {(list) => (list ? : null)}
-
-
- ) : (
-
-
- {(list) => (list ? : null)}
-
-
- )}
-
{/* Activity / LLM spend / Token charts as a fixed-height chart row (three-up), synced +
@@ -315,23 +297,45 @@ export default function Page() {
{/* Tabs alone on their row (Queue detail pattern), then the table below them. */}
-
- setTab("sessions")}
- >
- Sessions
-
- setTab("runs")}
- >
- Runs
-
-
-
+ {/* Single child so Content's gap-2.5 can't separate the bar from the table. */}
+
+
+
+ setTab("sessions")}
+ >
+ Sessions
+
+ setTab("runs")}
+ >
+ Runs
+
+
+
+ {tab === "sessions" ? (
+
+
+ {(list) => (list ? : null)}
+
+
+ ) : (
+
+
+ {(list) => (list ? : null)}
+
+
+ )}
+
+
+
+
) {
- // The table flows in the page-level scroll (MetricsLayout.Root scroll="page"); a sticky header
- // keeps the column labels pinned as the whole column scrolls.
+ // No `stickyHeader` — it drops the table's own overflow-x-auto and the charts scroll with it.
return tab === "sessions" ? (
}>
}>
@@ -367,7 +370,7 @@ function AgentContentArea({
sessions={list.sessions}
filters={list.filters}
hasFilters={list.hasFilters}
- stickyHeader
+ showTopBorder={false}
/>
) : (
@@ -386,7 +389,7 @@ function AgentContentArea({
filters={list.filters}
runs={list.runs}
variant="dimmed"
- stickyHeader
+ showTopBorder={false}
/>
) : (
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents/route.tsx
index f6723ddebaa..9c8c2c8fa5c 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.agents/route.tsx
@@ -1,10 +1,6 @@
import { Outlet } from "@remix-run/react";
-import { PageContainer } from "~/components/layout/AppLayout";
+// No PageContainer — the child pages render their own; nesting two collapses the inner one's height.
export default function Page() {
- return (
-
-
-
- );
+ return ;
}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx
index 6d81de239fe..b3a2b852ed9 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.scheduled.$taskParam/route.tsx
@@ -28,6 +28,7 @@ import {
DialogTrigger,
} from "~/components/primitives/Dialog";
import { Header2 } from "~/components/primitives/Headers";
+import { TitleBar } from "~/components/primitives/TitleBar";
import { InfoPanel } from "~/components/primitives/InfoPanel";
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
import { PaginationControls } from "~/components/primitives/Pagination";
@@ -292,31 +293,15 @@ export default function Page() {
- {/* Top bar — title on the left; actions + TimeFilter + pagination on the right.
- h-10 matches the right-hand sidebar header height. */}
-
-
Runs
+
+
-
- {newRunsCount > 0 ? (
- showNewRunsRef.current()} />
- ) : null}
-
View all runs
@@ -335,11 +320,18 @@ export default function Page() {
>
Bulk replay…
-
-
- {(list) => (list ? : null)}
-
-
+
@@ -363,23 +355,39 @@ export default function Page() {
{/* Runs table */}
-
-
}>
-
}>
- {(list) =>
- list ? (
-
- ) : (
-
- )
- }
-
-
+
+ {/* -mt-px absorbs the spare pixel below the handle's rule, centring the title. */}
+
+ {newRunsCount > 0 ? (
+ showNewRunsRef.current()}
+ />
+ ) : null}
+
+
+ {(list) => (list ? : null)}
+
+
+
+
+
}>
+
}>
+ {(list) =>
+ list ? (
+
+ ) : (
+
+ )
+ }
+
+
+
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx
index 12227dd66fd..15df31370d2 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.tasks.standard.$taskParam/route.tsx
@@ -20,6 +20,7 @@ import { statusColor } from "~/components/primitives/charts/statusColors";
import { CopyableText } from "~/components/primitives/CopyableText";
import { DateTime } from "~/components/primitives/DateTime";
import { Header2 } from "~/components/primitives/Headers";
+import { TitleBar } from "~/components/primitives/TitleBar";
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import * as Property from "~/components/primitives/PropertyTable";
@@ -234,21 +235,8 @@ export default function Page() {
- {/* Top bar — title on the left; TimeFilter + pagination on the right.
- h-10 matches the right-hand sidebar header height. */}
-
-
Runs
-
- {newRunsCount > 0 ? (
- showNewRunsRef.current()} />
- ) : null}
-
-
-
- {(list) => (list ? : null)}
-
-
-
+
+
@@ -263,23 +251,39 @@ export default function Page() {
{/* Runs table */}
-
-
}>
-
}>
- {(list) =>
- list ? (
-
- ) : (
-
- )
- }
-
-
+
+ {/* -mt-px absorbs the spare pixel below the handle's rule, centring the title. */}
+
+ {newRunsCount > 0 ? (
+ showNewRunsRef.current()}
+ />
+ ) : null}
+
+
+ {(list) => (list ? : null)}
+
+
+
+
+
}>
+
}>
+ {(list) =>
+ list ? (
+
+ ) : (
+
+ )
+ }
+
+
+