feat(trace): Add pinnable attribute column to trace waterfall#119051
feat(trace): Add pinnable attribute column to trace waterfall#119051nsdeschenes wants to merge 21 commits into
Conversation
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>
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>
📊 Type Coverage Diff
🔍 1 new type safety issue introducedType assertions (
This is informational only and does not block the PR. |
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>
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>
|
@sentry review |
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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]); |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 847c596. Configure here.


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
pinnedAttributequery param), so it survives reload and is shareable. The param is stripped from header breadcrumb links.additional_attributesparam, skipping the request when it's already present in the trace response.Closes EXP-1077
Example: