Skip to content

fix: Stale waterfall selection when opening another span - #3285

Merged
kodiakhq[bot] merged 25 commits into
hyperdxio:mainfrom
bahltushar:fix_stale_waterfall_selection
Oct 22, 2026
Merged

kodiakhq[bot] merged 25 commits into
hyperdxio:mainfrom
bahltushar:fix_stale_waterfall_selection

Conversation

@bahltushar

@bahltushar bahltushar commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Opening another span of the same trace left the previous span selected in the waterfall. Closing a result now clears eventRowWhere, and opening a result keeps it only when that result is the span already selected.
The eventRowWhere query parser is shared by the results table, the expandable row opener, and the trace panel, so every path that opens or closes a result updates the selection the same way.

Screenshots or video

2026-10-05_10-50-10.mp4

Environment

HyperDX 2.x (reproduced on the build currently packaged as 2.38.0)
Trace search, Lucene
ClickHouse OpenTelemetry traces source

Steps to reproduce

  • Search for one trace that contains more than one span with the same name. For example:

TraceId:"854cf2e4-1302-4ce2-ba2a-94475bf89d68" AND SpanName:"FareMasterPricerTravelBoardSearch"

  • The results table shows three rows, all on amadeusprovideradapter-dwsvc:
image
  • Open 8cea712d1d4df628. The waterfall selects that span, and its details match it: FareMasterPricerTravelBoardSearch, parent a892ebf6acfe9ca7, duration 3.095s.
  • Close the side panel.
  • Open b2e2b0df5304a26a.

Expected

The waterfall highlights b2e2b0df5304a26a, and the Overview shows that span (parent b0be84a5cec5874d, duration 1.735s). eventRowWhere is replaced by the newly opened row, or cleared so the new row becomes the selection.

Actual

The header updates to b2e2b0df5304a26a, but the waterfall highlight and Overview still describe 8cea712d1d4df628.

Test plan

[x] Open a span from a trace, close the result, then open a different span of the same trace. The waterfall should not stay on the first span.
[x] Open the same span that is already selected. The waterfall should stay on that span.
[x] Open a span from a different trace. The previous span selection should not appear in the new waterfall.
[x] yarn ci:unit packages/app/src/components/tests/DBSqlRowTableWithSidebar.test.tsx

@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 011d4c3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

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

@vercel

vercel Bot commented Oct 5, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Hi @bahltushar, thanks for the pull request!

Before we review code from a first-time contributor we ask that a maintainer vouches for you, and you're not on our list yet. This PR stays open — it just isn't in the review queue until someone vouches.

To get vouched, open an issue saying hello and what you're working on:

https://github.com/hyperdxio/hyperdx/issues/new?template=introduce-yourself.md

A maintainer will usually reply within a day or two, and then this PR gets picked up as normal. More detail in our contributing guide.

@github-actions github-actions Bot added the needs-vouch Author needs a maintainer to vouch for them label Oct 5, 2026
// waterfall. A new result of the same trace must not keep the previous span.
// The exception is the result that *is* that span — clearing it would drop the
// selection the click just made.
export function eventRowWhereForOpenedRow<T extends { id: string }>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 major — "Keep" branch compares two different row-id formats, so it never matches and the selection is always cleared

current.id is the waterfall's id, built by useRowWhere from the waterfall's own columns with SpanAttributes/SpanEvents/__hdx_hidden dropped (DBTraceWaterfallChart.tsx:442-466). openedRowWhere is the search table's id, built from the search select or rowKeyColumns (DBRowTable.tsx:1808-1814). These strings are not equal in real use, so the function always returns null. Here is how that hurts: a user picks span B in the waterfall, then clicks the same highlighted result row again (or uses the expanded row's 'Open in side panel'). B gets cleared, and the opened span isn't re-highlighted either, because appliedHighlightHintRef already holds that hint key (DBTraceWaterfallChart.tsx:930-933). Fix: compare against the currently open rowWhere, i.e. keep eventRowWhere when rowWhere === openedRowWhere. Or match on trace and span identity instead of raw where-strings. Then drop the id-equality branch.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The keep check no longer compares the waterfall id to the search-row where string. It keeps eventRowWhere only when the result already open is the one being opened again (rowWhere === openedRowWhere).

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

