diff --git a/docs/components/geistdocs/mobile-docs-bar.tsx b/docs/components/geistdocs/mobile-docs-bar.tsx
new file mode 100644
index 0000000000..cd253f9e49
--- /dev/null
+++ b/docs/components/geistdocs/mobile-docs-bar.tsx
@@ -0,0 +1,81 @@
+'use client';
+
+import type { TableOfContents } from 'fumadocs-core/toc';
+import { useState } from 'react';
+import { IconFileText } from '@/components/geistcn-fallbacks/geistcn-assets/icons/icon-file-text';
+import { IconMenuAlt } from '@/components/geistcn-fallbacks/geistcn-assets/icons/icon-menu-alt';
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import { useSidebarContext } from '@/hooks/geistdocs/use-sidebar';
+import { cn } from '@/lib/utils';
+
+interface MobileDocsBarProps {
+ toc?: TableOfContents;
+}
+
+export const MobileDocsBar = ({ toc }: MobileDocsBarProps) => {
+ const { setIsOpen: setSidebarOpen } = useSidebarContext();
+ const [tocOpen, setTocOpen] = useState(false);
+
+ return (
+
+
+
+ {toc && toc.length > 0 && (
+ <>
+
+
+
+
+
+
+ On this page
+
+
+ Table of contents for the current page.
+
+
+
+
+
+ >
+ )}
+
+ );
+};
diff --git a/docs/components/geistdocs/mobile-menu.tsx b/docs/components/geistdocs/mobile-menu.tsx
index 42b0593137..e5d9cd20a1 100644
--- a/docs/components/geistdocs/mobile-menu.tsx
+++ b/docs/components/geistdocs/mobile-menu.tsx
@@ -1,20 +1,156 @@
'use client';
-import { MenuIcon } from 'lucide-react';
-import { useSidebarContext } from '@/hooks/geistdocs/use-sidebar';
-import { Button } from '../ui/button';
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import { useEffect, useRef, useState } from 'react';
+import { IconArrowUpRight } from '@/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right';
+import { nav } from '@/geistdocs';
+import { cn } from '@/lib/utils';
+import { SearchButton } from './search';
-export const MobileMenu = () => {
- const { isOpen, setIsOpen } = useSidebarContext();
+function NavLink({
+ href,
+ external,
+ onClick,
+ children,
+}: {
+ href: string;
+ external?: boolean;
+ onClick?: () => void;
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+ {external && (
+
+ )}
+
+ );
+}
+function MobileMenuButton({
+ expanded,
+ onClick,
+}: {
+ expanded: boolean;
+ onClick: () => void;
+}) {
return (
-
+ );
+}
+
+export const MobileMenu = () => {
+ const [show, setShow] = useState(false);
+ const pathname = usePathname();
+ const previousPathname = useRef(pathname);
+
+ // Close on route change
+ useEffect(() => {
+ if (pathname !== previousPathname.current) {
+ setShow(false);
+ previousPathname.current = pathname;
+ }
+ }, [pathname]);
+
+ // Close on escape
+ useEffect(() => {
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape' && show) {
+ setShow(false);
+ }
+ };
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, [show]);
+
+ // Lock scroll when open
+ useEffect(() => {
+ document.body.style.overflow = show ? 'hidden' : '';
+ return () => {
+ document.body.style.overflow = '';
+ };
+ }, [show]);
+
+ const close = () => setShow(false);
+
+ return (
+ <>
+
setShow(!show)} />
+
+ {/* Backdrop */}
+ {/* biome-ignore lint/a11y/noNoninteractiveElementInteractions: backdrop dismiss */}
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: backdrop dismiss */}
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents: backdrop dismiss */}
+
+
+ {/* Popover */}
+
+ {/* Search */}
+
+
+
+
+ {/* Navigation */}
+
+
+ >
);
};
diff --git a/docs/components/geistdocs/navbar-logo.tsx b/docs/components/geistdocs/navbar-logo.tsx
new file mode 100644
index 0000000000..8e02e86acb
--- /dev/null
+++ b/docs/components/geistdocs/navbar-logo.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import Link from 'next/link';
+import type { ComponentType, ReactNode } from 'react';
+import { IconSlashForward } from '@/components/geistcn-fallbacks/geistcn-assets/icons/icon-slash-forward';
+import { LogoAiElements } from '@/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-elements';
+import { LogoChatSdk } from '@/components/geistcn-fallbacks/geistcn-assets/logos/logo-chat-sdk';
+import { LogoFlagsSdk } from '@/components/geistcn-fallbacks/geistcn-assets/logos/logo-flags-sdk';
+import { LogoIconVercel } from '@/components/geistcn-fallbacks/geistcn-assets/logos/logo-icon-vercel';
+import { LogoStreamdown } from '@/components/geistcn-fallbacks/geistcn-assets/logos/logo-streamdown';
+import { LogoVercelOss } from '@/components/geistcn-fallbacks/geistcn-assets/logos/logo-vercel-oss';
+import {
+ NavigationMenu,
+ NavigationMenuContent,
+ NavigationMenuItem,
+ NavigationMenuLink,
+ NavigationMenuList,
+ NavigationMenuTrigger,
+} from '@/components/ui/navigation-menu';
+import { cn } from '@/lib/utils';
+
+const OSS_PRODUCT_LINKS: {
+ href: string;
+ logo: ComponentType<{ height: number }>;
+ height: number;
+}[] = [
+ { href: 'https://flags-sdk.dev/', logo: LogoFlagsSdk, height: 20 },
+ { href: 'https://chat-sdk.dev/', logo: LogoChatSdk, height: 20 },
+ { href: 'https://elements.ai-sdk.dev/', logo: LogoAiElements, height: 12 },
+ { href: 'https://streamdown.ai/', logo: LogoStreamdown, height: 17 },
+];
+
+type NavbarLogoProps = {
+ className?: string;
+ /** Where the logo links to (defaults to "/") */
+ href?: string;
+} & (
+ | {
+ variant: 'oss';
+ /** Logo shown in the trigger (e.g. LogoAiSdk) */
+ logo: ReactNode;
+ }
+ | {
+ variant?: 'standard';
+ /** Logo shown after the slash (e.g. product name) */
+ logo: ReactNode;
+ }
+);
+
+export function NavbarLogo({
+ className,
+ href = '/',
+ ...props
+}: NavbarLogoProps) {
+ const isOss = props.variant === 'oss';
+
+ return (
+
+
+ {isOss ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
+
+
+ {isOss ? (
+
+
+
+
+ e.stopPropagation()}
+ >
+ {props.logo}
+
+
+
+
+ {OSS_PRODUCT_LINKS.map(
+ ({ href, logo: ProductLogo, height }) => (
+ -
+
+
+
+
+
+
+ )
+ )}
+
+
+
+
+
+ ) : (
+
+ {props.logo}
+
+ )}
+
+ );
+}
diff --git a/docs/components/geistdocs/navbar.tsx b/docs/components/geistdocs/navbar.tsx
index ad88a7c92c..97f68ac2d4 100644
--- a/docs/components/geistdocs/navbar.tsx
+++ b/docs/components/geistdocs/navbar.tsx
@@ -1,27 +1,19 @@
-import { SiVercel } from '@icons-pack/react-simple-icons';
-import { DynamicLink } from 'fumadocs-core/dynamic-link';
import { basePath, Logo, nav, suggestions } from '@/geistdocs';
import { Chat } from './chat';
import { DesktopMenu } from './desktop-menu';
-import { SlashIcon } from './icons';
import { MobileMenu } from './mobile-menu';
+import { NavbarLogo } from './navbar-logo';
import { SearchButton } from './search';
export const Navbar = () => (
-
-
-
-
-
-
-
-
-
-
+
+
+
+ } variant="oss" />
+
-
-
-
+
+
diff --git a/docs/components/geistdocs/search.tsx b/docs/components/geistdocs/search.tsx
index 71ee415d41..1a2b7d7ad7 100644
--- a/docs/components/geistdocs/search.tsx
+++ b/docs/components/geistdocs/search.tsx
@@ -18,10 +18,10 @@ import { cn } from '@/lib/utils';
import { Button } from '../ui/button';
import { Kbd } from '../ui/kbd';
-type SearchButtonProps = {
+interface SearchButtonProps {
className?: string;
onClick?: () => void;
-};
+}
export const SearchDialog = ({
basePath,
@@ -48,7 +48,7 @@ export const SearchDialog = ({
-
+
);
@@ -60,7 +60,11 @@ export const SearchButton = ({ className, onClick }: SearchButtonProps) => {
return (
{
@@ -72,7 +76,9 @@ export const SearchButton = ({ className, onClick }: SearchButtonProps) => {
variant="outline"
>
Search...
- ⌘K
+
+ ⌘K
+
);
};
diff --git a/docs/components/geistdocs/sidebar.tsx b/docs/components/geistdocs/sidebar.tsx
index 8726f845bc..60c9b8bb8b 100644
--- a/docs/components/geistdocs/sidebar.tsx
+++ b/docs/components/geistdocs/sidebar.tsx
@@ -63,7 +63,7 @@ export const Sidebar = () => {
{renderSidebarList(root.children)}
-
+
Mobile Menu
diff --git a/docs/components/ui/card.tsx b/docs/components/ui/card.tsx
index fe84886e8f..49ef778cff 100644
--- a/docs/components/ui/card.tsx
+++ b/docs/components/ui/card.tsx
@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ | {
+ primary: string;
+ secondary?: string;
+ };
+
+const DEFAULT_COLORS: ColorObject = {
+ // Level: Poor
+ '0': 'var(--ds-red-800)',
+ // Level: Okay
+ '33': 'var(--ds-amber-700)',
+ // Level: Good
+ '66': 'var(--ds-green-700)',
+};
+
+export interface GaugeProps {
+ /** The value for the gauge. This needs to be a number between 0 and 100 representing 0% to 100%. */
+ value: number;
+ /** Determines whether the value is displayed as a number. */
+ showValue?: boolean;
+ /** Either color stops the gauge should be based on `value`, or static colors. */
+ colors?: ColorObject;
+ size: 'tiny' | 'small' | 'medium' | 'large';
+ /* Decides which arc should be prioritized. Use `equal` when displaying a ratio and `primary` when displaying a single percentage. `equal` leads to both arcs being the exact same size at 50. */
+ arcPriority?: 'equal' | 'primary';
+ placeholder?: boolean;
+ indeterminate?: boolean;
+ className?: string;
+ children?: React.ReactNode;
+ style?: CSSProperties;
+}
+
+const GaugeSizeMapping: Record
= {
+ tiny: 20,
+ small: 32,
+ medium: 64,
+ large: 128,
+};
+
+const GaugeGapMapping: Record = {
+ tiny: 3,
+ small: 2,
+ medium: 1,
+ large: 1,
+};
+
+const FontConfigMapping: Record<
+ GaugeProps['size'],
+ {
+ size: number;
+ weight: number;
+ } | null
+> = {
+ tiny: null,
+ small: {
+ size: 11,
+ weight: 500,
+ },
+ medium: {
+ size: 18,
+ weight: 500,
+ },
+ large: {
+ size: 32,
+ weight: 600,
+ },
+};
+
+const CIRCLE_SIZE = 100;
+
+export function Gauge({
+ colors = DEFAULT_COLORS,
+ value,
+ placeholder: _placeholder,
+ indeterminate,
+ className,
+ arcPriority = 'primary',
+ style,
+ size: sizeLabel,
+ children,
+ showValue = false,
+ ...props
+}: GaugeProps): JSX.Element {
+ const placeholder = _placeholder || false;
+ const size = GaugeSizeMapping[sizeLabel];
+ const textConfig = FontConfigMapping[sizeLabel];
+
+ // Gets the color for the current value based on the color map.
+ const [primaryColor, secondaryColor] = useMemo<
+ Readonly<[string | undefined, string | undefined]>
+ >(() => {
+ if ('primary' in colors) {
+ return [colors.primary, colors.secondary] as const;
+ }
+
+ const stops = Object.keys(colors).map(Number);
+ if (stops.length === 0) return [undefined, undefined] as const;
+
+ const c = stops.filter((stop) => value >= stop).pop();
+
+ return [c !== undefined ? colors[c] : undefined, undefined] as const;
+ }, [colors, value]);
+
+ // Calculates arc & circle values based on the current size & value.
+ const arcValues = useMemo(() => {
+ const strokeWidth = size <= GaugeSizeMapping.tiny ? 15 : 10;
+
+ const circumference = 2 * Math.PI * (CIRCLE_SIZE / 2 - strokeWidth / 2);
+ const pxToPercent = CIRCLE_SIZE / circumference;
+
+ const circleProps: React.SVGProps = {
+ cx: CIRCLE_SIZE / 2,
+ cy: CIRCLE_SIZE / 2,
+ r: CIRCLE_SIZE / 2 - strokeWidth / 2,
+ strokeWidth,
+ strokeDashoffset: 0,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ };
+
+ const baseGap = Math.round(strokeWidth * pxToPercent);
+
+ let segmentGap = baseGap + GaugeGapMapping[sizeLabel];
+
+ if (value === 0 || value >= 100) {
+ segmentGap = 0;
+ }
+
+ const offsetFactor = arcPriority === 'equal' ? 0.5 : 0;
+
+ const primaryStrokePercent = value - segmentGap * 2 * offsetFactor;
+
+ const diffToOnePercent = Math.max(1 - primaryStrokePercent, 0);
+
+ const secondaryStrokePercent =
+ 100 - value - segmentGap * 2 * (1 - offsetFactor) - diffToOnePercent;
+
+ return {
+ circleProps,
+ circumference,
+ segmentGap,
+ primaryStrokePercent,
+ secondaryStrokePercent,
+ offsetFactor,
+ };
+ }, [arcPriority, size, sizeLabel, value]);
+
+ const {
+ circleProps,
+ circumference,
+ offsetFactor,
+ segmentGap,
+ primaryStrokePercent,
+ secondaryStrokePercent,
+ } = arcValues;
+
+ return (
+
+
+
+ {children || showValue ? (
+
+ {children ||
+ (textConfig && (
+
+ {value}
+
+ ))}
+
+ ) : null}
+
+ );
+}
diff --git a/docs/components/ui/navigation-menu.tsx b/docs/components/ui/navigation-menu.tsx
index c4e3a221ff..6afb7f2f5a 100644
--- a/docs/components/ui/navigation-menu.tsx
+++ b/docs/components/ui/navigation-menu.tsx
@@ -1,30 +1,42 @@
-import { cva } from 'class-variance-authority';
-import { ChevronDownIcon } from 'lucide-react';
+import * as React from 'react';
import { NavigationMenu as NavigationMenuPrimitive } from 'radix-ui';
-import type * as React from 'react';
+import { cva } from 'class-variance-authority';
+import { IconChevronDownSmall } from '@/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-down-small';
import { cn } from '@/lib/utils';
+function isTouchDevice() {
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
+}
+
+function isKeyboardClick(event: React.MouseEvent) {
+ return event.detail === 0;
+}
+
function NavigationMenu({
className,
children,
viewport = true,
+ viewportClassName,
...props
}: React.ComponentProps & {
viewport?: boolean;
+ viewportClassName?: string;
}) {
return (
{children}
- {viewport && }
+ {viewport && (
+
+ )}
);
}
@@ -59,25 +71,35 @@ function NavigationMenuItem({
}
const navigationMenuTriggerStyle = cva(
- 'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1'
+ 'group inline-flex relative px-3 gap-1 w-max items-center justify-center text-sm transition-colors text-gray-900 hover:text-gray-1000 focus:text-gray-1000 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:text-gray-1000 data-[state=open]:text-gray-1000'
);
function NavigationMenuTrigger({
className,
children,
+ onClick,
...props
}: React.ComponentProps) {
return (
{
+ if (!isTouchDevice() && !isKeyboardClick(event)) {
+ event.preventDefault();
+ }
+ onClick?.(event);
+ }}
{...props}
>
- {children}{' '}
-
+
);
}
@@ -90,8 +112,8 @@ function NavigationMenuContent({
) {
+}: React.ComponentProps & {
+ wrapperClassName?: string;
+}) {
return (
-
+
);
}
diff --git a/docs/components/ui/skeleton.tsx b/docs/components/ui/skeleton.tsx
index 02535dc4f5..6f2095f01e 100644
--- a/docs/components/ui/skeleton.tsx
+++ b/docs/components/ui/skeleton.tsx
@@ -4,9 +4,11 @@ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
+ >
+
+
);
}
diff --git a/docs/components/ui/tabs.tsx b/docs/components/ui/tabs.tsx
index 36e9eb674c..1c4838cee1 100644
--- a/docs/components/ui/tabs.tsx
+++ b/docs/components/ui/tabs.tsx
@@ -1,20 +1,28 @@
'use client';
import { Tabs as TabsPrimitive } from 'radix-ui';
+import { createContext, use } from 'react';
import type * as React from 'react';
import { cn } from '@/lib/utils';
+type TabsVariant = 'default' | 'underline';
+
+const TabsVariantContext = createContext('default');
+
function Tabs({
className,
+ variant = 'default',
...props
-}: React.ComponentProps) {
+}: React.ComponentProps & { variant?: TabsVariant }) {
return (
-
+
+
+
);
}
@@ -22,11 +30,14 @@ function TabsList({
className,
...props
}: React.ComponentProps) {
+ const variant = use(TabsVariantContext);
return (
) {
+ const variant = use(TabsVariantContext);
return (
('releases');
+ const [mode, setMode] = useState("releases");
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
- useEffect(() => {
- if (!open) return;
+ // Client-side cache: serve stale data instantly on mode switch,
+ // then refresh in background.
+ const cache = useRef>({});
- async function fetchHistory() {
+ const fetchHistory = useCallback(async () => {
+ const cacheKey = `${worldId}:${metricName}:${mode}`;
+ const cached = cache.current[cacheKey];
+
+ // Show cached data immediately if available
+ if (cached) {
+ setData(cached);
+ } else {
setLoading(true);
- setError(null);
+ }
+ setError(null);
- try {
- const res = await fetch(
- `/api/benchmark-history?worldId=${encodeURIComponent(worldId)}&metricName=${encodeURIComponent(metricName)}&mode=${mode}`
- );
+ try {
+ const res = await fetch(
+ `/api/benchmark-history?worldId=${encodeURIComponent(worldId)}&metricName=${encodeURIComponent(metricName)}&mode=${mode}`,
+ );
- if (!res.ok) {
- throw new Error(`Failed to fetch: ${res.status}`);
- }
+ if (!res.ok) {
+ throw new Error(`Failed to fetch: ${res.status}`);
+ }
- const history = await res.json();
- setData(history);
- } catch (err) {
+ const history = await res.json();
+ cache.current[cacheKey] = history;
+ setData(history);
+ } catch (err) {
+ // Only show error if we have no cached data to fall back on
+ if (!cached) {
setError(
- err instanceof Error ? err.message : 'Failed to fetch history'
+ err instanceof Error ? err.message : "Failed to fetch history",
);
- } finally {
- setLoading(false);
- setHasLoadedOnce(true);
}
+ } finally {
+ setLoading(false);
+ setHasLoadedOnce(true);
}
+ }, [worldId, metricName, mode]);
+ useEffect(() => {
+ if (!open) return;
fetchHistory();
- }, [open, worldId, metricName, mode]);
+ }, [open, fetchHistory]);
// Check if this is a stream benchmark (has ttfb data)
const isStreamBenchmark = data.some((d) => d.ttfb !== undefined);
// Check if we have workflow min/max data (for showing range)
const hasWorkflowRange = data.some(
- (d) => d.workflowMin !== undefined && d.workflowMax !== undefined
+ (d) => d.workflowMin !== undefined && d.workflowMax !== undefined,
);
// Calculate stats for the chart - use workflowTime when available
@@ -141,10 +153,10 @@ export function BenchmarkHistoryChart({
})()
: null;
- const modeLabel = mode === 'releases' ? 'releases' : 'commits';
+ const modeLabel = mode === "releases" ? "releases" : "commits";
const getGitHubUrl = (point: BenchmarkHistoryPoint) => {
- if (mode === 'releases') {
+ if (mode === "releases") {
return `https://github.com/vercel/workflow/releases/tag/workflow@${point.label}`;
}
return `https://github.com/vercel/workflow/commit/${point.commit}`;
@@ -157,19 +169,20 @@ export function BenchmarkHistoryChart({
{metricName}
-
+
Performance history over the last {data.length} {modeLabel}
-
+
-
+
{/* Tabs for switching between releases and commits */}
setMode(v as HistoryMode)}
+ variant="underline"
className="mb-4"
>
-
+
Releases
@@ -179,319 +192,377 @@ export function BenchmarkHistoryChart({
- {/* Show spinner only on initial load when there's no data */}
- {loading && data.length === 0 && (
-
-
+ {/* Loading skeleton matching the stat cards + chart layout */}
+ {!hasLoadedOnce && data.length === 0 && (
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+
)}
{error && (
-
+
{error}
)}
- {!error && data.length === 0 && !loading && (
-
+ {!error && data.length === 0 && hasLoadedOnce && (
+
No historical data available
)}
- {!error && data.length > 0 && stats && (
-
- {/* Stats summary */}
-
-
-
- {stats.samples ?? '—'}
-
-
Samples
-
-
-
- {formatTime(stats.current)}
-
-
Time
-
- {isStreamBenchmark && stats.ttfb !== undefined && (
-
-
- {formatTime(stats.ttfb)}
-
-
TTFB
-
- )}
- {isStreamBenchmark && stats.slurp !== undefined && (
-
-
- {formatTime(stats.slurp)}
-
-
Slurp
-
- )}
-
+ {!error &&
+ data.length > 0 &&
+ stats &&
+ (() => {
+ const statCards = [
+ { label: "Samples", value: String(stats.samples ?? "—") },
+ { label: "Time", value: formatTime(stats.current) },
+ ...(isStreamBenchmark && stats.ttfb !== undefined
+ ? [
+ {
+ label: "TTFB",
+ value: formatTime(stats.ttfb),
+ colorClass: "text-green-900 dark:text-green-600",
+ },
+ ]
+ : []),
+ ...(isStreamBenchmark && stats.slurp !== undefined
+ ? [
+ {
+ label: "Slurp",
+ value: formatTime(stats.slurp),
+ colorClass: "text-purple-900",
+ },
+ ]
+ : []),
+ {
+ label: "Trend",
+ value: `${Math.abs(stats.trendPercent).toFixed(1)}%`,
+ colorClass:
+ stats.trendPercent < -1
+ ? "text-green-900 dark:text-green-600"
+ : stats.trendPercent > 1
+ ? "text-red-900 dark:text-red-800"
+ : "text-muted-foreground",
+ icon:
+ stats.trendPercent < -1
+ ? "down"
+ : stats.trendPercent > 1
+ ? "up"
+ : "flat",
+ },
+ ];
+
+ return (
+
+ {/* Stats summary */}
1
- ? 'text-red-600 dark:text-red-500'
- : 'text-muted-foreground'
- }`}
+ className={`grid gap-3 mb-6 ${isStreamBenchmark ? "grid-cols-5" : "grid-cols-3"}`}
>
- {stats.trendPercent < -1 ? (
-
- ) : stats.trendPercent > 1 ? (
-
- ) : (
-
- )}
- {Math.abs(stats.trendPercent).toFixed(1)}%
+ {statCards.map((stat) => (
+
+
+ {stat.value}
+ {stat.icon === "down" && (
+
+ )}
+ {stat.icon === "up" && (
+
+ )}
+ {stat.icon === "flat" && (
+
+ )}
+
+
+ {stat.label}
+
+
+ ))}
-
Trend
-
-
- {/* Chart */}
-
- ({
- ...d,
- // Use workflowTime as the display value
- displayTime: d.workflowTime ?? 0,
- // For range area: only show if we have workflow min/max
- displayMin: d.workflowMin ?? 0,
- rangeHeight:
- d.workflowMin !== undefined && d.workflowMax !== undefined
- ? d.workflowMax - d.workflowMin
- : 0,
- }))}
- margin={{ top: 10, right: 10, left: 0, bottom: 0 }}
- >
-
-
-
+ ({
+ ...d,
+ // Use workflowTime as the display value
+ displayTime: d.workflowTime ?? 0,
+ // For range area: only show if we have workflow min/max
+ displayMin: d.workflowMin ?? 0,
+ rangeHeight:
+ d.workflowMin !== undefined &&
+ d.workflowMax !== undefined
+ ? d.workflowMax - d.workflowMin
+ : 0,
+ }))}
+ margin={{ top: 10, right: 10, left: 0, bottom: 0 }}
+ >
+
+
+
+
+
+
+
-
-
-
-
- formatTime(value)}
- tickLine={false}
- axisLine={false}
- tickMargin={8}
- tick={{ fontSize: 10 }}
- width={55}
- domain={[0, 'auto']}
- />
- {
- if (!active || !payload?.length) return null;
- const point = payload[0]
- ?.payload as BenchmarkHistoryPoint & {
- displayTime: number;
- };
- if (!point) return null;
- const hasRange =
- point.workflowMin !== undefined &&
- point.workflowMax !== undefined;
- return (
-
-
{label}
-
-
-
-
- Time:
-
-
- {point.workflowTime !== undefined
- ? formatTime(point.workflowTime)
- : '—'}
-
-
- {point.ttfb !== undefined && (
-
-
-
- TTFB:
-
-
- {formatTime(point.ttfb)}
-
-
- )}
- {point.slurp !== undefined && (
-
-
-
- Slurp:
-
-
- {formatTime(point.slurp)}
-
-
- )}
- {hasRange && (
-
- Range: {formatTime(point.workflowMin!)} –{' '}
- {formatTime(point.workflowMax!)}
+
formatTime(value)}
+ tickLine={false}
+ axisLine={false}
+ tickMargin={8}
+ tick={{ fontSize: 10 }}
+ width={55}
+ domain={[0, "auto"]}
+ />
+ {
+ if (!active || !payload?.length) return null;
+ const point = payload[0]
+ ?.payload as BenchmarkHistoryPoint & {
+ displayTime: number;
+ };
+ if (!point) return null;
+ const hasRange =
+ point.workflowMin !== undefined &&
+ point.workflowMax !== undefined;
+ return (
+
+
+ {label}
- )}
- {point.samples && (
-
- Samples: {point.samples}
+
+
+
+
+ Time:
+
+
+ {point.workflowTime !== undefined
+ ? formatTime(point.workflowTime)
+ : "—"}
+
+
+ {point.ttfb !== undefined && (
+
+
+
+ TTFB:
+
+
+ {formatTime(point.ttfb)}
+
+
+ )}
+ {point.slurp !== undefined && (
+
+
+
+ Slurp:
+
+
+ {formatTime(point.slurp)}
+
+
+ )}
+ {hasRange && (
+
+ Range: {formatTime(point.workflowMin!)} –{" "}
+ {formatTime(point.workflowMax!)}
+
+ )}
+ {point.samples && (
+
+ Samples: {point.samples}
+
+ )}
+
+ {new Date(
+ point.timestamp,
+ ).toLocaleDateString()}
+
+
+ Click point to open on GitHub
+
- )}
-
- {new Date(point.timestamp).toLocaleDateString()}
-
- Click point to open on GitHub
-
-
-
- );
- }}
- />
- {/* Only show range area if we have workflow min/max data */}
- {hasWorkflowRange && (
- <>
- {/* Invisible base area up to min value */}
-
- {/* Visible area from min to max (stacked on top of min) */}
-
+ {/* Invisible base area up to min value */}
+
+ {/* Visible area from min to max (stacked on top of min) */}
+
+ >
+ )}
+ {/* Fill area under the line — only for single-line charts */}
+ {!isStreamBenchmark && (
+
+ )}
+ {/* Line showing workflow time (primary metric) */}
+ {
+ const point = (
+ event as unknown as {
+ payload: BenchmarkHistoryPoint;
+ }
+ ).payload;
+ if (point) {
+ window.open(getGitHubUrl(point), "_blank");
+ }
+ },
+ }}
/>
- >
- )}
- {/* Line showing workflow time (primary metric) */}
- {
- const point = (
- event as unknown as { payload: BenchmarkHistoryPoint }
- ).payload;
- if (point) {
- window.open(getGitHubUrl(point), '_blank');
- }
- },
- }}
- />
- {/* TTFB line for stream benchmarks */}
- {isStreamBenchmark && (
-
- )}
- {/* Slurp line for stream benchmarks */}
- {isStreamBenchmark && (
-
- )}
-
-
+ {/* TTFB line for stream benchmarks */}
+ {isStreamBenchmark && (
+
+ )}
+ {/* Slurp line for stream benchmarks */}
+ {isStreamBenchmark && (
+
+ )}
+
+
-
- Lower is better. Results may vary due to CI environment, network
- conditions, and other factors.
-
-
- )}
+
+ Lower is better. Results may vary due to CI environment,
+ network conditions, and other factors.
+
+
+ );
+ })()}
diff --git a/docs/components/worlds/WorldCardSimple.tsx b/docs/components/worlds/WorldCardSimple.tsx
index 7f6aa6a07c..0af6fed2d5 100644
--- a/docs/components/worlds/WorldCardSimple.tsx
+++ b/docs/components/worlds/WorldCardSimple.tsx
@@ -1,15 +1,8 @@
'use client';
-import {
- AlertCircle,
- BadgeCheck,
- CheckCircle2,
- Clock,
- HeartHandshake,
- ShieldCheck,
- XCircle,
-} from 'lucide-react';
+import { BadgeCheck, ShieldCheck } from 'lucide-react';
import Link from 'next/link';
+import { Badge } from '@/components/ui/badge';
import {
Card,
CardContent,
@@ -17,12 +10,12 @@ import {
CardHeader,
CardTitle,
} from '@/components/ui/card';
+import { Gauge } from '@/components/ui/gauge';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
-import { cn } from '@/lib/utils';
import type { World } from './types';
interface WorldCardSimpleProps {
@@ -30,34 +23,7 @@ interface WorldCardSimpleProps {
world: World;
}
-const statusConfig = {
- passing: {
- label: 'Passing',
- icon: CheckCircle2,
- className: 'bg-green-500/10 text-green-600 border-green-500/20',
- },
- partial: {
- label: 'Partial',
- icon: AlertCircle,
- className: 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20',
- },
- failing: {
- label: 'Failing',
- icon: XCircle,
- className: 'bg-red-500/10 text-red-600 border-red-500/20',
- },
- pending: {
- label: 'Pending',
- icon: Clock,
- className: 'bg-muted text-muted-foreground',
- },
-};
-
export function WorldCardSimple({ id, world }: WorldCardSimpleProps) {
- const e2eStatus = world.e2e?.status || 'pending';
- const config = statusConfig[e2eStatus];
- const StatusIcon = config.icon;
-
// Use nextjs-turbopack data for scoring if available, otherwise fall back to total
const turbopackData = world.e2e?.nextjsTurbopack;
@@ -76,39 +42,21 @@ export function WorldCardSimple({ id, world }: WorldCardSimpleProps) {
? Math.round((effectivePassed / effectiveTotal) * 100)
: 0;
- // E2E color based on pass rate (muted)
- const e2eColorClass = !world.e2e
- ? 'text-muted-foreground'
- : displayProgress === 100
- ? 'text-green-600/70'
- : displayProgress >= 75
- ? 'text-yellow-600/70'
- : 'text-red-600/70';
-
return (
-
-
+
+
{world.name}
- {world.type === 'official' ? (
-
-
-
-
-
- Maintained by Vercel
-
-
- ) : (
+ {world.type === 'official' && (
-
+
- Maintained by the community
+ Maintained by Vercel
)}
@@ -119,57 +67,56 @@ export function WorldCardSimple({ id, world }: WorldCardSimpleProps) {
-
+
{world.description}
- {/* Stats footer band */}
-
- {/* E2E - left */}
+ {/* Stats footer */}
+
+ {/* E2E with gauge */}
-
-
- E2E
-
- {world.e2e ? `${displayProgress}%` : '—'}
-
-
-
-
- E2E Test Suite Coverage
-
-
- {/* Encryption - right */}
-
-
-
-
+ = 75
+ ? { primary: 'var(--ds-green-700)' }
+ : displayProgress >= 50
+ ? { primary: 'var(--ds-amber-700)' }
+ : { primary: 'var(--ds-red-700)' }
+ }
/>
- Encrypted
-
- {world.features.includes('encryption') ? 'Yes' : 'No'}
+
+ E2E:{` `}
+
+ {world.e2e ? `${displayProgress}%` : '—'}
+
- End-to-end user data encryption
+ E2E Test Suite Coverage
+ {/* Encryption badge */}
+ {world.features.includes('encryption') && (
+
+
+
+
+ Encrypted
+
+
+
+ End-to-end user data encryption
+
+
+ )}
diff --git a/docs/components/worlds/WorldDetailHero.tsx b/docs/components/worlds/WorldDetailHero.tsx
index 8b8d4e077e..3163b2faa2 100644
--- a/docs/components/worlds/WorldDetailHero.tsx
+++ b/docs/components/worlds/WorldDetailHero.tsx
@@ -46,17 +46,17 @@ const statusConfig = {
passing: {
label: 'Passing',
icon: CheckCircle2,
- className: 'text-green-500',
+ className: 'text-green-900',
},
partial: {
label: 'Partial',
icon: AlertCircle,
- className: 'text-yellow-500',
+ className: 'text-amber-900',
},
failing: {
label: 'Failing',
icon: XCircle,
- className: 'text-red-500',
+ className: 'text-red-900',
},
pending: {
label: 'Pending',
@@ -115,7 +115,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
: null);
return (
-
+
{/* Breadcrumbs */}
@@ -134,15 +134,15 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
{/* Main content grid */}
-
+
{/* Left side - Title and description */}
-
+
{world.name}
{world.type === 'official' ? (
-
+
Maintained by Vercel
@@ -151,7 +151,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
) : (
-
+
Maintained by the community
@@ -205,7 +205,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
{/* Right side - Quick links */}
-
+
{/* E2E Tests */}
@@ -214,7 +214,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
>
{e2e ? (
@@ -243,7 +243,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
>
{world.benchmark10SeqMs !== null ? (
@@ -276,7 +276,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
>
-
+
npm
@@ -288,7 +288,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
>
-
+
Source
)}
@@ -301,7 +301,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
>
-
+
Example
)}
@@ -314,7 +314,7 @@ export function WorldDetailHero({ id, world }: WorldDetailHeroProps) {
href="/docs/how-it-works/encryption"
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
>
-
+
E2E Encrypted
diff --git a/docs/components/worlds/WorldTestingPerformance.tsx b/docs/components/worlds/WorldTestingPerformance.tsx
index fb6cfea817..f24c876c01 100644
--- a/docs/components/worlds/WorldTestingPerformance.tsx
+++ b/docs/components/worlds/WorldTestingPerformance.tsx
@@ -1,16 +1,16 @@
-'use client';
+"use client";
-import { useState } from 'react';
import {
- CheckCircle2,
AlertCircle,
- XCircle,
+ CheckCircle2,
Clock,
- TrendingUp,
Info,
Timer,
-} from 'lucide-react';
-import { Badge } from '@/components/ui/badge';
+ TrendingUp,
+ XCircle,
+} from "lucide-react";
+import { useState } from "react";
+import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
@@ -18,15 +18,15 @@ import {
TableHead,
TableHeader,
TableRow,
-} from '@/components/ui/table';
+} from "@/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
-} from '@/components/ui/tooltip';
-import { cn } from '@/lib/utils';
-import { formatTime, type World } from './types';
-import { BenchmarkHistoryChart } from './BenchmarkHistoryChart';
+} from "@/components/ui/tooltip";
+import { cn } from "@/lib/utils";
+import { BenchmarkHistoryChart } from "./BenchmarkHistoryChart";
+import { formatTime, type World } from "./types";
const TimeColumnHeader = () => (