diff --git a/.changeset/trace-nav-tooltips.md b/.changeset/trace-nav-tooltips.md
new file mode 100644
index 0000000000..7a14ff91f8
--- /dev/null
+++ b/.changeset/trace-nav-tooltips.md
@@ -0,0 +1,5 @@
+---
+'@workflow/web-shared': patch
+---
+
+Add keyboard-shortcut tooltips to the up/down span navigation buttons in the trace viewer detail panel.
diff --git a/packages/web-shared/src/components/new-trace-viewer/trace-viewer.tsx b/packages/web-shared/src/components/new-trace-viewer/trace-viewer.tsx
index 9614a48d5b..f4282c21ba 100644
--- a/packages/web-shared/src/components/new-trace-viewer/trace-viewer.tsx
+++ b/packages/web-shared/src/components/new-trace-viewer/trace-viewer.tsx
@@ -29,6 +29,7 @@ import { useSidebarDataOptional } from '../sidebar/sidebar-data-context';
import type { Trace } from '../trace-viewer/types';
import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
import { IconButton } from '../ui/icon-button';
+import { Kbd } from '../ui/kbd';
import EventList from './components/event-list';
import { SplitPane } from './components/split-pane';
import {
@@ -36,6 +37,12 @@ import {
Timeline,
TimelineHeader,
} from './components/timeline';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '../ui/tooltip';
import { ActiveSpanProvider, useActiveSpan } from './context';
import { DetailPanel } from './detail-panel';
import { searchSpans } from './search';
@@ -154,9 +161,11 @@ function useSelectedSpanInfo(): SelectedSpanInfo | null {
export function NewTraceViewer({ trace }: NewTraceViewerProps): ReactNode {
return (
-
-
-
+
+
+
+
+
);
}
@@ -634,22 +643,42 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
{selectedSpanName}
-
-
-
-
-
-
+
+
+
+
+
+
+ {prevSpanId ? (
+
+ Navigate up
+ K
+
+ ) : null}
+
+
+
+
+
+
+
+ {nextSpanId ? (
+
+ Navigate down
+ J
+
+ ) : null}
+
(
+ ({ className, variant, size, type = 'button', ...props }, ref) => (
- );
-}
+ )
+);
+IconButton.displayName = 'IconButton';
export { iconButtonVariants };
diff --git a/packages/web-shared/src/components/ui/kbd.tsx b/packages/web-shared/src/components/ui/kbd.tsx
new file mode 100644
index 0000000000..f6b6e9f597
--- /dev/null
+++ b/packages/web-shared/src/components/ui/kbd.tsx
@@ -0,0 +1,21 @@
+import type { ReactNode } from 'react';
+import { cn } from '../../lib/utils';
+
+export function Kbd({
+ children,
+ className,
+}: {
+ children: ReactNode;
+ className?: string;
+}): ReactNode {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/web-shared/src/components/ui/tooltip.tsx b/packages/web-shared/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000000..c753b60993
--- /dev/null
+++ b/packages/web-shared/src/components/ui/tooltip.tsx
@@ -0,0 +1,47 @@
+'use client';
+
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
+import * as React from 'react';
+import { cn } from '../../lib/utils';
+
+const TooltipProvider = TooltipPrimitive.Provider;
+
+const Tooltip = TooltipPrimitive.Root;
+
+const TooltipTrigger = TooltipPrimitive.Trigger;
+
+const TooltipContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ showArrow?: boolean;
+ }
+>(
+ (
+ { className, sideOffset = 8, showArrow = true, children, ...props },
+ ref
+ ) => (
+
+
+ {children}
+ {showArrow ? (
+
+ ) : null}
+
+
+ )
+);
+TooltipContent.displayName = TooltipPrimitive.Content.displayName;
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };