Repository: facebook/react
Pull request: [DevTools] Tweak the presentation of the Promise value

Show the value as "fulfilled: Type" or "rejected: Type" immediately instead of having to expand it twice. We could show all the properties of the object immediately like we do in the Performance Track but it's not always particularly interesting data in the value that isn't already in the header.

I also moved it to the end after the stack traces since I think the stack is more interesting but I'm also visually trying to connect the stack trace with the "name" since typically the "name" will come from part of the stack trace.

Before:

<img width="517" height="433" alt="Screenshot 2025-08-03 at 11 39 49 PM" src="https://github.com/user-attachments/assets/ad28d8a2-c149-4957-a393-20ff3932a819" />

After:

<img width="520" height="476" alt="Screenshot 2025-08-03 at 11 58 35 PM" src="https://github.com/user-attachments/assets/53a755b0-bb68-4305-9d16-d6fac7ca4910" />


Changed files:
filename: packages/react-client/src/ReactFlightPerformanceTrack.js
patch: @@ -490,7 +490,7 @@ export function logComponentAwait(
       if (typeof value === 'object' && value !== null) {
         addObjectToProperties(value, properties, 0, '');
       } else if (value !== undefined) {
-        addValueToProperties('Resolved', value, properties, 0, '');
+        addValueToProperties('awaited value', value, properties, 0, '');
       }
       const tooltipText = getIOLongName(
         asyncInfo.awaited,
@@ -547,7 +547,7 @@ export function logIOInfoErrored(
             String(error.message)
           : // eslint-disable-next-line react-internal/safe-string-coercion
             String(error);
-      const properties = [['Rejected', message]];
+      const properties = [['rejected with', message]];
       const tooltipText =
         getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected';
       debugTask.run(

filename: packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css
patch: @@ -97,11 +97,11 @@
 }
 
 .CollapsableContent {
-  padding: 0.25rem 0;
+  margin-top: -0.25rem;
 }
 
 .PreviewContainer {
-  padding: 0 0.25rem 0.25rem 0.25rem;
+  padding: 0.25rem;
 }
 
 .TimeBarContainer {

filename: packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
patch: @@ -107,11 +107,10 @@ function SuspendedByRow({
   }
 
   const value: any = asyncInfo.awaited.value;
-  const isErrored =
-    value !== null &&
-    typeof value === 'object' &&
-    value[meta.name] === 'rejected Thenable';
-
+  const metaName =
+    value !== null && typeof value === 'object' ? value[meta.name] : null;
+  const isFulfilled = metaName === 'fulfilled Thenable';
+  const isRejected = metaName === 'rejected Thenable';
   return (
     <div className={styles.CollapsableRow}>
       <Button
@@ -136,7 +135,7 @@ function SuspendedByRow({
         <div className={styles.TimeBarContainer}>
           <div
             className={
-              !isErrored ? styles.TimeBarSpan : styles.TimeBarSpanErrored
+              !isRejected ? styles.TimeBarSpan : styles.TimeBarSpanErrored
             }
             style={{
               left: left.toFixed(2) + '%',
@@ -147,6 +146,20 @@ function SuspendedByRow({
       </Button>
       {isOpen && (
         <div className={styles.CollapsableContent}>
+          {stack !== null && stack.length > 0 && (
+            <StackTraceView stack={stack} />
+          )}
+          {owner !== null && owner.id !== inspectedElement.id ? (
+            <OwnerView
+              key={owner.id}
+              displayName={owner.displayName || 'Anonymous'}
+              hocDisplayNames={owner.hocDisplayNames}
+              compiledWithForget={owner.compiledWithForget}
+              id={owner.id}
+              isInStore={store.containsElement(owner.id)}
+              type={owner.type}
+            />
+          ) : null}
           <div className={styles.PreviewContainer}>
             <KeyValue
               alphaSort={true}
@@ -158,27 +171,27 @@ function SuspendedByRow({
               element={element}
               hidden={false}
               inspectedElement={inspectedElement}
-              name={'Promise'}
-              path={[index, 'awaited', 'value']}
+              name={
+                isFulfilled
+                  ? 'awaited value'
+                  : isRejected
+                    ? 'rejected with'
+                    : 'pending value'
+              }
+              path={
+                isFulfilled
+                  ? [index, 'awaited', 'value', 'value']
+                  : isRejected
+                    ? [index, 'awaited', 'value', 'reason']
+                    : [index, 'awaited', 'value']
+              }
               pathRoot="suspendedBy"
               store={store}
-              value={asyncInfo.awaited.value}
+              value={
+                isFulfilled ? value.value : isRejected ? value.reason : value
+              }
             />
           </div>
-          {stack !== null && stack.length > 0 && (
-            <StackTraceView stack={stack} />
-          )}
-          {owner !== null && owner.id !== inspectedElement.id ? (
-            <OwnerView
-              key={owner.id}
-              displayName={owner.displayName || 'Anonymous'}
-              hocDisplayNames={owner.hocDisplayNames}
-              compiledWithForget={owner.compiledWithForget}
-              id={owner.id}
-              isInStore={store.containsElement(owner.id)}
-              type={owner.type}
-            />
-          ) : null}
         </div>
       )}
     </div>

filename: packages/react-devtools-shared/src/hydration.js
patch: @@ -21,6 +21,8 @@ import type {
   InspectedElementPath,
 } from 'react-devtools-shared/src/frontend/types';
 
+import noop from 'shared/noop';
+
 export const meta = {
   inspectable: (Symbol('inspectable'): symbol),
   inspected: (Symbol('inspected'): symbol),
@@ -317,6 +319,15 @@ export function dehydrate(
         };
       }
 
+      if (
+        data.status === 'resolved_model' ||
+        data.status === 'resolve_module'
+      ) {
+        // This looks it's a lazy initialization pattern such in Flight.
+        // Since we're about to inspect it. Let's eagerly initialize it.
+        data.then(noop);
+      }
+
       switch (data.status) {
         case 'fulfilled': {
           const unserializableValue: Unserializable = {