Skip to content

Issue details event navigation collapses to a single "First" tab with the overflow trigger drawn over it #124247

Description

@aashutoshrathi

Product Area: Issues

Environment

SaaS (https://sentry.io/)

Steps to Reproduce

  1. Open any issue on sentry.io (Issues > pick an issue).
  2. Look at the event navigation bar above the event, next to the < > arrows and the "View More Events" / "Copy as" buttons.
  3. Resize the window to any width. The result does not change.

Expected Result

The three event preset tabs are visible side by side, as they were until Sep 10:

First | Latest | Recommended

Actual Result

Only "First" is visible. "Latest" and "Recommended" are inside the overflow menu, and the overflow trigger ("...") is drawn on top of the "First" text. The tab list wrapper measures about 32px wide in DevTools, regardless of viewport width.

Event navigation showing only "First", with the overflow trigger overlapping the text

Jumping to the latest event is a one-click action I use many times a day. It now takes two clicks (open the overflow menu, then pick "Latest") even though there is plenty of room for all three tabs.

Regression

Started with #123879 (f06d9ec, merged Sep 10, 2026). That PR removed the disableOverflow prop from Tabs, which turned on the automatic overflow logic for this tab list.

Root Cause

useOverflowTabs in static/app/components/core/tabs/tabList.tsx compares two numbers that are measured differently:

  • available = outerWrap.clientWidth is rounded to a whole pixel.
  • fullWidth is the sum of each tab's getBoundingClientRect().width, which is fractional.

On the issue details page the tab list sits inside a shrink-to-fit flex item (TourElement div > NavigationWrapper), so the wrapper is exactly as wide as the visible tabs. For example the tabs sum to 223.078px and clientWidth reports 223. 223.078 <= 223 is false, so the last tab is moved into the menu. Overflowing tabs render display: none, the shrink-to-fit wrapper shrinks around the remaining tabs, the ResizeObserver fires, and the check fails again. This repeats until only the first tab is left, because the first tab is always kept:

if (index === 0 || nextUsed <= budget) {

At that point the wrapper is about as wide as one tab, the budget (available - 48) is negative, and the absolutely positioned overflow trigger (right: 0) lands on top of the first tab.

Before #123879 this tab list passed disableOverflow, so the measurement never ran. Restoring the old TabsWrap CSS (flex-basis: auto) does not fix it, so the flex-basis change in that PR is not the cause.

Reproduction outside Sentry

A static page with the same DOM and CSS (shrink-to-fit flex parent, TabsWrap, wrapper Container, ul grid, absolutely positioned trigger) and the overflow logic ported verbatim shows the same cascade in headless Chromium at every viewport width from 520px to 1440px:

223.000/223.078 -> hide [Recommended]
108.000/223.078 -> hide [Latest, Recommended]
 47.000/223.078 -> stable: only "First", trigger overlaps it

Proposed Fix

Measure the wrapper the same way the tabs are measured:

-    const available = outerWrap.clientWidth;
+    const available = outerWrap.getBoundingClientRect().width;

With that change the same page shows all three tabs at every width and the trigger never overlaps:

Event navigation with the fix: First, Latest and Rec. side by side

A possible follow-up hardening: when available is smaller than the 48px reserve, the forced first tab and the trigger will always overlap. Reserving the trigger's space in layout (padding on the wrapper while overflowing) would remove that case.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions