Skip to content

chore: refactor TimelineChart for performance#2185

Open
karl-power wants to merge 1 commit intomainfrom
karl/timeline-viewer-perf
Open

chore: refactor TimelineChart for performance#2185
karl-power wants to merge 1 commit intomainfrom
karl/timeline-viewer-perf

Conversation

@karl-power
Copy link
Copy Markdown
Contributor

@karl-power karl-power commented May 4, 2026

Summary

Refactors TimelineChart for performance on traces with thousands of spans. The previous implementation re-rendered the entire chart on every wheel tick because pan/zoom were React state. This branch moves to native horizontal scrolling for pan, an imperative scale ref for zoom, and imperative DOM updates for the X-axis ticks and mouse cursor — so a wheel event no longer triggers a React render of all virtualized rows.

  • Pan is now native horizontal scroll, not drag-to-pan. The previous useDrag handler is gone; users scroll with trackpad/scroll wheel as with any scrollable container. Cmd/Ctrl + scroll still zooms.
  • Single resize handle between label column and events column, instead of one per row.

A Storybook story (TimelineChart.stories.tsx) is added with 1000 synthetic rows for working on this in isolation.

How to test locally or on Vercel

  1. Open a trace with many spans (ideally several hundred). The "Default" Storybook story under Components → TimelineChart also has 1000 synthetic rows for isolated testing.
  2. Scroll vertically — virtualized rows should behave as before; X-axis stays pinned at top, label column stays pinned at left.
  3. Cmd/Ctrl + scroll over the events area to zoom in/out. Confirm:
    • cursor under the mouse stays anchored to the same point in time across the zoom step (no drift)
    • X-axis ticks recompute with the new interval
    • mouse cursor overlay shows the time at the cursor and flips its label to the opposite side past the midpoint
  4. Drag horizontally inside the events area (native scroll bar at bottom, or two-finger swipe). Label column stays in place.
  5. Hover an event bar — Mantine tooltip with the bar's text appears.
  6. On a span with span-events configured, confirm the diamond markers show up inside the bar and have their own tooltip with timestamp + attributes.
  7. On non-Log spans, confirm the duration label (X.YYms) renders beside the bar (right side for early spans, left side for spans past the timeline midpoint).
  8. Drag the resize handle between label column and events column — the label width should resize live across all rows.
  9. Resize the browser window — the mouse-cursor vertical line height should track the new container height (previously stuck at the first-render value).

References

  • Linear Issue: Closes HDX-4145

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 4, 2026

⚠️ No Changeset found

Latest commit: c525fbb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added the review/tier-4 Critical — deep review + domain expert sign-off label May 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🔴 Tier 4 — Critical

Touches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD.

Why this tier:

  • Large diff: 1225 production lines changed (threshold: 1000)

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 10
  • Production lines changed: 1225
  • Branch: karl/timeline-viewer-perf
  • Author: karl-power

To override this classification, remove the review/tier-4 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment May 4, 2026 4:11pm

Request Review

@karl-power karl-power requested review from a team and elizabetdev May 4, 2026 16:06
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

PR Review

This is a well-motivated refactor — replacing React state (scale, offset, cursorXPerc) with refs + imperative DOM updates is the right call for eliminating re-renders on every wheel tick. The architecture is sound. A few items to verify:

  • ⚠️ onEventClick changed from optional to required — If any future callers of TimelineChart omit it, TypeScript will catch it, but the API surface narrowed. Not a bug now (only one caller), but worth noting for the public interface.

  • ⚠️ minWidthPerc overlap behavior changed — Old code tracked lastEventEnd to prevent adjacent min-width bars from visually overlapping. New code positions each event purely by its time (position: absolute; left: percentX%) and doesn't adjust neighbors. Currently harmless because each trace row has a single event, but if rows ever get multiple events with minWidthPerc set, adjacent bars can visually overlap without lastEventEnd compensation.

  • ⚠️ Fragile DOM contract in TimelineXAxis — The imperative tick creation in recompute() assumes ticksContainerRef children are exclusively the tick elements it creates. The SCSS comment documents this (// Tick children are created/measured imperatively…), but container.lastChild! is a non-null assertion that breaks silently if anything else appends children. Low risk given the comment, but worth watching if the axis ever needs decorative elements.

  • ref as plain prop (React 19 pattern)TimelineMouseCursor and TimelineXAxis accept ref: Ref<Handle> as a plain prop with useImperativeHandle — correct for React 19, no forwardRef needed.

  • ResizeObserver for timelineHeight — Properly handles dynamic container resizing; the mouse-cursor line height was previously stuck at first-render value. Fix is correct.

  • Storybook story — 1000-row synthetic story is a good addition for isolated perf testing.

No blocking issues. The minWidthPerc overlap regression is the only behavioral change worth a follow-up if multi-event rows are ever used.

@karl-power karl-power force-pushed the karl/timeline-viewer-perf branch from c525fbb to 4ffb856 Compare May 4, 2026 16:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

E2E Test Results

All tests passed • 157 passed • 3 skipped • 1214s

Status Count
✅ Passed 157
❌ Failed 0
⚠️ Flaky 6
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@karl-power karl-power removed the request for review from a team May 4, 2026 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant