Skip to content

feat: allow scroll to zoom and panning to trace waterfall viewer#1843

Merged
kodiakhq[bot] merged 5 commits intomainfrom
karl/allow-zoom-and-pan-trace-viewer
Mar 13, 2026
Merged

feat: allow scroll to zoom and panning to trace waterfall viewer#1843
kodiakhq[bot] merged 5 commits intomainfrom
karl/allow-zoom-and-pan-trace-viewer

Conversation

@karl-power
Copy link
Contributor

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

Summary

Closes HDX-3527

  • Allows zooming the trace timeline viewer by holding cmd or ctrl key and scrolling in or out, adds hint to user that this is possible.
  • Allows panning on Y axis with the mouse by clicking and dragging.
  • Allows scrolling horizontally as well as vertically.

Screenshots or video

Screen.Recording.2026-03-09.at.11.54.55.mov

How to test locally or on Vercel

  1. Open the trace viewer and zoom and pan.
  2. Test using normal mouse and trackpad.

@vercel
Copy link

vercel bot commented Mar 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 Mar 13, 2026 2:29pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 4, 2026

⚠️ No Changeset found

Latest commit: c054eb2

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
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Claude finished @karl-power's task in 22s —— View job


PR Review

✅ No critical issues found.

A few minor observations:

  • ⚠️ useDrag.ts:25 — The pointermove listener captures onDragMove at creation time via closure (e => onDragMove?.(e)), but since onDragMove comes from useStableCallback this is safe. However, onDragEnd similarly captures abortController.current at closure time — this works correctly because abort() is called on the same controller instance that was captured.

  • ℹ️ useDrag.ts:13useRef<AbortController>(undefined) could be typed as useRef<AbortController | undefined>(undefined) for better explicitness, but this is a minor style point.

  • ℹ️ TimelineChart.tsx:104 — The panning sensitivity 0.125 / scale is a magic number — a named constant would help readability, but not blocking.

  • ℹ️ TimelineChart.tsx:109 — Zoom sensitivity 0.01 is also a magic number. Consider ZOOM_SENSITIVITY constant.

@karl-power karl-power requested review from a team, bot-hyperdx and dhable and removed request for a team, bot-hyperdx and dhable March 4, 2026 10:30
@karl-power karl-power marked this pull request as draft March 4, 2026 16:34
@karl-power karl-power changed the base branch from karl/timeline-components-refactor to main March 5, 2026 11:56
@karl-power karl-power force-pushed the karl/allow-zoom-and-pan-trace-viewer branch from 1db0067 to aad9127 Compare March 5, 2026 12:45
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Claude finished @karl-power's task in 1m 6s —— View job


PR Review

✅ No critical bugs. Two items worth addressing:

  • ⚠️ useDrag.ts:27-38 — Missing pointercancel handler. If a drag is interrupted (e.g., browser takes over pointer capture), pointerup won't fire and the pointermove listener stays active indefinitely. → Add document.addEventListener('pointercancel', abortController.abort.bind(abortController), { signal: abortController.signal }) or handle pointercancel alongside pointerup. Fix this →

  • ⚠️ TimelineChart.tsx:144 — The wheel event listener is registered without { passive: false }, so e.preventDefault() can't be called (and isn't called). On trackpads, horizontal scroll (deltaX) will pan the timeline and scroll the page simultaneously. → Register as { passive: false } and call e.preventDefault() when deltaX !== 0. Fix this →

@karl-power karl-power requested review from a team and wrn14897 and removed request for a team March 5, 2026 12:46
@karl-power karl-power marked this pull request as ready for review March 5, 2026 12:46
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

E2E Test Results

All tests passed • 86 passed • 3 skipped • 961s

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

Tests ran across 4 shards in parallel.

View full report →

@karl-power karl-power marked this pull request as draft March 9, 2026 09:20
@karl-power karl-power removed the request for review from wrn14897 March 9, 2026 09:20
@karl-power karl-power force-pushed the karl/allow-zoom-and-pan-trace-viewer branch from aad9127 to 709d75c Compare March 9, 2026 10:52
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Code Review

Overall this is a clean refactor — moving scale/zoom state internals and adopting modern pointer-event patterns. A couple of things worth fixing:

  • ⚠️ Missing pointercancel handler in useDrag.ts → If the browser cancels a pointer event (OS gesture, window blur during drag, touch interruption), pointerup never fires. The abortController.abort() won't be called, leaving userSelect: 'none' permanently stuck on the element (preventing text selection until page reload) and the pointermove listener orphaned on document. Fix by also aborting on pointercancel:

    document.addEventListener('pointercancel', e => {
      abortController.abort();
      onDragEnd?.(e);
    }, { signal: abortController.signal });
  • ⚠️ Zoom hint is hardcoded always-visible → The <Kbd>⌘/Ctrl</Kbd> + <Kbd>scroll</Kbd> to zoom hint is unconditionally rendered inside TimelineChart with no prop to hide it. The component is general-purpose (used by DBTraceWaterfallChart today, potentially others), but the hint is now baked into the internals. If zoom is always enabled this is fine, but worth at minimum an showZoomHint prop or moving the hint to the call site in DBTraceWaterfallChart.

  • useStableCallback + AbortController-based useDrag are well-implemented patterns.

  • ✅ Fixing the useEffect(() => { ... }) (no deps) → useEffect(..., [onWheel]) is a correct bug fix — the old code re-registered the wheel listener on every render.

  • DBHighlightedAttributesList guard (highlightedAttributeValues?.length > 0) is a safe improvement.


Open in Web View Automation 

@karl-power karl-power force-pushed the karl/allow-zoom-and-pan-trace-viewer branch from 709d75c to bbbaa88 Compare March 9, 2026 11:00
@karl-power karl-power marked this pull request as ready for review March 9, 2026 11:00
@karl-power karl-power requested review from a team and removed request for a team March 9, 2026 11:00
@karl-power karl-power requested a review from wrn14897 March 9, 2026 11:00
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Code Review

Good overall implementation — clean hook extraction, proper cleanup with AbortController, and solid use of useStableCallback. A few issues worth addressing:

  • ⚠️ Horizontal scroll propagates to parent containers → In onWheel, e.preventDefault() is only called for metaKey/ctrlKey (zoom), but not when handling deltaX. The wheel listener is { passive: false }, so preventDefault() is available — but without calling it for horizontal scroll, scroll events bubble up and any scrollable ancestor will also scroll simultaneously. Add if (deltaX !== 0) e.preventDefault() before updating offset with deltaX.

  • ⚠️ Zoom has no upper boundMath.max(v + -deltaY * 0.01, 1) clamps at minimum scale=1 but allows unbounded zoom-in. A user could zoom in to the point where the timeline is unusable. Add an upper clamp, e.g. Math.min(..., 100).

  • ⚠️ Zoom sensitivity is inconsistent across input devicesdeltaY * 0.01 means a standard mouse wheel (typically deltaY=120 per notch) produces a 1.2 scale jump per click — very aggressive. Trackpads produce fractional deltas so they feel smooth. Consider normalizing delta magnitude or using a smaller multiplier like 0.005.


Open in Web View Automation 

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall this is a clean refactor — moving scale/offset state internally, adopting AbortController-based drag cleanup, and extracting reusable hooks are all improvements.

Issues to address before merge:

  • ⚠️ No maximum zoom level in TimelineChart.tsxsetScale(v => Math.max(v + -deltaY * 0.01, 1)) has no upper bound. Extreme zoom levels (e.g. scale=1000) make panning hypersensitive and individual spans vanish. Add a cap: Math.min(Math.max(v + -deltaY * 0.01, 1), 100) or similar.

  • ⚠️ Missing setPointerCapture in useDrag.ts → Without e.currentTarget.setPointerCapture(e.pointerId), if the pointer leaves the browser window during a drag (e.g., dragging into OS chrome and releasing), pointerup may never fire on document, leaving the AbortController's listeners attached until the next drag starts. Add e.currentTarget.setPointerCapture(e.pointerId) in onPointerDown.

  • ℹ️ Zoom hint always visible → The ⌘/Ctrl + scroll to zoom hint renders permanently above the timeline. Consider showing it only on hover or as a tooltip to reduce visual clutter.

✅ No critical bugs or security issues found. The useStableCallback pattern, AbortController-based cleanup in useDrag, useLayoutEffect for scale offset sync, and highlightedAttributeValues?.length > 0 guard are all solid improvements.


Open in Web View Automation 

@karl-power karl-power changed the title feat: allow click+drag panning and zooming and horizontal scrolling o… feat: allow scroll to zoom and panning to trace waterfall viewer Mar 9, 2026
@karl-power karl-power force-pushed the karl/allow-zoom-and-pan-trace-viewer branch from fef4cba to 4189e3a Compare March 11, 2026 11:40
@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

PR Review

  • ⚠️ useDrag.ts missing setPointerCapture → On fast mouse moves, the pointer can escape the element before pointermove registers on document. Consider calling (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId) in the onPointerDown handler for more robust drag tracking (though the document-level listener mitigates this in most cases).

  • ⚠️ Simultaneous zoom + horizontal pan on trackpad → In onWheel, when metaKey || ctrlKey is held AND deltaX !== 0, both zoom and horizontal pan are applied. On trackpads, a two-finger pinch gesture may set both ctrlKey=true and deltaX !== 0, causing unexpected simultaneous zoom+pan. Consider adding an else if or skipping deltaX pan when in zoom mode.

  • ⚠️ Zoom hint always visible → The ⌘/Ctrl + scroll to zoom hint renders unconditionally in the <> fragment, even when the trace viewer is not zoomed. This adds persistent UI noise. Consider showing it only on hover or as a tooltip.

✅ The core implementation is clean — useStableCallback pattern is correct, AbortController-based drag is modern and leak-free, and removal of external scale/setScale props simplifies the API appropriately.

Copy link
Contributor

@pulpdrew pulpdrew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, this is a nice improvement. Please add a changeset

@kodiakhq kodiakhq bot merged commit 2a9a614 into main Mar 13, 2026
14 checks passed
@kodiakhq kodiakhq bot deleted the karl/allow-zoom-and-pan-trace-viewer branch March 13, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants