Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shy-frogs-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/web-shared': patch
---

Replace static trace viewer styles with Tailwind theme utilities.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { ArrowLeft, ArrowRight } from 'lucide-react';
import type { CSSProperties, ReactNode } from 'react';
import type { ReactNode } from 'react';
import {
Fragment,
memo,
Expand All @@ -28,7 +28,7 @@ import {
computeSpanGaps,
computeSpanMarkers,
computeSpanSegments,
getResourceColor,
getResourceClassNames,
getSpanDurationMs,
isSpanErrored,
} from '../utils';
Expand Down Expand Up @@ -62,11 +62,6 @@ const SEGMENT_CLASSES: Record<SegmentStatus, string> = {
received: 'bg-blue-200 border border-blue-500',
};

const TIMELINE_INSET_STYLE: CSSProperties = {
left: TIMELINE_PADDING_PX,
right: TIMELINE_PADDING_PX,
};

const STRIPED_SEGMENT_STATUSES: ReadonlySet<SegmentStatus> = new Set([
'pending',
'running',
Expand Down Expand Up @@ -260,18 +255,18 @@ function BoundaryArrow({
}

function PlainBar({
bg,
border,
className,
label,
}: {
bg: string;
border: string;
className: string;
label: string | null;
}): ReactNode {
return (
<div
className="relative h-6 w-full min-w-1 rounded-[0.25rem] border"
style={{ background: bg, borderColor: border }}
className={cn(
'relative h-6 w-full min-w-1 rounded-[0.25rem] border',
className
)}
>
{label ? <DurationLabel label={label} /> : null}
</div>
Expand Down Expand Up @@ -352,14 +347,13 @@ function SegmentBar({
<div
key={i}
className={cn(
'absolute h-full overflow-hidden rounded-[0.25rem]',
'absolute h-full min-w-px overflow-hidden rounded-[0.25rem]',
SEGMENT_CLASSES[seg.status]
)}
style={{
// 1px gap between adjacent segments, distributed equally.
left: `calc(${seg.leftPct}% + 0.5px)`,
width: `calc(${seg.widthPct}% - 1px)`,
minWidth: 1,
}}
>
{STRIPED_SEGMENT_STATUSES.has(seg.status) ? (
Expand Down Expand Up @@ -457,13 +451,10 @@ const TimelineBar = memo(function TimelineBar({
markers.length > 0 || offscreen.left !== null || offscreen.right !== null;

const isErrored = isSpanErrored(span);
const colors = getResourceColor(span.resource);
const fallbackBg = isErrored
? (colors.errorBg ?? 'var(--ds-red-200)')
: colors.bg;
const fallbackBorder = isErrored
? (colors.errorBorder ?? 'var(--ds-red-500)')
: colors.border;
const colors = getResourceClassNames(span.resource);
const fallbackClassName = isErrored
? (colors.errorClassName ?? 'border-red-500 bg-red-200')
: colors.className;

const totalLabel = formatDurationPrecise(totalDurationMs);
const showTotalLabel =
Expand All @@ -486,7 +477,7 @@ const TimelineBar = memo(function TimelineBar({
)}
onClick={handleClick}
>
<div className="absolute inset-y-0" style={TIMELINE_INSET_STYLE}>
<div className="absolute inset-x-4 inset-y-0">
<div
className="absolute top-1/2 h-6 -translate-y-1/2 overflow-hidden rounded-[0.25rem]"
style={getBarPositionStyle(geometry)}
Expand All @@ -495,15 +486,13 @@ const TimelineBar = memo(function TimelineBar({
<BoundaryArrow direction={geometry.mode.direction} />
) : geometry.mode.kind === 'tiny' ? (
<div
className="h-6 rounded-[0.25rem] border"
style={{ background: fallbackBg, borderColor: fallbackBorder }}
className={cn('h-6 rounded-[0.25rem] border', fallbackClassName)}
/>
) : segments.length > 0 ? (
<SegmentBar segments={segments} showLabels={!hasMarkers} />
) : (
<PlainBar
bg={fallbackBg}
border={fallbackBorder}
className={fallbackClassName}
label={showTotalLabel ? totalLabel : null}
/>
)}
Expand Down Expand Up @@ -612,10 +601,9 @@ const DeltaMeasureLine = memo(function DeltaMeasureLine({
return (
<>
<div
className="absolute h-px bg-amber-800"
className="absolute h-px w-1 bg-amber-800"
style={{
left: Math.min(anchorX, guideX),
width: MEASURE_GUIDE_OUTSET_PX,
top: anchorCenterY,
}}
/>
Expand Down Expand Up @@ -781,8 +769,7 @@ export function Timeline({
>
<div
aria-hidden
className="absolute inset-y-0 pointer-events-none"
style={TIMELINE_INSET_STYLE}
className="pointer-events-none absolute inset-x-4 inset-y-0"
>
{markers.map((marker) =>
// Skip the "0s" origin marker since the left edge already implies it.
Expand All @@ -796,10 +783,7 @@ export function Timeline({
)}
</div>
{hover != null && (
<div
className="absolute inset-y-0 pointer-events-none z-10"
style={TIMELINE_INSET_STYLE}
>
<div className="pointer-events-none absolute inset-x-4 inset-y-0 z-10">
<div
className="absolute top-0 bottom-0 w-px bg-gray-alpha-500"
style={{ left: `${hover.fraction * 100}%` }}
Expand All @@ -824,8 +808,7 @@ export function Timeline({
{altHeld && anchorIndex < 0 && (
<div
aria-hidden
className="absolute inset-y-0 pointer-events-none"
style={TIMELINE_INSET_STYLE}
className="pointer-events-none absolute inset-x-4 inset-y-0"
>
{gapMeasurements.map((gap) => (
<DeltaMeasureLine
Expand All @@ -839,8 +822,7 @@ export function Timeline({
{measurement && (
<div
aria-hidden
className="absolute inset-y-0 pointer-events-none z-20"
style={TIMELINE_INSET_STYLE}
className="pointer-events-none absolute inset-x-4 inset-y-0 z-20"
>
<DeltaMeasureLine {...measurement} timelineWidth={timelineWidth} />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,62 @@
import { Skeleton } from '../../ui/skeleton';
import { DEFAULT_START_PX, paneColTemplate } from './pane-constants';

// Mirrors SplitPane's initial column template so the skeleton lines up with
// the real viewer's first paint.
const COL_TEMPLATE = paneColTemplate(DEFAULT_START_PX);

const ROWS: { id: string; name: number; off: number; bar: number }[] = [
{ id: 'r0', name: 62, off: 0, bar: 72 },
{ id: 'r1', name: 78, off: 6, bar: 48 },
{ id: 'r2', name: 50, off: 10, bar: 55 },
{ id: 'r3', name: 84, off: 18, bar: 30 },
{ id: 'r4', name: 45, off: 18, bar: 42 },
{ id: 'r5', name: 66, off: 34, bar: 38 },
{ id: 'r6', name: 55, off: 41, bar: 25 },
{ id: 'r7', name: 40, off: 50, bar: 30 },
const ROWS = [
{
id: 'r0',
nameClassName: 'w-[62%]',
offsetClassName: 'left-0',
barClassName: 'w-[72%]',
topClassName: 'top-0',
},
{
id: 'r1',
nameClassName: 'w-[78%]',
offsetClassName: 'left-[6%]',
barClassName: 'w-[48%]',
topClassName: 'top-[3.5px]',
},
{
id: 'r2',
nameClassName: 'w-1/2',
offsetClassName: 'left-[10%]',
barClassName: 'w-[55%]',
topClassName: 'top-[7px]',
},
{
id: 'r3',
nameClassName: 'w-[84%]',
offsetClassName: 'left-[18%]',
barClassName: 'w-[30%]',
topClassName: 'top-[10.5px]',
},
{
id: 'r4',
nameClassName: 'w-[45%]',
offsetClassName: 'left-[18%]',
barClassName: 'w-[42%]',
topClassName: 'top-[14px]',
},
{
id: 'r5',
nameClassName: 'w-[66%]',
offsetClassName: 'left-[34%]',
barClassName: 'w-[38%]',
topClassName: 'top-[17.5px]',
},
{
id: 'r6',
nameClassName: 'w-[55%]',
offsetClassName: 'left-[41%]',
barClassName: 'w-1/4',
topClassName: 'top-[21px]',
},
{
id: 'r7',
nameClassName: 'w-2/5',
offsetClassName: 'left-1/2',
barClassName: 'w-[30%]',
topClassName: 'top-[24.5px]',
},
];

const HEADER_MARKERS = ['m0', 'm1', 'm2', 'm3'];
Expand All @@ -36,25 +79,17 @@ export function TraceViewerSkeleton() {
{/* Minimap strip: thin density lines tracing the same shape as the bars */}
<div className="relative h-10 min-h-10 shrink-0 border-b border-gray-alpha-400">
<div className="absolute inset-x-4 top-[6px]">
{ROWS.map((row, index) => (
{ROWS.map((row) => (
<Skeleton
key={row.id}
className="absolute h-[3px] rounded-full"
style={{
left: `${row.off}%`,
width: `${row.bar}%`,
top: index * 3.5,
}}
className={`absolute h-[3px] rounded-full ${row.offsetClassName} ${row.barClassName} ${row.topClassName}`}
/>
))}
</div>
</div>

{/* Header row: search header | divider | timeline header */}
<div
className="shrink-0 grid"
style={{ gridTemplateColumns: COL_TEMPLATE }}
>
<div className="grid shrink-0 grid-cols-[340px_1px_minmax(50px,1fr)]">
<div className="h-10 min-h-10 flex items-center border-b border-gray-alpha-400 pl-4 pr-2 gap-1.5">
<Skeleton className="w-3.5 h-3.5 shrink-0 rounded-sm" />
<Skeleton className="h-3.5 w-40" />
Expand All @@ -70,21 +105,15 @@ export function TraceViewerSkeleton() {
</div>

{/* Content row: event list | gutter | timeline */}
<div
className="grid flex-1 min-h-0 overflow-hidden"
style={{ gridTemplateColumns: COL_TEMPLATE }}
>
<div className="grid min-h-0 flex-1 grid-cols-[340px_1px_minmax(50px,1fr)] overflow-hidden">
{/* Sidebar event rows */}
<div className="block overflow-visible">
<ul className="block divide-y divide-gray-alpha-400 border-b border-gray-alpha-400">
{ROWS.map((row) => (
<li key={row.id} className="h-10 flex items-center pl-4 pr-2">
<div className="flex min-w-0 flex-1 items-center gap-2">
<Skeleton className="w-4 h-4 shrink-0 rounded-sm" />
<Skeleton
className="h-3.5"
style={{ width: `${row.name}%` }}
/>
<Skeleton className={`h-3.5 ${row.nameClassName}`} />
</div>
<Skeleton className="ml-2 h-3.5 w-10 shrink-0" />
</li>
Expand All @@ -101,8 +130,7 @@ export function TraceViewerSkeleton() {
<div key={row.id} className="relative h-10">
<div className="absolute inset-x-4 inset-y-0">
<Skeleton
className="absolute top-1/2 -translate-y-1/2 h-6 rounded-[0.25rem]"
style={{ left: `${row.off}%`, width: `${row.bar}%` }}
className={`absolute top-1/2 h-6 -translate-y-1/2 rounded-[0.25rem] ${row.offsetClassName} ${row.barClassName}`}
/>
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/web-shared/src/components/trace-viewer/icons.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const WorkflowIcon = () => {
return (
<svg
aria-hidden="true"
data-testid="geist-icon"
height="16"
strokeLinejoin="round"
style={{ color: 'currentColor' }}
viewBox="0 0 16 16"
width="16"
>
Expand All @@ -19,12 +19,12 @@ const WorkflowIcon = () => {
const WebhookIcon = () => {
return (
<svg
aria-hidden="true"
data-testid="geist-icon"
height="16"
strokeLinejoin="round"
viewBox="0 0 16 16"
width="16"
style={{ color: 'currentColor' }}
>
<path
fillRule="evenodd"
Expand All @@ -39,12 +39,12 @@ const WebhookIcon = () => {
const SleepIcon = () => {
return (
<svg
aria-hidden="true"
data-testid="geist-icon"
height="16"
strokeLinejoin="round"
viewBox="0 0 16 16"
width="16"
style={{ color: 'currentColor' }}
>
<path
fillRule="evenodd"
Expand All @@ -59,12 +59,12 @@ const SleepIcon = () => {
const StepForwardIcon = () => {
return (
<svg
aria-hidden="true"
data-testid="geist-icon"
height="16"
strokeLinejoin="round"
viewBox="0 0 16 16"
width="16"
style={{ color: 'currentColor' }}
>
<path
fillRule="evenodd"
Expand Down
12 changes: 5 additions & 7 deletions packages/web-shared/src/components/trace-viewer/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
computeSpanMarkers,
computeSpanSegments,
computeTimeMarkers,
getResourceColor,
getResourceClassNames,
} from './utils';

/** Build a high-res timestamp tuple ([seconds, nanoseconds]) for a given ms. */
Expand Down Expand Up @@ -248,13 +248,11 @@ describe('computeTimeMarkers', () => {
});
});

describe('getResourceColor', () => {
describe('getResourceClassNames', () => {
it('uses gray for hooks (passive spans), not amber', () => {
expect(getResourceColor('hook')).toEqual({
bg: 'var(--ds-gray-200)',
border: 'var(--ds-gray-500)',
errorBg: 'var(--ds-red-200)',
errorBorder: 'var(--ds-red-500)',
expect(getResourceClassNames('hook')).toEqual({
className: 'border-gray-500 bg-gray-200',
errorClassName: 'border-red-500 bg-red-200',
});
});
});
Expand Down
Loading
Loading