3 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 3 minor

2 posted as inline comment(s) on the changed lines. 1 listed below.

Findings outside the changed lines

1 minor
  • 🔵 packages/app/tests/e2e/components/TableComponent.ts:1231 — clickRowContaining falls back to calling el.click() from script when the drawer covers the row, so the E2E test exercises a click a real user can't make → When isCoveredBySidePanel is true, the pointer click would land on the drawer, so a user can't reach that row either. Calling HTMLElement.click() skips Playwright's actionability checks and makes the 'switch rows with the panel open' test pass anyway. Instead, put the test in a state where the row really is clickable (for example a wider viewport or a collapsed nav), or fail loudly when the row is covered.

Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@greptile-apps

greptile-apps Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects span selection in trace views across search and session replay.

The repository’s component-size requirement remains unmet and must be satisfied before merging.

Fix All in Claude CodeFindings

  1. P2 Component size limit exceeded ▶

Summary

Updates the trace waterfall to follow the newly opened span.

  • The trace waterfall follows the span in the result you open.

Changes since the previous review only remove the unrelated Docker mount workaround. No new issues were found.

Reviews (28) · Last reviewed commit: "chore: Revert changes to unrelated files" · Reviewed by Greptile

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found.

The core fix is sound and matches the approach the prior reviewer requested: the selection is cleared where it is made (DBRowSidePanelErrorBoundary._onClose), a changed hint replaces the selection in the waterfall (useRowHighlightHint), and the opened-row change clears a stale selection before a still-mounted waterfall re-applies it (useOpenedRowSpanSelection). The duplicated schema was extracted to eventRowWhere.ts, and both unit and E2E coverage were added. No data loss, injection, auth, or guaranteed-crash path is introduced. Remaining items are reliability/maintainability smells and test-coverage gaps.

🟡 P2 -- recommended

  • packages/app/src/components/DBRowSidePanel.tsx:1358 -- eventRowWhere is a single shared URL param, so _onClose calling setEventRowWhere(null) wipes the selection of any other panel mounted on the same trace, not just the one being closed.
    • Fix: gate the close-time clear so it only runs when the closing panel owns the current selection, or scope selection state per panel instead of a single global query param.
  • packages/app/src/hooks/__tests__/useRowHighlightHint.test.tsx:26 -- every row uses the same Timestamp/SpanId/Body strings for both the hint and the row lookup, so the suite cannot catch a mismatch where the waterfall-built predicate and the search-table predicate differ for the same span.
    • Fix: add a case where the opened span's predicate string differs from the waterfall row's predicate and assert the already-selected span is kept.
  • packages/app/tests/e2e/components/TableComponent.ts:70 -- clickRowContaining/isCoveredBySidePanel rely on boundingBox geometry and a raw el.click() fallback, and getHighlightedWaterfallSpan matches a hashed CSS-module class substring, which is the brittleness pattern reviewers repeatedly flagged as flaky.
    • Fix: expose stable data-testids for the row-open button, the highlighted waterfall span, and the span-details panel, and assert against those instead of geometry/class substrings.
🔵 P3 nitpicks (4)
  • packages/app/src/hooks/useRowHighlightHint.ts:15 -- HighlightRow types Timestamp/SpanId/Body as unknown and then compares them with === against string hint fields, which silently yields false on any type drift rather than failing to type-check.
    • Fix: type these fields as string | undefined (or narrow before comparing) so a shape change surfaces at compile time.
  • packages/app/src/hooks/useOpenedRowSpanSelection.ts:27 -- the first-mount guard keys off initialRowId; if the panel mounts with rowId undefined and it later resolves, the effect treats the resolution as a row change and clears a selection that may have arrived from the URL.
    • Fix: skip the clear while the previous rowId was undefined, or initialize from the first defined value.
  • packages/app/src/SessionSidePanel.tsx:132 -- handleEventNavigate and the breadcrumb onClick wrapper in DBRowSidePanel.tsx call clearSpanSelection() manually even though useOpenedRowSpanSelection already clears when the opened row changes, leaving the clear rule spread across several call sites a new opener could miss.
    • Fix: confirm the manual clears are needed for timing; if not, drop them and rely on the hook so the rule lives in one place.
  • packages/app/src/hooks/useOpenedRowSpanSelection.ts:11 -- the fix depends on a parent useLayoutEffect running after the child waterfall's useEffect within a commit, but that ordering does not hold when the trace panel unmounts and remounts (a different commit); the comment asserts the guarantee without flagging the remount caveat.
    • Fix: note the remount path in the comment and confirm useOpenedRowSpanSelection's clear (not the ordering) is what covers it.

Reviewers (8): correctness, adversarial, frontend-races, typescript, testing, maintainability, project-standards, previous-comments.

Testing gaps:

  • No unit assertion for opening the already-selected span via a differently-built predicate (same-span keep case).
  • New E2E relies on geometric/class-substring selectors previously reported as flaky; no stable test ids.

Note: The dispatched reviewer sub-agents had not returned verifiable findings at synthesis time; the above is a conservative synthesis from direct diff analysis and the prior-comment thread, re-graded with the ship-blocker rubric (default-down). The previously raised "selection survives a source switch" item was withdrawn by its author as pre-existing and out of scope, and is not counted here.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

@bahltushar

Copy link
Copy Markdown
Contributor Author

The equal-predicate source switch already kept the span before this pull request, because eventRowWhere is cleared only when the trace ID changes. Comparing the source here would widen the change.

@pulpdrew pulpdrew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tracking this down, and for the clear repro. The diagnosis is right: the panel remounts, sees the old eventRowWhere in the URL, treats it like a deep link, and won't auto-select the newly opened row. I'd like to fix it in a different place, though.

Problems with clearing in the openers

  • It only covers two of the openers. This fix lives in DBSqlRowTableWithSidebar.onOpenSidebar and ExpandableRowTable.useSidebarOpener. DBRowSidePanel is also opened from PatternSidePanel, PodDetailsSidePanel and SessionSidePanel, and those still have the bug. Any new opener would also need to know to call the helper.
  • The logic is duplicated. The same rule is written out in both openers and has to be kept in sync by hand.
  • Keeping the span on close doesn't do anything. After a close, rowWhere is null, so the next open always clears the span. All that's left is a stale eventRowWhere in the URL, which gets copied into shared row links. The PR description also says closing clears it, but the code does the opposite.

Suggested approach: fix it where the selection is made

  1. Clear the selection when the drawer closes. In DBRowSidePanelErrorBoundary._onClose (DBRowSidePanel.tsx), call setEventRowWhere(null) next to clearTraceWaterfallSearchState(). Every host closes through this path, so you don't need to touch any opener.
  2. Let a changed hint replace the selection. This covers clicking another result while the drawer stays open. In the auto-select effect in DBTraceWaterfallChartContainer, only the first hint after mount or a trace change should yield to an existing selection. Today the code yields whenever highlightedRowWhere != null. Change that check to:
// Only the first hint after mount or a trace change yields to an existing
// selection (restored from the URL, or picked by the user). A later hint
// change means a different row was opened, so it replaces the selection.
if (appliedHighlightHintRef.current == null && highlightedRowWhere != null) {
  appliedHighlightHintRef.current = hintKey;
  return;
}

Keep the rest of the effect as it is: the reset on traceId change, the early return when the key already matches, and the row lookup. Reloads and shared links still keep their URL selection. A span the user cleared or picked isn't overridden, because the hint key hasn't changed. Opening the row that's already open also keeps the selection, for the same reason.

I'd suggest pulling this effect out into a small hook (e.g. hooks/useRowHighlightHint.ts) so it can be unit tested with renderHook, without mocking nuqs or the table.

Testing

