DreamLake

Waterfall

A timeline waterfall for run traces and profiling data. The component splits into two synced panes: a searchable tree of events on the left, and a zoomable time axis on the right. Each row is positioned against the same clock, so parent/child timing and overlap read at a glance.

Rows come in two shapes, decided by their fields:

  • Duration bars — rows with startTime + duration render as a bar spanning that interval.
  • Instant events — rows with a bare time render as a single dot on the axis.

The hierarchy is built from parentId (a root row has parentId: null). Collapsible parents can be expanded and collapsed in the tree; the search bar at the top of the left pane filters rows by label (with case-sensitive and regex toggles).

Interaction

The time axis manages its own viewport, so the whole timeline is navigable without any external state:

  • Zoom — Shift/Alt + mouse wheel zooms the time axis in and out around the cursor. Tune the feel with zoomFactor, and clamp the range with minWindow / maxWindow. Zoom-out is unlimited by default. The bottom duration drag control uses the same limits. Pass a finite maxWindow, such as maxWindow={3600}, to cap the visible window at one hour.
  • Resize — drag the divider to resize the entry pane. Focus it and press Left/Right to move by 10px, Shift+Left/Right by 50px, or Home/End to reach its bounds. resizable={false} hides the divider.
  • Pan — drag horizontally (or use the navigation controls at the bottom) to scroll through time while staying at the current zoom.
  • Mark — click anywhere on a bar or the axis to drop a temporal marker (T1, T2, …) for measuring against. Click a marker again to remove it.
  • Hover — hovering a row in either pane highlights its counterpart in the other. The highlight is continuous across the divider: only the outside corners are rounded, including when hovering a whole subtree.

The initial window fits the complete logData time span with 5% padding on each side, subject to minWindow and maxWindow. A single instant event starts in a one-second window; empty data uses a 33-second fallback. Fitting happens on mount, so search, expansion, and subsequent data rerenders preserve your pan and zoom.

panelWidth sets the initial requested width; changing it resets that request. User resizing is managed internally and calls onPanelWidthChange. Container resizing clamps the visible width without discarding the request: the timeline keeps 120px, or half the width in containers narrower than 240px. The preferred minimum is relaxed when necessary.

Example

A small agent run trace: a root task, two attempts, and the steps inside each. Duration bars use varied color tones; the halt, checkpoint, and completion rows are instant time events. getIcon returns a small lucide icon per row based on its etype.

agent run
plan + tool select
attempt 1
fetch context
run tool: search
rate limited — halted
attempt 2
fetch context
generate answer
checkpoint saved
run complete
-2s
0ms
2s
4s
6s
8s
10s
10.340s

The <Waterfall> fills its container, so wrap it in a fixed-height element (here 360px).

Props — WaterfallProps

PropTypeDefaultDescription
logDataLogItemType[]—Rows to render. Hierarchy comes from each row's parentId.
getIcon(item: LogItemType) => ReactNode—Returns the icon shown for a row in the tree.
panelWidthnumber300Initial requested pane width in px; changing it resets the request.
resizablebooleantrueEnable mouse and keyboard resizing of the entry pane.
minPanelWidthnumber160Preferred minimum entry width in pixels, relaxed on narrow containers.
maxPanelWidthnumberInfinityMaximum entry width in pixels; timeline space is also reserved.
onPanelWidthChange(width: number) => void—Called after a user resize with the clamped pixel width.
temporalCursornumber—Controlled time position of the interactive cursor.
onTemporalCursorChange(time: number) => void—Called as the cursor moves over the axis.
hoveredIdstring | null—Controlled hovered row id. Falls back to internal state if omitted.
onItemHover(id: string | null) => void—Called when the hovered row changes.
minWindownumber0.01Minimum zoom window duration, in seconds.
maxWindownumberInfinityMaximum zoom window duration, in seconds.
zoomFactornumber1.1Multiplier applied per wheel-zoom step.
enabledbooleantrueEnable wheel pan/zoom handling.
childrenReactNode—Extra content rendered inside the timeline (e.g. custom overlays).
classNamestring—Extra classes on the outer wrapper.

Row shape — LogItemType

A row is either an instant event (time) or a duration bar (startTime + duration). It extends the shared TreeDataItem, so id, parentId, and label come from there.

FieldTypeDescription
idstringUnique row id. Referenced by children via parentId.
parentIdstring | nullParent row id, or null for a root row.
labelstringText shown in the tree and used by search.
etype'task' | 'attempt' | 'info' | 'step'Row category — handy for choosing an icon.
icon'history' | 'file-code' | 'bot' | 'check-circle' | 'pause-circle'Optional built-in icon hint.
startTimenumberBar start time, in seconds. Pair with duration.
durationnumberBar length, in seconds. Pair with startTime.
timenumberInstant-event time, in seconds. Renders a dot instead of a bar.
createTimenumberOptional creation timestamp metadata.
color'blue' | 'green' | 'orange' | 'gray-light' | 'gray-medium' | 'purple'Bar / dot tone.
isCollapsiblebooleanWhether the row can be collapsed in the tree.
hasStripesbooleanRender the bar with a striped fill.
isHaltedStepbooleanMark a row as a halted step (e.g. error / rate-limit stop).

Release note — 0.1.15

  • The entry-pane divider supports dragging and accessible keyboard resizing.
  • Responsive width limits retain usable timeline space and preserve the requested width.

Release note — 0.1.14

  • The initial viewport fits the input events instead of the fixed demo window.

  • Hovered rows now join squarely at the pane divider; outer corners remain rounded.

  • maxWindow defaults to Infinity instead of ten times the built-in timeline duration.

  • Wheel and duration-drag zoom both respect explicit minWindow and maxWindow values.