Skip to content

[DevTools] Avoid scrollbars in Suspense breadcrumbs#468

Closed
everettbu wants to merge 4 commits into
mainfrom
sebbie/suspense-breadcrumbs-overflow
Closed

[DevTools] Avoid scrollbars in Suspense breadcrumbs#468
everettbu wants to merge 4 commits into
mainfrom
sebbie/suspense-breadcrumbs-overflow

Conversation

@everettbu

@everettbu everettbu commented Feb 5, 2026

Copy link
Copy Markdown

Mirror of facebook/react#35700
Original author: eps1lon


Summary

Alternate to react/react#34914

The Suspense breadcrumbs are only a single line height high. Having a scrollbar in such a container looks bad since it takes up 50% of the space. MacOS also has this default where the scrollbar overlays the container on touch devices without a mouse attached (i.e. MacBook default).

Now we use the same UI we use in the Owner stack in the Components tab i.e. shrink to showing a single item and moving navigation into a menu.

How did you test this change?

Before:
CleanShot 2026-02-05 at 16 17 57`@2x`

After:

CleanShot.2026-02-05.at.14.59.44.mp4

@everettbu everettbu added CLA Signed React Core Team Opened by a member of the React Core Team labels Feb 5, 2026
@everettbu
everettbu force-pushed the sebbie/suspense-breadcrumbs-overflow branch from 6f44da2 to d538e85 Compare February 5, 2026 15:25
@everettbu
everettbu marked this pull request as ready for review February 5, 2026 15:25
@greptile-apps

greptile-apps Bot commented Feb 5, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR updates the Suspense tab breadcrumbs UI to avoid horizontal scrollbars in the one-line header area by switching to an overflow-aware presentation (similar to the Components tab Owner stack): when the breadcrumb list would overflow, it collapses into a menu + navigation controls.

Implementation-wise, the old SuspenseTab.css scrollbar container wrapper is removed, and SuspenseBreadcrumbs now renders either a flat <ol> list (non-overflow) or a Reach Menu-based dropdown (overflow), with width/overflow detection using useIsOverflowing and a ResizeObserver-based measurement of total child width.

Confidence Score: 3/5

  • Not safe to merge as-is due to a few correctness issues in SuspenseBreadcrumbs.
  • Core idea is straightforward and matches existing patterns (OwnersStack), but the new breadcrumbs menu code has Flow/type inconsistencies, references missing CSS module classes, and assumes ResizeObserver exists (can throw in some environments).
  • packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js and SuspenseBreadcrumbs.css

Important Files Changed

Filename Overview
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.css Adds flex container + Reach menu button/list styling for overflow breadcrumbs.
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js Introduces overflow-aware breadcrumbs menu, but has Flow/type drift, missing CSS module classes, and unguarded ResizeObserver usage.
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css Removes old horizontal scrollbar styling for breadcrumbs container.
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js Inlines SuspenseBreadcrumbs into header (removes wrapper div).

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +177 to +181
type SuspenseBreadcrumbsDropdownProps = {
lineage: $ReadOnlyArray<SuspenseNode['id']>,
selectedIndex: number,
selectElement: (id: SuspenseNode['id']) => void,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Broken Flow props type
SuspenseBreadcrumbsDropdownProps declares selectedIndex, but SuspenseBreadcrumbsDropdown is invoked without it (SuspenseBreadcrumbs.js:146-149) and the function signature also omits it. This will fail Flow checking (and makes the type misleading).

Suggested change
type SuspenseBreadcrumbsDropdownProps = {
lineage: $ReadOnlyArray<SuspenseNode['id']>,
selectedIndex: number,
selectElement: (id: SuspenseNode['id']) => void,
};
type SuspenseBreadcrumbsDropdownProps = {
lineage: $ReadOnlyArray<SuspenseNode['id']>,
- selectedIndex: number,
selectElement: (id: SuspenseNode['id']) => void,
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
Line: 177:181

Comment:
**Broken Flow props type**
`SuspenseBreadcrumbsDropdownProps` declares `selectedIndex`, but `SuspenseBreadcrumbsDropdown` is invoked without it (`SuspenseBreadcrumbs.js:146-149`) and the function signature also omits it. This will fail Flow checking (and makes the type misleading).

```suggestion
 type SuspenseBreadcrumbsDropdownProps = {
   lineage: $ReadOnlyArray<SuspenseNode['id']>,
-  selectedIndex: number,
   selectElement: (id: SuspenseNode['id']) => void,
 };
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +193 to +197
<MenuItem
key={suspenseNodeID}
className={`${styles.Component}`}
onSelect={selectElement.bind(null, suspenseNodeID)}>
{node === null ? 'Unknown' : node.name || 'Unknown'}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing CSS class
className={${styles.Component}} references styles.Component, but SuspenseBreadcrumbs.css doesn't define a .Component class, so this will end up as an empty class and menu items won't get the intended styling.

If this was meant to share the menu item styling from OwnersStack, it needs a local class in SuspenseBreadcrumbs.css (or use an existing class name that actually exists in this module).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
Line: 193:197

Comment:
**Missing CSS class**
`className={`${styles.Component}`}` references `styles.Component`, but `SuspenseBreadcrumbs.css` doesn't define a `.Component` class, so this will end up as an empty class and menu items won't get the intended styling.

If this was meant to share the menu item styling from OwnersStack, it needs a local class in `SuspenseBreadcrumbs.css` (or use an existing class name that actually exists in this module).

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +244 to +247
<Button
className={parent !== null ? undefined : styles.NotInStore}
onClick={parent !== null ? selectElement.bind(null, parentID) : null}
title={`Up to ${parent === null ? 'Unknown' : parent.name || 'Unknown'}`}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing NotInStore style
styles.NotInStore is used when the parent node is missing (SuspenseBreadcrumbs.js:245), but there is no .NotInStore in SuspenseBreadcrumbs.css. As a result, the "disabled" styling won't apply (and the button will look interactive even though onClick is null).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
Line: 244:247

Comment:
**Missing NotInStore style**
`styles.NotInStore` is used when the parent node is missing (`SuspenseBreadcrumbs.js:245`), but there is no `.NotInStore` in `SuspenseBreadcrumbs.css`. As a result, the "disabled" styling won't apply (and the button will look interactive even though `onClick` is null).

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +285 to +286
const ResizeObserver = container.ownerDocument.defaultView.ResizeObserver;
const observer = new ResizeObserver(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ResizeObserver may be undefined
This assumes container.ownerDocument.defaultView.ResizeObserver exists and then instantiates it unconditionally. In environments without ResizeObserver (or if it’s not exposed on that window), this will throw at runtime when breadcrumbs render in the non-overflowing state.

OwnersStack avoids this by only measuring without creating a new observer; here you probably want to guard ResizeObserver before new ResizeObserver(...).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
Line: 285:286

Comment:
**ResizeObserver may be undefined**
This assumes `container.ownerDocument.defaultView.ResizeObserver` exists and then instantiates it unconditionally. In environments without `ResizeObserver` (or if it’s not exposed on that window), this will throw at runtime when breadcrumbs render in the non-overflowing state.

OwnersStack avoids this by only measuring without creating a new observer; here you probably want to guard `ResizeObserver` before `new ResizeObserver(...)`.

How can I resolve this? If you propose a fix, please make it concise.

@everettbu

Copy link
Copy Markdown
Author

Upstream PR was closed or merged. Code is synced via branch mirror.

@everettbu everettbu closed this Feb 5, 2026
@everettbu
everettbu deleted the sebbie/suspense-breadcrumbs-overflow branch February 5, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants