@samitouri / QOS-React / commits / 91cd812874

[DevTools] Fix WhatChanged scrolling out of view in profiler sidebar (#36244)

## Summary Previously, WhatChanged and InspectedElementBadges were rendered inside the same scrollable [Content div](https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js#L130-L147) as the commit list. When a component had many render timestamps, scrolling down to pick one caused the "what changed" context to scroll out of view, requiring the user to scroll back up to see it. While the profiler already exposes render details via hover tooltips on fibers, those are transient and pointer-dependent. This change introduces a sticky header section at the top of the Content div that holds the current commit summary, badges, and WhatChanged. The section is collapsible (click or Enter/Space) so users can reclaim vertical space for the commit list when needed. The commit list now sits below the sticky header as a sibling, so it scrolls independently while the summary remains always visible. Closes #36525 --- ### Current Behavior https://github.com/user-attachments/assets/05f2889a-4e0d-4464-965a-2821c63c2d30 ### Proposed UI Apologies on this one. I had to [upload it on YouTube](https://youtu.be/g3KMaqn33G0) since the video is quite big. --- ## How did you test this change? ### Manual Testing | Case | Screenshot | | ------------- | ------------- | | Expanded simple | <img width="1512" height="948" alt="expanded-default" src="https://github.com/user-attachments/assets/00d3bf11-ef98-463e-96ec-c918e58147ff" /> | | Collapsed Simple | <img width="1512" height="948" alt="collapsed-default" src="https://github.com/user-attachments/assets/75d0ae79-c96f-4c55-9dc1-538ba65037b8" /> | | Focused using tab navigation while expanded | <img width="1512" height="948" alt="expanded-tab-focused-simple" src="https://github.com/user-attachments/assets/c4098f2d-1c43-4b04-ab69-1e646e79bc8e" /> | | Focused using tab navigation while collapsed | <img width="1512" height="948" alt="collapsed-tab-focused-simple" src="https://github.com/user-attachments/assets/75840812-b700-44dd-b82d-51d5a867f501" /> | | Long render info( expanded ) | <img width="1512" height="948" alt="long-desc-expanded" src="https://github.com/user-attachments/assets/53c4d776-55d5-4bb0-818d-a70e032e57d7" /> | | Long render info( collapsed ) | <img width="1512" height="948" alt="long-desc-collapsed" src="https://github.com/user-attachments/assets/45eb7f6f-fcc6-4f7b-88e7-e3ece9e59d19" /> | #### Hover (expanded) https://github.com/user-attachments/assets/d04668d3-1e0e-44c7-af7e-087d246ec07d #### Hover (collapsed) https://github.com/user-attachments/assets/86638b8d-60c2-4cfa-b357-c1104e76767e --- ## Why this matters This is more than a visual polish issue: * The Profiler is fundamentally a context-driven debugging tool * Losing the “why did this render?” explanation during navigation interrupts analysis flow * The issue becomes progressively worse on large applications or heavily interactive surfaces * Hover tooltips are not an adequate replacement because they are transient and pointer-dependent * Keyboard users are disproportionately affected due to repeated navigation and scrolling In practice, this increases cognitive load during performance debugging and makes long profiling sessions unnecessarily cumbersome.

Kurtd Daniel Bigtas committed Jul 7, 2026 at 21:18 UTC 91cd8128741854d3281ca6511d8c3d76e877be4c
3 files changed +55 -23
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css
+6 -5
@@ -1,8 +1,9 @@
1 .Root {
2 user-select: none;
3 - display: inline-flex;
4 -}
5 -
6 -.Root *:not(:first-child) {
7 - margin-left: 0.25rem;
3 + flex: 1 1 auto;
4 + min-width: 0;
5 + display: flex;
6 + flex-wrap: wrap;
7 + justify-content: start;
8 + gap: 0.25rem;
9 }
packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css
+25 -2
@@ -8,12 +8,35 @@
8 }
9
10 .Content {
11 - padding: 0.5rem;
11 + padding-bottom: 0.5rem;
12 user-select: none;
13 - overflow-y: auto;
13 + display: flex;
14 + flex-direction: column;
15 + min-height: 0;
16 + height: 100%;
17 +}
18 +
19 +.RenderInfo {
20 + flex: 0 0 auto;
21 display: flex;
22 flex-direction: column;
23 gap: 0.5rem;
24 + padding: 0.5rem;
25 + border-bottom: 1px solid var(--color-border);
26 + background: var(--color-background);
27 +}
28 +
29 +.RenderInfoContent {
30 + display: flex;
31 + flex-direction: column;
32 + gap: 0.75rem;
33 +}
34 +
35 +.CommitList {
36 + flex: 1;
37 + min-height: 0;
38 + overflow-y: auto;
39 + padding: 0.5rem;
40 }
41
42 .Component {
packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js
+24 -16
@@ -129,23 +129,31 @@ export default function SidebarSelectedFiberInfo(): React.Node {
129 <ButtonIcon type="close" />
130 </Button>
131 </div>
132 - <div className={styles.Content} onKeyDown={handleKeyDown} tabIndex={0}>
133 - {node != null && (
134 - <InspectedElementBadges
135 - hocDisplayNames={node.hocDisplayNames}
136 - compiledWithForget={node.compiledWithForget}
137 - />
138 - )}
139 - <WhatChanged fiberID={selectedFiberID as any as number} />
140 - {listItems.length > 0 && (
141 - <div>
142 - <label className={styles.Label}>Rendered at: </label>
143 - {listItems}
132 + <div className={styles.Content}>
133 + <div className={styles.RenderInfo}>
134 + <div className={styles.RenderInfoContent}>
135 + {node != null && (
136 + <InspectedElementBadges
137 + hocDisplayNames={node.hocDisplayNames}
138 + compiledWithForget={node.compiledWithForget}
139 + />
140 + )}
141 + <WhatChanged fiberID={selectedFiberID as any as number} />
142 </div>
145 - )}
146 - {listItems.length === 0 && (
147 - <div>Did not render on the client during this profiling session.</div>
148 - )}
143 + </div>
144 + <div className={styles.CommitList} onKeyDown={handleKeyDown}>
145 + {listItems.length > 0 && (
146 + <div>
147 + <label className={styles.Label}>Rendered at: </label>
148 + {listItems}
149 + </div>
150 + )}
151 + {listItems.length === 0 && (
152 + <div>
153 + Did not render on the client during this profiling session.
154 + </div>
155 + )}
156 + </div>
157 </div>
158 </Fragment>
159 );