Skip to content

Commit d03f84b

Browse files
waleedlatif1claude
andcommitted
fix(logs): trace view chevron padding, section state leak, and tab-scoped keyboard nav
- Pad tree rows from panel edge so the root chevron isn't visually clipped. - Key DetailCodeSection by label so collapse state belongs to the section purpose, preventing isOpen from leaking across span changes when positional slots happened to align. - Ignore log-to-log arrow-key nav while the Trace tab is active so TraceView owns span navigation; filter inputs keep native caret movement. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 38625cc commit d03f84b

2 files changed

Lines changed: 38 additions & 25 deletions

File tree

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
3838
const DEFAULT_BLOCK_COLOR = '#6b7280'
3939
const TREE_PANE_WIDTH = 300
4040
const INDENT_PX = 12
41+
const ROW_BASE_PADDING_LEFT = 12
4142
const MIN_BAR_PCT = 0.5
4243

4344
interface TraceViewProps {
@@ -344,7 +345,7 @@ const TraceTreeRow = memo(function TraceTreeRow({
344345
>
345346
<div
346347
className='flex min-w-0 items-center gap-1.5 pt-1 pr-2'
347-
style={{ paddingLeft: 8 + depth * INDENT_PX }}
348+
style={{ paddingLeft: ROW_BASE_PADDING_LEFT + depth * INDENT_PX }}
348349
>
349350
{canExpand ? (
350351
<button
@@ -733,7 +734,6 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
733734

734735
return (
735736
<div className='flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-3.5 pt-3 pb-4'>
736-
{/* Header */}
737737
<div className='flex items-start gap-2'>
738738
{!isIterationType(span.type) && (
739739
<div
@@ -777,7 +777,6 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
777777
</div>
778778
</div>
779779

780-
{/* Metadata block */}
781780
{metaEntries.length > 0 && (
782781
<div className='flex flex-col gap-1.5 rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2.5 py-2 dark:bg-transparent'>
783782
{metaEntries.map((m) => (
@@ -786,26 +785,34 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
786785
</div>
787786
)}
788787

789-
{/* Content sections */}
788+
{/* Keys by label: without them, React reused a single DetailCodeSection
789+
across span changes and carried isOpen between sections with different
790+
labels — a collapsed Output on one span appeared as a collapsed Input
791+
on the next. */}
790792
{span.input !== undefined && span.input !== null && (
791-
<DetailCodeSection label='Input' data={span.input} />
793+
<DetailCodeSection key='input' label='Input' data={span.input} />
792794
)}
793795
{span.output !== undefined && span.output !== null && (
794796
<DetailCodeSection
797+
key={isDirectError ? 'error' : 'output'}
795798
label={isDirectError ? 'Error' : 'Output'}
796799
data={span.output}
797800
isError={isDirectError}
798801
/>
799802
)}
800-
{span.thinking && <DetailCodeSection label='Thinking' data={span.thinking} />}
803+
{span.thinking && <DetailCodeSection key='thinking' label='Thinking' data={span.thinking} />}
801804
{span.modelToolCalls && span.modelToolCalls.length > 0 && (
802-
<DetailCodeSection label='Tool calls' data={span.modelToolCalls} />
805+
<DetailCodeSection key='tool-calls' label='Tool calls' data={span.modelToolCalls} />
803806
)}
804807
{span.errorMessage && (
805-
<DetailCodeSection label='Error message' data={span.errorMessage} isError />
808+
<DetailCodeSection
809+
key='error-message'
810+
label='Error message'
811+
data={span.errorMessage}
812+
isError
813+
/>
806814
)}
807815

808-
{/* Raw timing footer */}
809816
{Number.isFinite(startedAt) && Number.isFinite(endedAt) && startedAt > 0 && endedAt > 0 && (
810817
<div className='flex items-center justify-between font-medium text-[var(--text-tertiary)] text-caption'>
811818
<span>Started {new Date(startedAt).toISOString()}</span>
@@ -823,7 +830,6 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
823830
* follow block-by-block and segment-by-segment what happened and why.
824831
*/
825832
export const TraceView = memo(function TraceView({ traceSpans }: TraceViewProps) {
826-
const containerRef = useRef<HTMLDivElement>(null)
827833
const [searchQuery, setSearchQuery] = useState('')
828834

829835
const { normalizedSpans, allIds, totalDuration, runStartMs, firstRootId, blockCount } =
@@ -898,10 +904,13 @@ export const TraceView = memo(function TraceView({ traceSpans }: TraceViewProps)
898904
const handleCollapseAll = useCallback(() => setExpandedNodes(new Set()), [])
899905

900906
useEffect(() => {
901-
const container = containerRef.current
902-
if (!container) return
903907
const handler = (e: KeyboardEvent) => {
904-
if (!container.contains(document.activeElement)) return
908+
// Ignore while typing in inputs / contentEditable (filter box, etc.).
909+
const target = e.target as HTMLElement | null
910+
if (target) {
911+
const tag = target.tagName
912+
if (tag === 'INPUT' || tag === 'TEXTAREA' || target.isContentEditable) return
913+
}
905914
if (!selectedId) return
906915
const currentIndex = flatList.findIndex((entry) => getSpanId(entry.span) === selectedId)
907916
if (currentIndex === -1) return
@@ -950,7 +959,7 @@ export const TraceView = memo(function TraceView({ traceSpans }: TraceViewProps)
950959
}
951960

952961
return (
953-
<div ref={containerRef} className='-mx-3.5 flex h-full min-h-0 flex-col'>
962+
<div className='-mx-3.5 flex h-full min-h-0 flex-col'>
954963
{/* Header strip */}
955964
<div className='flex items-center gap-2 border-[var(--border)] border-b px-3.5 pb-2'>
956965
<span

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -345,22 +345,26 @@ export const LogDetails = memo(function LogDetails({
345345
onClose()
346346
}
347347

348-
if (isOpen) {
349-
if (e.key === 'ArrowUp' && hasPrev && onNavigatePrev) {
350-
e.preventDefault()
351-
onNavigatePrev()
352-
}
353-
354-
if (e.key === 'ArrowDown' && hasNext && onNavigateNext) {
355-
e.preventDefault()
356-
onNavigateNext()
357-
}
348+
if (!isOpen) return
349+
350+
// When the Trace tab is active, arrow keys belong to TraceView's own
351+
// span-navigation handler. Log-to-log navigation should not hijack them.
352+
if (resolvedTab === 'trace') return
353+
354+
if (e.key === 'ArrowUp' && hasPrev && onNavigatePrev) {
355+
e.preventDefault()
356+
onNavigatePrev()
357+
}
358+
359+
if (e.key === 'ArrowDown' && hasNext && onNavigateNext) {
360+
e.preventDefault()
361+
onNavigateNext()
358362
}
359363
}
360364

361365
window.addEventListener('keydown', handleKeyDown)
362366
return () => window.removeEventListener('keydown', handleKeyDown)
363-
}, [isOpen, onClose, hasPrev, hasNext, onNavigatePrev, onNavigateNext])
367+
}, [isOpen, onClose, hasPrev, hasNext, onNavigatePrev, onNavigateNext, resolvedTab])
364368

365369
const formattedTimestamp = log ? formatDate(log.createdAt) : null
366370

0 commit comments

Comments
 (0)