The current test mocks quite a bit. With this being a scenario that crosses a few components and relies on URL state, Playwright E2E tests would be more appropriate. We should cover both scenarios:

  1. Opening a span, closing it, opening another span from the same trace
  2. Opening a span, then selecting another span from the same trace without closing the side panel first.

@bahltushar
bahltushar force-pushed the fix_stale_waterfall_selection branch from 7e9fab0 to becea77 Compare October 9, 2026 03:40
return;
}

if (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 major — The stale span still shows when the Trace tab remounts after another row of the same trace is opened

DBRowSidePanel.tsx:1126 renders DBTracePanel only while the Trace tab is active, so this ref goes back to null whenever the user switches tabs. Here is the failing sequence: open row A, view the Trace tab (A is selected), switch to Overview, click row B of the same trace in the results table while the drawer stays open, then return to Trace. The hook mounts fresh, sees highlightedRowWhere = A, takes the yield branch and keeps A, which is the bug this PR set out to fix. Make the open action decide instead of mount order: clear or replace eventRowWhere when the side panel's row changes (for example in DBSqlRowTableWithSidebar's onOpenSidebar, or in an effect on rowId in DBRowSidePanel). Alternatively, yield only when the existing selection is the hinted row. Add a test that unmounts and remounts the hook with a new hint while a selection exists.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

@bahltushar

Copy link
Copy Markdown
Contributor Author

@pulpdrew made the changes as suggested. Following were made:

  • The clear was removed from DBSqlRowTableWithSidebar and ExpandableRowTable. Those two openers were the only ones that knew about it, and closing the drawer did not actually clear the span left in the URL.
  • eventRowWhereForOpenedRow is gone.
  • The auto-select logic moved into useRowHighlightHint, used by the waterfall chart.
  • Unit tests cover that hook. The two end-to-end cases are in traces-workflow.spec.ts: close and open another span, and open a second span of the same trace without closing.


