Skip to content

feat(trace): Add pinnable attribute column to trace waterfall#119051

Draft
nsdeschenes wants to merge 21 commits into
masterfrom
nd/EXP-1077/feat-explore-add-trace-attribute-pin-action
Draft

feat(trace): Add pinnable attribute column to trace waterfall#119051
nsdeschenes wants to merge 21 commits into
masterfrom
nd/EXP-1077/feat-explore-add-trace-attribute-pin-action

Conversation

@nsdeschenes

@nsdeschenes nsdeschenes commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Let users pin one span attribute from the drawer's Attributes section and surface it as a dedicated column in the trace waterfall.

What changed

  • Pin action in the drawer — each attribute in the Attributes section gets a pin toggle. The pinned attribute is marked with an indicator next to its name.
  • New waterfall column — the pinned attribute renders as a column between the trace tree and the duration waterfall, with a header cell to unpin it. It's absolutely positioned at the divider boundary and consumes no flex width, so the span-bar coordinate system and divider math are untouched.
  • Shareable state — the pinned attribute is stored in the URL (pinnedAttribute query param), so it survives reload and is shareable. The param is stripped from header breadcrumb links.
  • Efficient loading — the attribute is fetched via the trace endpoint's additional_attributes param, skipping the request when it's already present in the trace response.
  • Scroll & layout — the column scrolls horizontally as one unit with the tree, its width is accounted for in the tree's horizontal scroll math, and it renders correctly on collapsed rows.

Closes EXP-1077

Example:

Screenshot 2026-07-06 at 10 15 12

nsdeschenes and others added 15 commits July 3, 2026 15:01
Let users pin one span attribute from the drawer's Attributes section. The
pinned attribute is loaded via the trace endpoint's additional_attributes param
(unless already requested) and rendered as a new column between the trace tree
and the duration waterfall, with a header cell to unpin it.

The pinned attribute is stored in the URL (pinnedAttribute query param) so it is
shareable and survives reload. The column is absolutely positioned at the divider
boundary and consumes no flex width, leaving the span-bar coordinate system and
divider math untouched.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
…esponse

The trace endpoint already returns a fixed set of native span fields (span.op,
span.duration, measurements, web vitals, etc.) plus the default additional
attributes. Pinning one of those previously added it to the additional_attributes
request, triggering a redundant full-trace refetch for data already present.

Exclude those keys from the request and resolve the pinned column's value from
the native span fields when the attribute is not requested, so excluded
attributes still render.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The exclusion set used the backend column names from spans_rpc.py (e.g.
description), but the drawer pins the user-facing attribute name (span.description
per SpanFields). As a result span.description — and other span-prefixed native
fields — were still added to the additional_attributes request even though the
trace response already includes them.

Key the native-attribute map by the drawer/user-facing names so both the request
exclusion and the column value lookup use the same key, and keep measurements as
an exact set so custom measurements are still requested.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pinned attribute column sat 3px left of the divider with a full space.md
right inset, leaving a visible gap between the value and the divider line.
Anchor the column flush to the divider (it is below the divider's z-index, so
drag still works) and tighten the right padding.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The header cell only had a left border. Unlike the per-row cells (whose right
edge is covered by the divider), the header sits in the top band above the
divider and needs its own right border to close the column outline.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pinned column's left border was drawn per-row, so it stopped after the last
row while the divider on the right continued full height, leaving the left edge
open below the rows. Draw the left border once as a full-height line (mirroring
the divider) and drop the per-row and header left borders so they don't double
up.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
Pinned values wider than the column were only reachable via the hover tooltip.
Wire up horizontal scrolling for the whole column at once (mirroring the tree
column): the view manager holds a single shared translate that is applied to
every cell's inner element and to cells mounted mid-scroll, so the column moves
together rather than one row at a time. Shift+wheel or horizontal wheel scrolls;
vertical wheel still scrolls the list.

Keep the horizontal padding on the inner element so the cell's clientWidth equals
the full column width and the max-scroll clamp reveals the trailing edge of the
value instead of stopping a few pixels short. Reset the offset when the pinned
attribute changes.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pinned column inherited the row's semantic text color (error/warning) and
matched the row's focus/search highlight background, making it visually
inconsistent. Force a uniform content color and drop the row-state background
overrides so the column reads as one column, and bold the pinned attribute name
in the header.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pinned attribute column overlays the right edge of the trace tree, so the
tree could no longer scroll far enough to reveal content hidden behind it. Treat
the tree's visible width as the list column width minus the pinned column width
when clamping its horizontal scroll and sizing the horizontal scrollbar, and
re-clamp when the pinned attribute is toggled.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The drawer's attribute table gave no indication of which attribute was pinned as
a column. Add a pin icon next to the pinned attribute's value (composing with any
existing renderer) so the pinned attribute is identifiable at a glance.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
Move the pin indicator from the attribute value to the attribute name. Add a
generic getAttributeKeySuffix render prop to AttributesTree that renders content
after the key, and use it from the trace drawer to show the pin icon next to the
pinned attribute's name.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pin indicator used vertical-align: text-bottom, which sat it below the
attribute name. Use vertical-align: middle so it centers with the key text.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pinned column header and left-border line are positioned against the trace
wrapper (full width), while the per-row cells live inside the vertically
scrolling container, which is narrower by the scrollbar width. With a
space-reserving scrollbar this offset them by listFraction * scrollbarWidth.

