Skip to content

Commit 6b4270a

Browse files
aadamgoughwaleedlatif1
authored andcommitted
fix(trace-spans): fixed small styling bugs (#1900)
* fix trace spands timeline styling and hover tooltip * fixed invalid dom properties
1 parent 9ae1290 commit 6b4270a

File tree

2 files changed

+51
-73
lines changed

2 files changed

+51
-73
lines changed

apps/sim/app/workspace/[workspaceId]/logs/components/trace-spans/components/trace-span-item.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type React from 'react'
2+
import { useState } from 'react'
23
import { ChevronDown, ChevronRight, Code, Cpu, ExternalLink } from 'lucide-react'
34
import { Tooltip } from '@/components/emcn'
45
import {
@@ -29,9 +30,8 @@ interface TraceSpanItemProps {
2930
onToggle: (spanId: string, expanded: boolean, hasSubItems: boolean) => void
3031
expandedSpans: Set<string>
3132
hasSubItems?: boolean
32-
hoveredPercent?: number | null
33-
hoveredWorkflowMs?: number | null
34-
forwardHover: (clientX: number, clientY: number) => void
33+
onTimelineHover?: (clientX: number, clientY: number, rect: DOMRect) => void
34+
onTimelineLeave?: () => void
3535
gapBeforeMs?: number
3636
gapBeforePercent?: number
3737
showRelativeChip?: boolean
@@ -52,13 +52,14 @@ export function TraceSpanItem({
5252
workflowStartTime,
5353
onToggle,
5454
expandedSpans,
55-
hoveredPercent,
56-
forwardHover,
55+
onTimelineHover,
56+
onTimelineLeave,
5757
gapBeforeMs = 0,
5858
gapBeforePercent = 0,
5959
showRelativeChip = true,
6060
chipVisibility = { model: true, toolProvider: true, tokens: true, cost: true, relative: true },
6161
}: TraceSpanItemProps): React.ReactNode {
62+
const [localHoveredPercent, setLocalHoveredPercent] = useState<number | null>(null)
6263
const spanId = span.id || `span-${span.name}-${span.startTime}`
6364
const expanded = expandedSpans.has(spanId)
6465
const hasChildren = span.children && span.children.length > 0
@@ -427,9 +428,18 @@ export function TraceSpanItem({
427428
style={{ width: 'calc(45% - 73px)', pointerEvents: 'none' }}
428429
>
429430
<div
430-
className='relative h-2 w-full overflow-visible rounded-full bg-accent/30'
431+
className='relative h-2 w-full overflow-hidden bg-accent/30'
431432
style={{ pointerEvents: 'auto' }}
432-
onPointerMove={(e) => forwardHover(e.clientX, e.clientY)}
433+
onPointerMove={(e) => {
434+
const rect = e.currentTarget.getBoundingClientRect()
435+
const clamped = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
436+
setLocalHoveredPercent(clamped * 100)
437+
onTimelineHover?.(e.clientX, e.clientY, rect)
438+
}}
439+
onPointerLeave={() => {
440+
setLocalHoveredPercent(null)
441+
onTimelineLeave?.()
442+
}}
433443
>
434444
{gapBeforeMs > 5 && (
435445
<div
@@ -456,15 +466,13 @@ export function TraceSpanItem({
456466
? 'rgba(148, 163, 184, 0.28)'
457467
: 'rgba(148, 163, 184, 0.32)'
458468
const baseColor = type === 'workflow' ? neutralRail : softenColor(spanColor, isDark)
459-
const isFlatBase = type !== 'workflow'
460469
return (
461470
<div
462471
className='absolute h-full'
463472
style={{
464473
left: `${safeStartPercent}%`,
465474
width: `${safeWidthPercent}%`,
466475
backgroundColor: baseColor,
467-
borderRadius: isFlatBase ? 0 : 9999,
468476
zIndex: 5,
469477
}}
470478
/>
@@ -600,13 +608,15 @@ export function TraceSpanItem({
600608
)
601609
})
602610
})()}
603-
{hoveredPercent != null && (
611+
{localHoveredPercent != null && (
604612
<div
605-
className='pointer-events-none absolute top-[-10px] bottom-[-10px] w-px bg-black/30 dark:bg-white/45'
606-
style={{ left: `${Math.max(0, Math.min(100, hoveredPercent))}%`, zIndex: 12 }}
613+
className='pointer-events-none absolute inset-y-0 w-px bg-black/30 dark:bg-white/45'
614+
style={{
615+
left: `${Math.max(0, Math.min(100, localHoveredPercent))}%`,
616+
zIndex: 12,
617+
}}
607618
/>
608619
)}
609-
<div className='absolute inset-x-0 inset-y-[-12px] cursor-crosshair' />
610620
</div>
611621
</div>
612622

@@ -658,7 +668,8 @@ export function TraceSpanItem({
658668
onToggle={onToggle}
659669
expandedSpans={expandedSpans}
660670
hasSubItems={childHasSubItems}
661-
forwardHover={forwardHover}
671+
onTimelineHover={onTimelineHover}
672+
onTimelineLeave={onTimelineLeave}
662673
gapBeforeMs={childGapMs}
663674
gapBeforePercent={childGapPercent}
664675
showRelativeChip={chipVisibility.relative}
@@ -707,7 +718,8 @@ export function TraceSpanItem({
707718
onToggle={onToggle}
708719
expandedSpans={expandedSpans}
709720
hasSubItems={hasToolCallData}
710-
forwardHover={forwardHover}
721+
onTimelineHover={onTimelineHover}
722+
onTimelineLeave={onTimelineLeave}
711723
showRelativeChip={chipVisibility.relative}
712724
chipVisibility={chipVisibility}
713725
/>

apps/sim/app/workspace/[workspaceId]/logs/components/trace-spans/trace-spans.tsx

Lines changed: 24 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ export function TraceSpans({ traceSpans, totalDuration = 0, onExpansionChange }:
1919
const [expandedSpans, setExpandedSpans] = useState<Set<string>>(new Set())
2020
const [typeFilters, setTypeFilters] = useState<Record<string, boolean>>({})
2121
const containerRef = useRef<HTMLDivElement | null>(null)
22-
const timelineHitboxRef = useRef<HTMLDivElement | null>(null)
23-
const [hoveredPercent, setHoveredPercent] = useState<number | null>(null)
2422
const [hoveredWorkflowMs, setHoveredWorkflowMs] = useState<number | null>(null)
2523
const [hoveredX, setHoveredX] = useState<number | null>(null)
24+
const [hoveredY, setHoveredY] = useState<number | null>(null)
2625
const [containerWidth, setContainerWidth] = useState<number>(0)
2726

2827
type ChipVisibility = {
@@ -151,39 +150,24 @@ export function TraceSpans({ traceSpans, totalDuration = 0, onExpansionChange }:
151150
return traceSpans ? filterTree(traceSpans) : []
152151
}, [traceSpans, effectiveTypeFilters])
153152

154-
const forwardHover = useCallback(
155-
(clientX: number, clientY: number) => {
156-
if (!timelineHitboxRef.current || !containerRef.current) return
157-
158-
const railRect = timelineHitboxRef.current.getBoundingClientRect()
153+
const handleTimelineHover = useCallback(
154+
(clientX: number, clientY: number, timelineRect: DOMRect) => {
155+
if (!containerRef.current) return
159156
const containerRect = containerRef.current.getBoundingClientRect()
157+
const clamped = Math.max(0, Math.min(1, (clientX - timelineRect.left) / timelineRect.width))
160158

161-
const withinX = clientX >= railRect.left && clientX <= railRect.right
162-
const withinY = clientY >= railRect.top && clientY <= railRect.bottom
163-
164-
if (!withinX || !withinY) {
165-
setHoveredPercent(null)
166-
setHoveredWorkflowMs(null)
167-
setHoveredX(null)
168-
return
169-
}
170-
171-
const clamped = Math.max(0, Math.min(1, (clientX - railRect.left) / railRect.width))
172-
setHoveredPercent(clamped * 100)
173159
setHoveredWorkflowMs(workflowStartTime + clamped * actualTotalDuration)
174-
setHoveredX(railRect.left + clamped * railRect.width - containerRect.left)
160+
setHoveredX(timelineRect.left + clamped * timelineRect.width - containerRect.left)
161+
setHoveredY(timelineRect.top - containerRect.top)
175162
},
176163
[actualTotalDuration, workflowStartTime]
177164
)
178165

179-
useEffect(() => {
180-
const handleMove = (event: MouseEvent) => {
181-
forwardHover(event.clientX, event.clientY)
182-
}
183-
184-
window.addEventListener('pointermove', handleMove)
185-
return () => window.removeEventListener('pointermove', handleMove)
186-
}, [forwardHover])
166+
const handleTimelineLeave = useCallback(() => {
167+
setHoveredWorkflowMs(null)
168+
setHoveredX(null)
169+
setHoveredY(null)
170+
}, [])
187171

188172
useEffect(() => {
189173
if (!containerRef.current) return
@@ -203,7 +187,7 @@ export function TraceSpans({ traceSpans, totalDuration = 0, onExpansionChange }:
203187
}
204188

205189
return (
206-
<div className='w-full'>
190+
<div className='relative w-full'>
207191
<div className='mb-2 flex items-center justify-between'>
208192
<div className='flex items-center gap-2'>
209193
<div className='font-medium text-muted-foreground text-xs'>Workflow Execution</div>
@@ -234,11 +218,6 @@ export function TraceSpans({ traceSpans, totalDuration = 0, onExpansionChange }:
234218
<div
235219
ref={containerRef}
236220
className='relative w-full overflow-hidden rounded-md border shadow-sm'
237-
onMouseLeave={() => {
238-
setHoveredPercent(null)
239-
setHoveredWorkflowMs(null)
240-
setHoveredX(null)
241-
}}
242221
>
243222
{filtered.map((span, index) => {
244223
const normalizedSpan = normalizeChildWorkflowSpan(span)
@@ -276,39 +255,26 @@ export function TraceSpans({ traceSpans, totalDuration = 0, onExpansionChange }:
276255
onToggle={handleSpanToggle}
277256
expandedSpans={expandedSpans}
278257
hasSubItems={hasSubItems}
279-
hoveredPercent={hoveredPercent}
280-
hoveredWorkflowMs={hoveredWorkflowMs}
281-
forwardHover={forwardHover}
258+
onTimelineHover={handleTimelineHover}
259+
onTimelineLeave={handleTimelineLeave}
282260
gapBeforeMs={gapMs}
283261
gapBeforePercent={gapPercent}
284262
showRelativeChip={chipVisibility.relative}
285263
chipVisibility={chipVisibility}
286264
/>
287265
)
288266
})}
267+
</div>
289268

290-
{/* Time label for hover (keep top label, row lines render per-row) */}
291-
{hoveredPercent !== null && hoveredX !== null && (
292-
<div
293-
className='-translate-x-1/2 pointer-events-none absolute top-1 rounded bg-popover px-1.5 py-0.5 text-[10px] text-foreground shadow'
294-
style={{ left: hoveredX, zIndex: 20 }}
295-
>
296-
{formatDurationDisplay(Math.max(0, (hoveredWorkflowMs || 0) - workflowStartTime))}
297-
</div>
298-
)}
299-
300-
{/* Hover capture area - aligned to timeline bars, not extending to edge */}
269+
{/* Time label for hover (positioned at top of timeline) */}
270+
{hoveredWorkflowMs !== null && hoveredX !== null && hoveredY !== null && (
301271
<div
302-
ref={timelineHitboxRef}
303-
className='pointer-events-auto absolute inset-y-0 right-[73px] w-[calc(45%-73px)]'
304-
onPointerMove={(e) => forwardHover(e.clientX, e.clientY)}
305-
onPointerLeave={() => {
306-
setHoveredPercent(null)
307-
setHoveredWorkflowMs(null)
308-
setHoveredX(null)
309-
}}
310-
/>
311-
</div>
272+
className='-translate-x-1/2 pointer-events-none absolute rounded border bg-popover px-1.5 py-0.5 font-mono text-[10px] text-foreground shadow-lg'
273+
style={{ left: hoveredX, top: hoveredY, zIndex: 20 }}
274+
>
275+
{formatDurationDisplay(Math.max(0, (hoveredWorkflowMs || 0) - workflowStartTime))}
276+
</div>
277+
)}
312278
</div>
313279
)
314280
}

0 commit comments

Comments
 (0)