export type RowHighlightHint = {
timestamp: string;
spanId: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — New RowHighlightHint type is not used where the same shape is already written out inline

DBTraceWaterfallChart.tsx:645 and DBTracePanel.tsx:234 still spell out { timestamp; spanId; body } inline. Replace both with RowHighlightHint so there is one definition (convention: define and import reusable named types, DRY).

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

clearTraceWaterfallSearchState();
// The selected span is in the URL. Leaving it there makes the next open,
// and any shared row link, show the previous span.
setEventRowWhere(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Span-selection clearing is spread across three call sites and runs twice on normal close

With the boundary, the X button runs Inner's closePanel (clear) and then _onClose (clear again). Outside click, Drawer onClose and the error fallback only reach _onClose, and the session panel only reaches Inner's clear. Every new way out of the panel has to remember to clear, which is how the session breadcrumb path above got missed. Move the clear into one place that owns leaving the panel, e.g. have sidePanelStack.clearTrail() or a shared leavePanel helper clear eventRowWhere, and drop the per-call-site setEventRowWhere(null)/clearSpanSelection() calls.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

[pushSource],
);

const clearSpanSelection = useOpenedRowSpanSelection(initialRowId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 major — Clicking the session breadcrumb leaves the old span selected, so the session replay bug still happens

The span is only cleared in handlePanelBack (Esc and the Back button) and in closePanel. In packages/app/src/SessionSidePanel.tsx:236-241, the session breadcrumb passes onClick: handleBackToSession straight through to parentBreadcrumbs, and that path never calls clearSpanSelection. Here is how it breaks. Open event A from a replay; the waterfall selects span A. Click the session breadcrumb: selectedEvent becomes null and DBRowSidePanelInner unmounts. Open event B in the same trace. DBRowSidePanelInner mounts again, and openedRowIdRef starts out as B, so useOpenedRowSpanSelection never clears. useRowHighlightHint then treats span A as a restored selection on its first hint and keeps it. Fix: clear eventRowWhere in SessionSidePanel's handleBackToSession (and handleEventNavigate). Another option is to wrap the onClick of each parentBreadcrumbs item in DBRowSidePanelInner with clearSpanSelection. Then add a test for the breadcrumb path.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

@bahltushar
bahltushar requested a review from pulpdrew October 12, 2026 02:38
@bahltushar
bahltushar force-pushed the fix_stale_waterfall_selection branch from 11806cc to 11253ef Compare October 12, 2026 03:15
@@ -755,7 +762,23 @@ export const DBRowSidePanelInner = ({
const items: BreadcrumbItem[] = [];

if (parentBreadcrumbs) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Session span clearing is spread across Inner instead of living in SessionSidePanel.handleBackToSession

The new handlePanelBack branch (clearSpanSelection(); onNavigateToParent()) and the parentBreadcrumbs.map wrapper both exist only to clear the span before SessionSidePanel.handleBackToSession runs. That is the only onNavigateToParent/parent-crumb provider (packages/app/src/SessionSidePanel.tsx:135,238,245), and it already has useClearSpanSelection in scope. Call clearSpanSelection() inside handleBackToSession and drop the crumb-wrapping map and the extra branch in Inner. Back and the crumb then go through one place, and Inner no longer rewrites callbacks it was handed.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

}, [isFullWidth, setSize]);

const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({});
const [, setEventRowWhere] = useQueryState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — _onClose sets up its own eventRowWhere setter instead of using the new useClearSpanSelection

This PR adds useClearSpanSelection (packages/app/src/hooks/useOpenedRowSpanSelection.ts:14), which wraps exactly useQueryState('eventRowWhere', eventRowWhereParser) + set(null). SessionSidePanel uses it, but DBRowSidePanelErrorBoundary sets up its own useQueryState and calls setEventRowWhere(null) directly. Use const clearSpanSelection = useClearSpanSelection() here and drop the nuqs/eventRowWhereParser imports from this file's boundary component. That leaves one definition of how a span selection is cleared.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@vercel

vercel Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Oct 13, 2026 5:24pm UTC

Request Review

pulpdrew
pulpdrew previously approved these changes Oct 13, 2026

@pulpdrew pulpdrew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

[pushSource],
);

const clearSpanSelection = useOpenedRowSpanSelection(initialRowId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Component size limit exceeded This change adds span-selection behavior to DBRowSidePanel.tsx, which is already over 1,400 lines. The repository requires component files to stay under 300 lines and large components to be broken down; the changed DBTracePanel.tsx and DBTraceWaterfallChart.tsx also exceed that limit. This requirement must be satisfied before merging.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

[pushSource],
);

const clearSpanSelection = useOpenedRowSpanSelection(initialRowId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 major — Span selection is cleared only when the root row changes, so drilldowns inside the panel still show the previous span

useOpenedRowSpanSelection(initialRowId) watches only the root row. A row pushed with pushNav, pushSource ("View Trace"), or reached by Back through popOne changes resolvedRowId but leaves initialRowId the same. For those rows isRowLoading renders "Loading...", which unmounts DBTracePanel. The waterfall then remounts, and useRowHighlightHint treats the old eventRowWhere (same trace) as a restored selection and keeps it. This is the same stale-span bug the PR fixes, reached through in-panel navigation. Example: open log A, click View Trace (span X is selected), go Back, open neighbour log B from the same trace in Context, click View Trace: X is still highlighted. Fix: key the hook on resolvedRowId, plus the active source id, instead of initialRowId.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

@@ -755,7 +762,23 @@ export const DBRowSidePanelInner = ({
const items: BreadcrumbItem[] = [];

if (parentBreadcrumbs) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Session-only cleanup is spread into the generic inner panel instead of handleBackToSession

SessionSidePanel is the only caller that passes onNavigateToParent and parentBreadcrumbs, and both point at handleBackToSession (SessionSidePanel.tsx:135). That component already calls clearSpanSelection in handleEventNavigate and handleClose. Call it in handleBackToSession too, then remove the breadcrumb-wrapping map here and the extra clearSpanSelection() in handlePanelBack. That keeps all of the session's clear-on-leave logic in one place.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

}, [isFullWidth, setSize]);

const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({});
const [, setEventRowWhere] = useQueryState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Close handler re-implements useClearSpanSelection with its own useQueryState

DBRowSidePanelErrorBoundary opens a second useQueryState('eventRowWhere', eventRowWhereParser) just to call setEventRowWhere(null). The new useClearSpanSelection in hooks/useOpenedRowSpanSelection.ts does exactly this, and SessionSidePanel already uses it. Use const clearSpanSelection = useClearSpanSelection() here, and drop the nuqs and eventRowWhereParser imports from this file.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@pulpdrew

Copy link
Copy Markdown
Contributor

It seems like the new E2E is either flaky or failing, could you try making it more reliable?

@bahltushar
bahltushar requested a review from pulpdrew October 16, 2026 11:15
[pushSource],
);

const clearSpanSelection = useOpenedRowSpanSelection(initialRowId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 major — Navigating to another row inside the panel doesn't clear the span, so the stale-span bug comes back

useOpenedRowSpanSelection(initialRowId) only watches the root row, but the waterfall's hint follows the active row (resolvedRowId, from the nav/source stacks via normalizedRow, ~line 736). To reproduce: open trace span A, so the Trace tab selects A. Switch to Surrounding Context and click a neighbour span B from the same trace (this goes through pushNav, and initialRowId doesn't change). Return to the Trace tab. The waterfall remounts, the first hint gives way to the existing selection A (its traceId still matches), and the header shows B while the highlight and details show A. Back (popOne) has the same problem in reverse. Fix: pass resolvedRowId (or activeRowId) to the hook, and add a nav-stack case to DBRowSidePanel.spanSelection.test.tsx.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

@@ -755,7 +762,23 @@ export const DBRowSidePanelInner = ({
const items: BreadcrumbItem[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — The inner panel wraps the parent's callbacks to clear the span on the session's behalf

Both handlePanelBack (via onNavigateToParent) and the parentBreadcrumbs wrapper end up calling SessionSidePanel's handleBackToSession, and that file already has clearSpanSelection. Call clearSpanSelection() inside handleBackToSession in SessionSidePanel.tsx. Then drop the breadcrumb .map wrapper and the extra call in handlePanelBack, so one layer owns this instead of two.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

}, [isFullWidth, setSize]);

const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({});
const [, setEventRowWhere] = useQueryState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — The drawer close handler repeats useClearSpanSelection inline

DBRowSidePanelErrorBoundary sets up its own useQueryState('eventRowWhere', eventRowWhereParser) just to call setEventRowWhere(null). That is exactly what the new useClearSpanSelection in hooks/useOpenedRowSpanSelection.ts does. Use const clearSpanSelection = useClearSpanSelection() and remove the direct nuqs and eventRowWhereParser imports here.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@pulpdrew

Copy link
Copy Markdown
Contributor

It appears that the E2E are still failing, and there is also a lint failure (make ci-lint should run the lints locally)

[pushSource],
);

const clearSpanSelection = useOpenedRowSpanSelection(initialRowId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 major — Span selection is cleared only when the root row changes, not when in-panel navigation changes the row on screen

The hook is keyed on initialRowId, but the panel renders resolvedRowId (leafNav?.rowId ?? activeSourceFrame?.rowId ?? initialRowId, line 304-308). Reproduce: select span X in a row's trace tab, then use pushNav (a surrounding-context neighbour), pushSource (View trace) or Back/breadcrumb (popOne/truncateTo) to reach another row in the same trace. Each of these loads a new row, so the trace panel unmounts and remounts (isRowLoading at line 887). On remount useRowHighlightHint yields to the old eventRowWhere, so span X stays highlighted, which is the bug this PR fixes. Fix: pass resolvedRowId (or a source+row key) to useOpenedRowSpanSelection.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

@@ -760,7 +767,23 @@ export const DBRowSidePanelInner = ({
const items: BreadcrumbItem[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Clearing on return to the session is done in the child by wrapping the parent's callbacks

The only parent that passes onNavigateToParent and parentBreadcrumbs is SessionSidePanel, and it passes handleBackToSession for both. Call clearSpanSelection() once inside handleBackToSession (SessionSidePanel.tsx:135), the way handleClose and handleEventNavigate already do. Then remove the breadcrumb .map wrapper and the extra branch in handlePanelBack, so this flow has one owner instead of two.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

}, [isFullWidth, setSize]);

const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({});
const [, setEventRowWhere] = useQueryState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Second hand-rolled eventRowWhere setter instead of the new useClearSpanSelection hook

DBRowSidePanelErrorBoundary creates its own useQueryState('eventRowWhere', eventRowWhereParser) only to call setEventRowWhere(null). That is exactly what useClearSpanSelection (hooks/useOpenedRowSpanSelection.ts) does, and SessionSidePanel already uses it. Use const clearSpanSelection = useClearSpanSelection() here and drop the nuqs and parser imports.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@bahltushar

Copy link
Copy Markdown
Contributor Author

@pulpdrew made the changes, lint issue is gone and hopefully now there won't be any e2e test failing. Although I checked in Preview option and can see the issue which was highlighted is fixed.

// A parent crumb (the session name) leaves this row without going
// through Back or Close. Clear the span on that click too.
items.push(
...parentBreadcrumbs.map(item => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Clearing the span when leaving a session event lives in the wrong component

onNavigateToParent and the parent breadcrumb onClick both point at SessionSidePanel.handleBackToSession (packages/app/src/SessionSidePanel.tsx:135), and that handler is the only one either path calls. Put clearSpanSelection() in handleBackToSession, next to the clears already in handleEventNavigate and handleClose. Then drop the parentBreadcrumbs.map wrapper and the onNavigateToParent-only clear in handlePanelBack. As written, the session's "leave this event" rule is split between the parent and a child that rewrites the parent's callbacks, and a new parent crumb or back path added later will miss it.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

}, [isFullWidth, setSize]);

const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({});
const [, setEventRowWhere] = useQueryState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — The close handler sets up its own eventRowWhere setter instead of using the new useClearSpanSelection hook

This PR adds useClearSpanSelection in packages/app/src/hooks/useOpenedRowSpanSelection.ts for exactly this operation, and SessionSidePanel already uses it. Call const clearSpanSelection = useClearSpanSelection() here and use it in _onClose. That removes the extra useQueryState('eventRowWhere', eventRowWhereParser) and the nuqs/eventRowWhereParser imports from this file, so clearing the span happens in one place.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

Comment thread scripts/test-e2e.sh Resolved

if (parentBreadcrumbs) {
items.push(...parentBreadcrumbs);
// A parent crumb (the session name) leaves this row without going

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Span clearing for 'leave to session' is added in DBRowSidePanelInner by wrapping callbacks the parent passes in, when the parent already has a single handler for it

Only SessionSidePanel passes onNavigateToParent and parentBreadcrumbs, and both point at handleBackToSession (SessionSidePanel.tsx:135, 238, 245). Call clearSpanSelection() inside handleBackToSession, then drop the parentBreadcrumbs.map wrapper and the extra branch in handlePanelBack. That leaves one clear per exit path, owned by the parent that controls the exit, rather than Inner rewriting another component's callbacks.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

}, [isFullWidth, setSize]);

const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({});
const [, setEventRowWhere] = useQueryState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 minor — Drawer close sets up its own eventRowWhere query state instead of reusing the new useClearSpanSelection hook

Replace the local useQueryState('eventRowWhere', eventRowWhereParser) and setEventRowWhere(null) with const clearSpanSelection = useClearSpanSelection() from @/hooks/useOpenedRowSpanSelection, which this PR adds and already uses in SessionSidePanel and DBRowSidePanelInner. Then every place that clears the span goes through one helper.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@pulpdrew pulpdrew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@kodiakhq
kodiakhq Bot merged commit d1cecf8 into hyperdxio:main Oct 22, 2026
26 of 28 checks passed
@bahltushar
bahltushar deleted the fix_stale_waterfall_selection branch October 23, 2026 05:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge external needs-vouch Author needs a maintainer to vouch for them

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants