Repository: facebook/react
Pull request: [DevTools] Remove markers from Suspense timeline

The marker were intended so that you can click on the "meaningful" milestones. But there's just too many in a sufficiently large app resulting in collapsed markers. We could make the input scrollable though for now I prefer having the whole timeline hoverable at once.

Maybe there's a better heuristic like "biggest N" boundaries instead. For now I added `X / N` display for orientation like we have in the Profiler tab.

Before
<img width="1218" height="138" alt="CleanShot 2025-09-01 at 16 27 15@2x" src="https://github.com/user-attachments/assets/18257df5-34c5-4915-9035-1b473f82fde8" />
After:
<img width="1458" height="96" alt="CleanShot 2025-09-01 at 16 45 55@2x" src="https://github.com/user-attachments/assets/ef504b0e-4cd4-4026-96a7-23d398ceb56b" />



Changed files:
filename: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.css
patch: @@ -2,7 +2,7 @@
   width: 100%;
   display: flex;
   flex-direction: row;
-  padding: 0 0.25rem;
+  padding: 0.25rem;
 }
 
 .SuspenseTimelineInput {
@@ -21,20 +21,6 @@
   max-width: 3rem;
 }
 
-.SuspenseTimelineMarkers {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-}
-
-.SuspenseTimelineMarkers > * {
-  flex: 1 1 0;
-  overflow: visible;
-  visibility: hidden;
-  width: 0
+.SuspenseTimelineProgressIndicator {
+  align-self: center;
 }
-
-.SuspenseTimelineActiveMarker {
-  visibility: visible;
-}
-

filename: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js
patch: @@ -11,14 +11,7 @@ import type {Element, SuspenseNode} from '../../../frontend/types';
 import type Store from '../../store';
 
 import * as React from 'react';
-import {
-  useContext,
-  useId,
-  useLayoutEffect,
-  useMemo,
-  useRef,
-  useState,
-} from 'react';
+import {useContext, useLayoutEffect, useMemo, useRef, useState} from 'react';
 import {BridgeContext, StoreContext} from '../context';
 import {TreeDispatcherContext} from '../Components/TreeContext';
 import {useHighlightHostInstance} from '../hooks';
@@ -112,30 +105,6 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
     setValue(max);
   }
 
-  const markersID = useId();
-  const markers: React.Node[] = useMemo(() => {
-    return timeline.map((suspense, index) => {
-      const takesUpSpace =
-        suspense.rects !== null &&
-        suspense.rects.some(rect => {
-          return rect.width > 0 && rect.height > 0;
-        });
-
-      return takesUpSpace ? (
-        <option
-          key={suspense.id}
-          className={
-            index === value ? styles.SuspenseTimelineActiveMarker : undefined
-          }
-          value={index}>
-          #{index + 1}
-        </option>
-      ) : (
-        <option key={suspense.id} />
-      );
-    });
-  }, [timeline, value]);
-
   if (rootID === undefined) {
     return <div className={styles.SuspenseTimelineInput}>Root not found.</div>;
   }
@@ -219,25 +188,26 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
   }
 
   return (
-    <div className={styles.SuspenseTimelineInput}>
-      <input
-        className={styles.SuspenseTimelineSlider}
-        type="range"
-        min={min}
-        max={max}
-        list={markersID}
-        value={value}
-        onBlur={handleBlur}
-        onChange={handleChange}
-        onFocus={handleFocus}
-        onPointerMove={handlePointerMove}
-        onPointerUp={clearHighlightHostInstance}
-        ref={inputRef}
-      />
-      <datalist id={markersID} className={styles.SuspenseTimelineMarkers}>
-        {markers}
-      </datalist>
-    </div>
+    <>
+      <div>
+        {value}/{max}
+      </div>
+      <div className={styles.SuspenseTimelineInput}>
+        <input
+          className={styles.SuspenseTimelineSlider}
+          type="range"
+          min={min}
+          max={max}
+          value={value}
+          onBlur={handleBlur}
+          onChange={handleChange}
+          onFocus={handleFocus}
+          onPointerMove={handlePointerMove}
+          onPointerUp={clearHighlightHostInstance}
+          ref={inputRef}
+        />
+      </div>
+    </>
   );
 }