Flow Studio
Virtual Scrolling
The Logs tab uses windowed (virtual) rendering — only the rows visible in the viewport are in the DOM. The logs array can grow to 100,000+ entries without degrading rendering performance, because React never renders more than a few hundred rows at a time.
Why Virtual Scrolling
Without virtual scrolling, a workflow that emits 50,000 log entries would create 50,000 DOM rows simultaneously — this would freeze the browser. With virtual scrolling, only the ~30 rows visible in the panel are rendered; the rest are calculated but not mounted.
VirtualLogList Implementation
// VirtualLogList.tsx — uses @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';
interface VirtualLogListProps {
logs: LogEntry[];
}
export function VirtualLogList({ logs }: VirtualLogListProps) {
const containerRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count : logs.length,
getScrollElement: () => containerRef.current,
estimateSize: () => 24, // Estimated row height in px
overscan : 10 // Render 10 extra rows above/below viewport
});
return (
<div ref={containerRef} style={{ height: '100%', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map(item => (
<div
key={logs[item.index].logId}
style={{
position : 'absolute',
top : `${item.start}px`,
height : `${item.size}px`,
width : '100%'
}}
>
<LogRow entry={logs[item.index]} />
</div>
))}
</div>
</div>
);
}
Auto-Scroll to Bottom
// Auto-scroll to newest entries during live run
useEffect(() => {
if (isLive && !userHasScrolledUp) {
virtualizer.scrollToIndex(logs.length - 1, { behavior: 'smooth' });
}
}, [logs.length]);
Filtering happens before the virtualizer sees the data. The Logs tab filters the
logs array in memory first (by level, nodeId, text search), then passes the filtered result to VirtualLogList. The virtualizer always receives a pre-filtered slice — this keeps rendering logic clean.