Expose the scrollbar width as a --trace-scrollbar-width CSS variable and add
that offset to the header and line, so they line up with the cells regardless of
scrollbar style.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
Collapsed rows did not render a pinned column cell, so the column showed the
collapsed row's background instead of the uniform column background over those
rows. Render the cell (a placeholder for these aggregate rows) for column
continuity, matching the root row.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
The pinnedAttribute query param is specific to the trace waterfall and is
meaningless on breadcrumb destinations (Traces, insights, dashboards, etc.).
Add it to the trace-view-only params already omitted from breadcrumb targets so
it doesn't leak into the Traces (and other) back-navigation links.

Refs EXP-1077

Co-Authored-By: Claude <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 6, 2026

Copy link
Copy Markdown

EXP-1077

@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jul 6, 2026
knip flagged three helpers in tracePinnedAttribute as unused exports. They
are only referenced within the module, so remove the export keyword rather
than the code itself.

Refs EXP-1077
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

📊 Type Coverage Diff

Metric Before After Delta
Coverage 93.88% 93.88% ±0%
Typed 134,169 134,262 🟢 +93
Untyped 8,743 8,745 🔴 +2
🔍 1 new type safety issue introduced

Type assertions (as) (1 new)

File Line Detail
static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx 1089 `as HTMLElement

This is informational only and does not block the PR.

nsdeschenes and others added 3 commits July 6, 2026 10:02
registerPinnedColumnRef added a wheel listener when a pinned cell mounted but
returned early on the null ref, so unmounting virtualized cells never removed
the handler. Remounts and Strict Mode could leave listeners attached to
detached DOM. Return a cleanup that removes the listener, keeping mount and
unmount symmetric.

Refs EXP-1077
Co-Authored-By: Claude <noreply@anthropic.com>
The pinned attribute column is opaque (it scrolls over the tree), so it kept a
plain background while the rest of the row picked up the focused, search-result,
and collapsed backgrounds — leaving the pinned cell as a mismatched block. Paint
the column with the same background in each of those row states. Text stays
uniform so it still doesn't inherit the row's error/warning color.

Refs EXP-1077
Co-Authored-By: Claude <noreply@anthropic.com>
The opaque pinned column sits on top of the row and covered the top and bottom
of the row's inset highlight/focus outline, leaving a gap in the blue border.
Redraw the top and bottom edges on the pinned cell in the Highlight and focus
states; its left and right edges are already drawn by the pinned column line and
the divider.

Refs EXP-1077
Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread static/app/views/performance/newTraceDetails/trace.tsx
nsdeschenes and others added 2 commits July 6, 2026 10:39
The pinned column header and its border line rendered whenever a pinned
attribute was set, but the virtualized list swaps in TraceLoadingRow (which
has no pinned cell) while the trace loads. During initial load or a refetch
after pinning, the header and line were left orphaned above placeholder rows
with no matching column cells.

Derive the loading condition once and reuse it so the header and line only
render alongside real rows. The pinned column is an absolutely-positioned
overlay, so hiding it during load reveals the tree underneath rather than
leaving an empty gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change the attribute actions menu copy from "Pin as column"/"Unpin
column" to "Pin attribute"/"Unpin attribute" to better describe the
action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nsdeschenes

Copy link
Copy Markdown
Contributor Author

@sentry review

@nsdeschenes

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 847c596. Configure here.

useLayoutEffect(() => {
manager.setPinnedColumnWidth(pinnedAttribute ? PINNED_COLUMN_WIDTH : 0);
manager.resetPinnedColumnScroll();
}, [manager, pinnedAttribute]);

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.

Pinned width active while loading

Medium Severity

When pinnedAttribute is in the URL, useLayoutEffect always sets the view manager’s pinned column width to 160px, but the header, border, and row cells only render once loading finishes. During loading, horizontal scroll clamping and the tree scrollbar behave as if the column is present even though it is not, which skews layout and can hide tree text when the column appears.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 847c596. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant