[DevTools] Always include the root in the timeline and select it by default (#34654)

Rebased on #34454.

Always include the root in the timeline even if it has no unique
suspenders, since even if it won't suspend, we have to be able to see
that and step to one step before the next boundary to see the first
boundary that does suspend in its fallback state.

Also, if there's no current selection on initial mount, select the last
entry in the timeline. We usually do this with `selectedSuspenseID` but
that doesn't happen on initial load. So this does it on initial load if
nothing else is selected by then. That way when you reload you get the
initial root selected.

There's a problem here because we should really use one source of truth
and `selectedSuspenseID` doesn't really do anything now. Either it
should be its separate source of truth and you can't show components in
the side-panel or it should be derived from the other state.

If it's derived, once there's a selection, e.g. in the root, then even
if new timelines load it will never change but that's probably a good
thing.
This commit is contained in:
Sebastian Markbåge 2025-10-02 14:20:02 -04:00 committed by GitHub
parent 70b52beca6
commit ced705d756
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -34,6 +34,10 @@ import {
SuspenseTreeStateContext,
} from './SuspenseTreeContext';
import {StoreContext, OptionsContext} from '../context';
import {
TreeDispatcherContext,
TreeStateContext,
} from '../Components/TreeContext';
import Button from '../Button';
import Toggle from '../Toggle';
import typeof {SyntheticPointerEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
@ -181,6 +185,18 @@ function SuspenseTab(_: {}) {
treeListHorizontalFraction,
} = state;
const {inspectedElementID} = useContext(TreeStateContext);
const {timeline} = useContext(SuspenseTreeStateContext);
const treeDispatch = useContext(TreeDispatcherContext);
useLayoutEffect(() => {
// If the inspected element is still null and we've loaded a timeline, we can set the initial selection.
// TODO: This tab should use its own source of truth instead so we only show suspense boundaries.
if (inspectedElementID === null && timeline.length > 0) {
const milestone = timeline[timeline.length - 1];
treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone});
}
}, [timeline, inspectedElementID]);
useLayoutEffect(() => {
const wrapperElement = wrapperTreeRef.current;