Repository: facebook/react
Pull request: [DevTools] Enable Suspense tab by default

## Summary

We only showed it conditionally during initial testing. To help discovery on initial release, we're enabling it everywhere. 

Conditionally showing was also annoying since it requires a waterfall on initial load that leads to shifting of DevTools tabs.

## How did you test this change?

- [x] Shows with React 19.2 (e.g. https://new.csb.app/)


Changed files:
filename: packages/react-devtools-extensions/src/main/index.js
patch: @@ -167,10 +167,6 @@ function createBridgeAndStore() {
     supportsClickToInspect: true,
   });
 
-  store.addListener('enableSuspenseTab', () => {
-    createSuspensePanel();
-  });
-
   store.addListener('settingsUpdated', (hookSettings, componentFilters) => {
     chrome.storage.local.set({...hookSettings, componentFilters});
   });
@@ -565,8 +561,7 @@ function mountReactDevTools() {
   createProfilerPanel();
   createSourcesEditorPanel();
   createElementsInspectPanel();
-  // Suspense Tab is created via the hook
-  // TODO(enableSuspenseTab): Create eagerly once Suspense tab is stable
+  createSuspensePanel();
 }
 
 let reactPollingInstance = null;

filename: packages/react-devtools-shared/src/backend/agent.js
patch: @@ -38,7 +38,7 @@ import type {
   ElementType,
 } from 'react-devtools-shared/src/frontend/types';
 import type {GroupItem} from './views/TraceUpdates/canvas';
-import {gte, isReactNativeEnvironment} from './utils';
+import {isReactNativeEnvironment} from './utils';
 import {
   sessionStorageGetItem,
   sessionStorageRemoveItem,
@@ -961,16 +961,6 @@ export default class Agent extends EventEmitter<{
 
     rendererInterface.setTraceUpdatesEnabled(this._traceUpdatesEnabled);
 
-    const renderer = rendererInterface.renderer;
-    if (renderer !== null) {
-      const devRenderer = renderer.bundleType === 1;
-      const enableSuspenseTab =
-        devRenderer && gte(renderer.version, '19.3.0-canary');
-      if (enableSuspenseTab) {
-        this._bridge.send('enableSuspenseTab');
-      }
-    }
-
     // When the renderer is attached, we need to tell it whether
     // we remember the previous selection that we'd like to restore.
     // It'll start tracking mounts for matches to the last selection path.

filename: packages/react-devtools-shared/src/bridge.js
patch: @@ -199,7 +199,6 @@ export type BackendEvents = {
   backendInitialized: [],
   backendVersion: [string],
   bridgeProtocol: [BridgeProtocol],
-  enableSuspenseTab: [],
   extensionBackendInitialized: [],
   fastRefreshScheduled: [],
   getSavedPreferences: [],

filename: packages/react-devtools-shared/src/devtools/store.js
patch: @@ -143,7 +143,6 @@ export default class Store extends EventEmitter<{
   backendVersion: [],
   collapseNodesByDefault: [],
   componentFilters: [],
-  enableSuspenseTab: [],
   error: [Error],
   hookSettings: [$ReadOnly<DevToolsHookSettings>],
   hostInstanceSelected: [Element['id'] | null],
@@ -239,8 +238,6 @@ export default class Store extends EventEmitter<{
   _supportsClickToInspect: boolean = false;
   _supportsTimeline: boolean = false;
   _supportsTraceUpdates: boolean = false;
-  // Dynamically set if the renderer supports the Suspense tab.
-  _supportsSuspenseTab: boolean = false;
 
   _isReloadAndProfileFrontendSupported: boolean = false;
   _isReloadAndProfileBackendSupported: boolean = false;
@@ -341,7 +338,6 @@ export default class Store extends EventEmitter<{
     bridge.addListener('hookSettings', this.onHookSettings);
     bridge.addListener('backendInitialized', this.onBackendInitialized);
     bridge.addListener('selectElement', this.onHostInstanceSelected);
-    bridge.addListener('enableSuspenseTab', this.onEnableSuspenseTab);
   }
 
   // This is only used in tests to avoid memory leaks.
@@ -2394,15 +2390,6 @@ export default class Store extends EventEmitter<{
     }
   }
 
-  get supportsSuspenseTab(): boolean {
-    return this._supportsSuspenseTab;
-  }
-
-  onEnableSuspenseTab = (): void => {
-    this._supportsSuspenseTab = true;
-    this.emit('enableSuspenseTab');
-  };
-
   // The Store should never throw an Error without also emitting an event.
   // Otherwise Store errors will be invisible to users,
   // but the downstream errors they cause will be reported as bugs.

filename: packages/react-devtools-shared/src/devtools/views/DevTools.js
patch: @@ -135,21 +135,7 @@ const suspenseTab = {
   title: 'React Suspense',
 };
 
-const defaultTabs = [componentsTab, profilerTab];
-const tabsWithSuspense = [componentsTab, profilerTab, suspenseTab];
-
-function useIsSuspenseTabEnabled(store: Store): boolean {
-  const subscribe = useCallback(
-    (onStoreChange: () => void) => {
-      store.addListener('enableSuspenseTab', onStoreChange);
-      return () => {
-        store.removeListener('enableSuspenseTab', onStoreChange);
-      };
-    },
-    [store],
-  );
-  return React.useSyncExternalStore(subscribe, () => store.supportsSuspenseTab);
-}
+const tabs = [componentsTab, profilerTab, suspenseTab];
 
 export default function DevTools({
   bridge,
@@ -183,8 +169,6 @@ export default function DevTools({
     LOCAL_STORAGE_DEFAULT_TAB_KEY,
     defaultTab,
   );
-  const enableSuspenseTab = useIsSuspenseTabEnabled(store);
-  const tabs = enableSuspenseTab ? tabsWithSuspense : defaultTabs;
 
   let tab = currentTab;
 
@@ -364,17 +348,15 @@ export default function DevTools({
                                         }
                                       />
                                     </div>
-                                    {enableSuspenseTab && (
-                                      <div
-                                        className={styles.TabContent}
-                                        hidden={tab !== 'suspense'}>
-                                        <SuspenseTab
-                                          portalContainer={
-                                            suspensePortalContainer
-                                          }
-                                        />
-                                      </div>
-                                    )}
+                                    <div
+                                      className={styles.TabContent}
+                                      hidden={tab !== 'suspense'}>
+                                      <SuspenseTab
+                                        portalContainer={
+                                          suspensePortalContainer
+                                        }
+                                      />
+                                    </div>
                                   </div>
                                   {editorPortalContainer ? (
                                     <EditorPane