Commit Graph
1038 Commits
Author SHA1 Message Date
Sebastian Markbåge 3a5b326d81 [Fiber] Trigger default indicator for isomorphic async actions with no root associated (#33190)
Stacked on #33160, #33162, #33186 and #33188.

We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.
2025-05-13 16:10:28 -04:00
Sebastian Markbåge b480865db0 [Fiber] Always flush Default priority in the microtask if a Transition was scheduled (#33186)
Stacked on #33160.

The purpose of this is to avoid calling `onDefaultTransitionIndicator`
when a Default priority update acts as the loading indicator, but still
call it when unrelated Default updates happens nearby.

When we schedule Default priority work that gets batched with other
events in the same frame more or less. This helps optimize by doing less
work. However, that batching means that we can't separate work from one
setState from another. If we would consider all Default priority work in
a frame when determining whether to show the default we might never show
it in cases like when you have a recurring timer updating something.

This instead flushes the Default priority work eagerly along with the
sync work at the end of the event, if this event scheduled any
Transition work. This is then used to determine if the default indicator
needs to be shown.
2025-05-13 15:52:44 -04:00
Sebastian Markbåge 62d3f36ea7 [Fiber] Trigger default transition indicator if needed (#33160)
Stacked on #33159.

This implements `onDefaultTransitionIndicator`.

The sequence is:

1) In `markRootUpdated` we schedule Transition updates as needing
`indicatorLanes` on the root. This tracks the lanes that currently need
an indicator to either start or remain going until this lane commits.
2) Track mutations during any commit. We use the same hook that view
transitions use here but instead of tracking it just per view transition
scope, we also track a global boolean for the whole root.
3) If a sync/default commit had any mutations, then we clear the
indicator lane for the `currentEventTransitionLane`. This requires that
the lane is still active while we do these commits. See #33159. In other
words, a sync update gets associated with the current transition and it
is assumed to be rendering the loading state for that corresponding
transition so we don't need a default indicator for this lane.
4) At the end of `processRootScheduleInMicrotask`, right before we're
about to enter a new "event transition lane" scope, it is no longer
possible to render any more loading states for the current transition
lane. That's when we invoke `onDefaultTransitionIndicator` for any roots
that have new indicator lanes.
5) When we commit, we remove the finished lanes from `indicatorLanes`
and once that reaches zero again, then we can clean up the default
indicator. This approach means that you can start multiple different
transitions while an indicator is still going but it won't stop/restart
each time. Instead, it'll wait until all are done before stopping.

Follow ups:

- [x] Default updates are currently not enough to cancel because those
aren't flush in the same microtask. That's unfortunate. #33186
- [x] Handle async actions before the setState. Since these don't
necessarily have a root this is tricky. #33190
- [x] Disable for `useDeferredValue`. ~Since it also goes through
`markRootUpdated` and schedules a Transition lane it'll get a default
indicator even though it probably shouldn't have one.~ EDIT: Turns out
this just works because it doesn't go through `markRootUpdated` when
work is left behind.
- [x] Implement built-in DOM version by default. #33162
2025-05-13 15:45:11 -04:00
Jack Pope 4ca97e4891 Clean up enableSiblingPrerendering flag (#32319) 2025-05-08 20:49:23 -04:00
Sebastian Markbåge 693803a9bb Rename Suspense unstable_name to name (#33014)
This was only used by Transition Tracing which isn't really used
anywhere.

However, we want to start using it for other DevTools.
2025-04-24 16:53:34 -04:00
Sebastian Markbåge 539bbdbd86 Warn if you pass a hidden prop to Activity (#32916)
Since `hidden` is a prop on arbitrary DOM elements it's a common mistake
to think that it would also work that way on `<Activity>` but it
doesn't. In fact, we even had this mistakes in our own tests.

Maybe there's an argument that we should actually just support it but we
also have more modes planned.

So this adds a warning. It should also already be covered by TypeScript.
2025-04-15 17:17:22 -04:00
Sebastian Markbåge 8da36d0508 Enable Suspensey Images inside <ViewTransition> subtrees (#32820)
Even if the `enableSuspenseyImages` flag is off.

Started View Transitions already wait for Suspensey Fonts and this is
another Suspensey feature that is even more important for View
Transitions - even though we eventually want it all the time. So this
uses `<ViewTransition>` as an early opt-in for that tree into Suspensey
Images, which we can ship in a minor.

If you're doing an update inside a ViewTransition then we're eligible to
start a ViewTransition in any Transition that might suspend. Even if
that doesn't end up animating after all, we still consider it Suspensey.
We could try to suspend inside the startViewTransition but that's not
how it would work with `enableSuspenseyImages` on and we can't do that
for startGestureTransition.

Even so we still need some opt-in to trigger the Suspense fallback even
before we know whether we'll animate or not. So the simple solution is
just that `<ViewTransition>` opts in the whole subtree into Suspensey
Images in general.

In this PR I disable `enableSuspenseyImages` in experimental so that we
can instead test the path that only enables it inside `<ViewTransition>`
tree since that's the path that would next graduate to a minor.
2025-04-08 17:55:15 -04:00
Sebastian Markbåge efb22d8850 Add Suspensey Images behind a Flag (#32819)
We've known we've wanted this for many years and most of the
implementation was already done for Suspensey CSS. This waits to commit
until images have decoded by default or up to 500ms timeout (same as
suspensey fonts).

It only applies to Transitions, Retries (Suspense), Gesture Transitions
(flag) and Idle (doesn't exist). Sync updates just commit immediately.

`<img loading="lazy" src="..." />` opts out since you explicitly want it
to load lazily in that case.

`<img onLoad={...} src="..." />` also opts out since that implies you're
ok with managing your own reveal.

In the future, we may add an opt in e.g. `<img blocking="render"
src="..." />` that opts into longer timeouts and re-suspends even sync
updates. Perhaps also triggering error boundaries on errors.

The rollout for this would have to go in a major and we may have to
relax the default timeout to not delay too much by default. However, we
can also make this part of `enableViewTransition` so that if you opt-in
by using View Transitions then those animations will suspend on images.
That we could ship in a minor.
2025-04-04 14:54:05 -04:00
lauren 313332d111 [crud] Revert CRUD overload (#32741)
Cleans up this experiment. After some internal experimentation we are
deprioritizing this project for now and may revisit it at a later point.
2025-03-26 12:04:57 -04:00
Sebastian "Sebbie" SilbermannandJack Pope 4a9df08157 Stop creating Owner Stacks if many have been created recently (#32529)
Co-authored-by: Jack Pope <jackpope1@gmail.com>
2025-03-23 15:47:03 -07:00
Ricky daee08562c [activity] remove ref for now (#32645)
Followup from https://github.com/facebook/react/pull/32499

Manual mode is unused and has some bugs such as revealing hidden
boundaries when manually toggling. We also want to change how manual
mode works, and do some refactors to Activity to make it easier to
support. For now we'll remove it, then add it back after the other
changes we have planned.
2025-03-21 14:44:02 -04:00
Ricky 8243f3f063 [bug] Fix component name for Portal and add tests (#32640)
Based off: https://github.com/facebook/react/pull/32499

While looking into `React.lazy` issues for built-ins, I noticed we
already error for `lazy` with build-ins, but we don't have any tests for
`getComponentNameFromType` using all the built-ins. This may be
something we should handle, but for now we should at least have tests.

Here's why: while writing tests, I noticed we check `type` instead of
`$$typeof` for portals:


https://github.com/facebook/react/blob/9cdf8a99edcfd94d7420835ea663edca04237527/packages/react-reconciler/src/ReactPortal.js#L25-L32

This PR adds tests for all the built-ins and fixes the portal bug.

[Commit to
review](https://github.com/facebook/react/pull/32640/commits/e068c167d48d4df01e79db8f13276bb46d7ab439)
2025-03-17 11:23:28 -04:00
Ricky 1a191701fe [refactor] Add element type for Activity (#32499)
This PR separates Activity to it's own element type separate from
Offscreen. The goal is to allow us to add Activity element boundary
semantics during hydration similar to Suspense semantics, without
impacting the Offscreen behavior in suspended children.
2025-03-17 09:17:00 -04:00
Ricky e0fe347967 [flags] remove enableOwnerStacks (#32426)
Bassed off: https://github.com/facebook/react/pull/32425

Wait to land internally.

[Commit to
review.](https://github.com/facebook/react/pull/32426/commits/66aa6a4dbb78106b4f3d3eb367f5c27eb8f30c66)

This has landed everywhere
2025-03-04 12:34:34 -05:00
Sebastian "Sebbie" Silbermann 22e39ea72e Include component name in "async/await is not supported" error message if available (#32435) 2025-02-25 10:48:44 +01:00
Sebastian "Sebbie" Silbermann ed8b68dd17 Stop exporting dev-only methods in OSS production builds (#32200) 2025-02-13 18:26:36 +01:00
Sebastian "Sebbie" SilbermannandYounes Henni c6a7e18636 Ensure captureOwnerStack returns null when no stack is available (#32353)
Co-authored-by: Younes Henni <youneshenniwrites@gmail.com>
2025-02-13 18:09:49 +01:00
lauren 2c5fd26c07 [crud] Merge useResourceEffect into useEffect (#32205)
Merges the useResourceEffect API into useEffect while keeping the
underlying implementation the same. useResourceEffect will be removed in
the next diff.

To fork between behavior we rely on a `typeof` check for the updater or
destroy function in addition to the CRUD feature flag. This does now
have to be checked every time (instead of inlined statically like before
due to them being different hooks) which will incur some non-zero amount
(possibly negligble) of overhead for every effect.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32205).
* #32206
* __->__ #32205
2025-02-11 14:18:50 -05:00
lauren 0461c0d8a4 [crud] Rename useResourceEffect flag (#32204)
Rename the flag in preparation for the overload.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32204).
* #32206
* #32205
* __->__ #32204
2025-02-11 14:05:50 -05:00
Sebastian Markbåge 98418e8902 [Fiber] Suspend the commit while we wait for the previous View Transition to finish (#32002)
Stacked on #31975.

View Transitions cannot handle interruptions in that if you start a new
one before the previous one has finished, it just stops and then
restarts. It doesn't seamlessly transition into the new transition.

This is generally considered a bad thing but I actually think it's quite
good for fire-and-forget animations (gestures is another story). There
are too many examples of bad animations in fast interactions because the
scenario wasn't predicted. Like overlapping toasts or stacked layers
that look bad. The only case interrupts tend to work well is when you do
a strict reversal of an animation like returning to the page you just
left or exiting a modal just being opened. However, we're limited by the
platform even in that regard.

I think one reason interruptions have traditionally been seen as good is
because it's hard if you have a synchronous framework to not interrupt
since your application state has already moved on. We don't have that
limitation since we can suspend commits. We can do all the work to
prepare for the next commit by rendering while the animation is going
but then delay the commit until the previous one finishes.

Another technical limitation earlier animation libraries suffered from
is only have the option to either interrupt or sequence animations since
it's modeling just one change set. Like showing one toast at a time.
That's bad. We don't have that limitation because we can interrupt a
previously suspended commit and start working on a new one instead.
That's what we do for suspended transitions in general. The net effect
is that we batch the commits.

Therefore if you get multiple toasts flying in fast, they can animate as
a batch in together all at once instead of overlapping slightly or being
staggered. Interruptions (often) bad. Staggered animations bad. Batched
animations good.

This PR stashes the currently active View Transition with an expando on
the container that's animating (currently always document). This is
similar to what we do with event handlers etc. We reason we do this with
an expando is that if you have multiple Reacts on the same page they
need to wait for each other. However, one of those might also be the SSR
runtime. So this lets us wait for the SSR runtime's animations to finish
before starting client ones. This could really be a more generic name
since this should ideally be shared across frameworks. It's kind of
strange that this property doesn't already exist in the DOM given that
there can only be one. It would be useful to be able to coordinate this
across libraries.
2025-01-08 13:36:57 -05:00
Sebastian Markbåge e30c6693e4 [Fiber] Delete isMounted internals (#31966)
The public API has been deleted a long time ago so this should be unused
unless it's used by hacks. It should be replaced with an
effect/lifecycle that manually tracks this if you need it.

The problem with this API is how the timing implemented because it
requires Placement/Hydration flags to be cleared too early. In fact,
that's why we also have a separate PlacementDEV flag that works
differently.


https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberCommitWork.js#L2157-L2165

We should be able to remove this code now.
2025-01-08 12:08:30 -05:00
Ricky 6b865330f4 [assert helpers] react-reconciler (#31986)
Based off: https://github.com/facebook/react/pull/31984
2025-01-06 14:12:53 -05:00
Ricky 83be48b9de [tests] fix hidden use() warnings (#31984)
`spyOnDev` is such a footgun.
2025-01-06 14:12:35 -05:00
Sebastian Markbåge c81312e3a7 [Fiber] Refactor Commit Phase into Separate Functions for Before Mutation/Mutation/Layout (#31930)
This is doing some general clean up to be able to split the commit root three phases into three separate async steps.
2025-01-02 14:55:34 -05:00
Ricky 94867f33be [asserts helpers] react package (#31853)
Based off https://github.com/facebook/react/pull/31844

Commit to review:
https://github.com/facebook/react/commit/11aa104e3e70c0accc21f785060b812beb145089

Converts the rest of the `react` package.
2024-12-23 14:58:20 -05:00
Sebastian "Sebbie" Silbermann 518d06d26a Turn off enableYieldingBeforePassive (#31857) 2024-12-19 20:43:01 +01:00
Ricky faf6c4dfdc [flags] Remove debugRenderPhaseSideEffectsForStrictMode (#31839)
This is enabled everywhere, we can just use the inline `__DEV__` checks.
2024-12-18 17:51:12 -05:00
Ricky 74dd2da9ac [flags] Remove enableModernStrictMode (#31838)
This is enabled everywhere.
2024-12-18 14:45:37 -05:00
Sebastian Markbåge facec3ee71 [Fiber] Schedule passive effects using the regular ensureRootIsScheduled flow (#31785)
This treats workInProgressRoot work and rootWithPendingPassiveEffects
the same way. Basically as long as there's some work on the root, yield
the current task. Including passive effects. This means that passive
effects are now a continuation instead of a separate callback. This can
mean they're earlier or later than before. Later for Idle in case
there's other non-React work. Earlier for same Default if there's other
Default priority work.

This makes sense since increasing priority of the passive effects beyond
Idle doesn't really make sense for an Idle render.

However, for any given render at same priority it's more important to
complete this work than start something new.

Since we special case continuations to always yield to the browser, this
has the same effect as #31784 without implementing `requestPaint`. At
least assuming nothing else calls `requestPaint`.

<img width="587" alt="Screenshot 2024-12-14 at 5 37 37 PM"
src="https://github.com/user-attachments/assets/8641b172-8842-4191-8bf0-50cbe263a30c"
/>
2024-12-17 17:01:31 -05:00
Jack Pope 34ee3919c3 Clean up enableLazyContextPropagation (#31810)
This flag has shipped everywhere, let's clean it up.
2024-12-17 11:56:00 -05:00
Jack Pope 909ed63e0a Clean up context access profiling experiment (#31806)
We introduced the `unstable_useContextWithBailout` API to run compiler
based experiments. This API was designed to be an experiment proxy for
alternative approaches which would be heavier to implement. The
experiment turned out to be inconclusive. Since most of our performance
critical usage is already optimized, we weren't able to find a clear win
with this approach.

Since we don't have further plans for this API, let's clean it up.
2024-12-16 12:32:07 -05:00
Ricky e06c72fcf4 [flags] Cleanup enableCache (#31778)
This is landed everywhere
2024-12-15 12:34:08 -05:00
Ricky 2d320563f3 [flags] Delete enableDebugTracing (#31780)
This is unused, even in the one builds that uses it, and we don't plan
on landing it in this form.
2024-12-15 12:16:10 -05:00
Ricky 2e25ee373d [flags] Cleanup enableUseMemoCacheHook (#31767)
Based off https://github.com/facebook/react/pull/31766

This has already landed everywhere.
2024-12-14 11:11:04 -05:00
Ricky ef63718a27 Remove enableAsyncActions (#31757)
Based on https://github.com/facebook/react/pull/31756

This is landed everywhere
2024-12-13 13:58:18 -05:00
lauren e3b7ef32be [crud] Only export uRC when flag is enabled (#31617)
It's tricky to do feature detection of uRC currently because it's always
present on the export. Let's conditionally export it instead.
2024-11-22 16:13:42 -05:00
lauren c11c9510fa [crud] Fix deps comparison bug (#31599)
Fixes a bug with the experimental `useResourceEffect` hook where we
would compare the wrong deps when there happened to be another kind of
effect preceding the ResourceEffect. To do this correctly we need to add
a pointer to the ResourceEffect's identity on the update.

I also unified the previously separate push effect impls for resource
effects since they are always pushed together as a unit.
2024-11-20 16:54:41 -05:00
lauren 047d95e85f [crud] Basic implementation (#31523)
This PR introduces a new experimental hook `useResourceEffect`, which is
something that we're doing some very early initial tests on.

This may likely not pan out and will be removed or modified if so.
Please do not rely on it as it will break.
2024-11-18 10:16:37 -05:00
Sebastian Markbåge c13986da78 Fix Overlapping "message" Bug in Performance Track (#31528)
When you schedule a microtask from render or effect and then call
setState (or ping) from there, the "event" is the event that React
scheduled (which will be a postMessage). The event time of this new
render will be before the last render finished.

We usually clamp these but in this scenario the update doesn't happen
while a render is happening. Causing overlapping events.

Before:

<img width="1229" alt="Screenshot 2024-11-12 at 11 01 30 PM"
src="https://github.com/user-attachments/assets/9652cf3b-b358-453c-b295-1239cbb15952">

Therefore when we finalize a render we need to store the end of the last
render so when we a new update comes in later with an event time earlier
than that, we know to clamp it.

There's also a special case here where when we enter the
`RootDidNotComplete` or `RootSuspendedWithDelay` case we neither leave
the root as in progress nor commit it. Those needs to finalize too.
Really this should be modeled as a suspended track that we haven't added
yet. That's the gap between "Blocked" and "message" below.

After:

<img width="1471" alt="Screenshot 2024-11-13 at 12 31 34 AM"
src="https://github.com/user-attachments/assets/b24f994e-9055-4b10-ad29-ad9b36302ffc">

I also fixed an issue where we may log the same event name multiple
times if we're rendering more than once in the same event. In this case
I just leave a blank trace between the last commit and the next update.

I also adding ignoring of the "message" event at all in these cases when
the event is from React's scheduling itself.
2024-11-14 16:35:08 -05:00
RickyandJoe Savona b836de613d Fix continuation bug (#31434)
## Overview

In `scheduleTaskForRootDuringMicrotask` we clear `root.callbackNode` if
the work loop is [suspended waiting on
data](https://github.com/facebook/react/blob/ac3ca097aeecae8fe3ec7f9b286307a923676518/packages/react-reconciler/src/ReactFiberRootScheduler.js#L338).

But we don't null check `root.callbackNode` before returning a
continuation in `performWorkOnRootViaSchedulerTask` where
`scheduleTaskForRootDuringMicrotask` is synchronously called, causing an
infinite loop when the only thing in the queue is something suspended
waiting on data.

This essentially restores the behavior from here:
https://github.com/facebook/react/pull/26328/files#diff-72ff2175ae3569037f0b16802a41b0cda2b2d66bb97f2bda78ed8445ed487b58L1168

Found by investigating the failures for
https://github.com/facebook/react/pull/31417

## TODO
- add a test

---------

Co-authored-by: Joe Savona <joesavona@fb.com>
2024-11-11 17:25:37 -05:00
989af12f72 Make prerendering always non-blocking with fix (#31452)
We've previously failed to land this change due to some internal apps
seeing infinite render loops due to external store state updates during
render. It turns out that since the `renderWasConcurrent` var was moved
into the do block, the sync render triggered from the external store
check was stuck with a `RootSuspended` `exitStatus`. So this is not
unique to sibling prerendering but more generally related to how we
handle update to a sync external store during render.

We've tested this build against local repros which now render without
crashes. We will try to add a unit test to cover the scenario as well.

---------

Co-authored-by: Andrew Clark <git@andrewclark.io>
Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
2024-11-08 12:38:41 -05:00
Jan Kassens e1378902bb [string-refs] cleanup string ref code (#31443) 2024-11-06 14:00:10 -05:00
Jan Kassens 07aa494432 Remove enableRefAsProp feature flag (#30346)
The flag is fully rolled out.
2024-11-04 14:30:58 -05:00
Sebastian Markbåge 0bc3074873 Capture the source and not just the stack on first seen error (#31367)
Otherwise we can't capture the owner stack at the right location when
there's a rethrow.
2024-10-28 13:59:38 -07:00
Jack Pope cae764ce81 Revert "[Re-land] Make prerendering always non-blocking (#31268)" (#31355)
This reverts commit 6c4bbc7832.

It looked like the bug we found on the original land was related to
broken product code. But through landing #31268 we found additional bugs
internally. Since disabling the feature flag does not fix the bugs, we
have to revert again to unblock the sync. We can continue to debug with
our internal build.
2024-10-25 09:17:07 -07:00
Jack PopeandAndrew Clark 6c4bbc7832 [Re-land] Make prerendering always non-blocking (#31268)
Follows https://github.com/facebook/react/pull/31238

___

This is a partial re-land of
https://github.com/facebook/react/pull/31056. We saw breakages surface
after the original land and had to revert. Now that they've been fixed,
let's try this again. This time we'll split up the commits to give us
more control of testing and rollout internally.

Original PR: https://github.com/facebook/react/pull/31056
Original Commit:
https://github.com/facebook/react/pull/31056/commits/4c71025d8d1bd46344ad793e7ed3049d24f7395a
Revert PR: https://github.com/facebook/react/pull/31080

Commit description:

> When a synchronous update suspends, and we prerender the siblings, the
prerendering should be non-blocking so that we can immediately restart
once the data arrives.
>
> This happens automatically when there's a Suspense boundary, because
we immediately commit the boundary and then proceed to a Retry render,
which are always concurrent. When there's not a Suspense boundary, there
is no Retry, so we need to take care to switch from the synchronous work
loop to the concurrent one, to enable time slicing.

Co-authored-by: Andrew Clark <git@andrewclark.io>
2024-10-15 16:47:02 -04:00
Ricky 76aee6f39d Revert "Make prerendering always non-blocking" (#31080)
Reverts facebook/react#31056
2024-09-26 16:48:57 -04:00
Andrew Clark 0f1856c49f Make prerendering always non-blocking (#31056)
When a synchronous update suspends, and we prerender the siblings, the
prerendering should be non-blocking so that we can immediately restart
once the data arrives.

This happens automatically when there's a Suspense boundary, because we
immediately commit the boundary and then proceed to a Retry render,
which are always concurrent. When there's not a Suspense boundary, there
is no Retry, so we need to take care to switch from the synchronous work
loop to the concurrent one, to enable time slicing.
2024-09-25 16:31:44 -04:00
Andrew Clark 3c7667a694 Unify perform{Sync,Concurrent}WorkOnRoot implementation (#31029)
Over time the behavior of these two paths has converged to be
essentially the same. So this merges them back into one function. This
should save some code size and also make it harder for the behavior to
accidentally diverge. (For the same reason, rolling out this change
might expose some areas where we had already accidentally diverged.)
2024-09-25 14:33:11 -04:00
Sebastian Markbåge d4688dfaaf [Fiber] Track Event Time, startTransition Time and setState Time (#31008)
This tracks the current window.event.timeStamp the first time we
setState or call startTransition. For either the blocking track or
transition track. We can use this to show how long we were blocked by
other events or overhead from when the user interacted until we got
called into React.

Then we track the time we start awaiting a Promise returned from
startTransition. We can use this track how long we waited on an Action
to complete before setState was called.

Then finally we track when setState was called so we can track how long
we were blocked by other word before we could actually start rendering.
For a Transition this might be blocked by Blocking React render work.

We only log these once a subsequent render actually happened. If no
render was actually scheduled, then we don't log these. E.g. if an
isomorphic Action doesn't call startTransition there's no render so we
don't log it.

We only log the first event/update/transition even if multiple are
batched into it later. If multiple Actions are entangled they're all
treated as one until an update happens. If no update happens and all
entangled actions finish, we clear the transition so that the next time
a new sequence starts we can log it.

We also clamp these (start the track later) if they were scheduled
within a render/commit. Since we share a single track we don't want to
create overlapping tracks.

The purpose of this is not to show every event/action that happens but
to show a prelude to how long we were blocked before a render started.
So you can follow the first event to commit.

<img width="674" alt="Screenshot 2024-09-20 at 1 59 58 AM"
src="https://github.com/user-attachments/assets/151ba9e8-6b3c-4fa1-9f8d-e3602745eeb7">

I still need to add the rendering/suspended phases to the timeline which
why this screenshot has a gap.

<img width="993" alt="Screenshot 2024-09-20 at 12 50 27 AM"
src="https://github.com/user-attachments/assets/155b6675-b78a-4a22-a32b-212c15051074">

In this case it's a Form Action which started a render into the form
which then suspended on the action. The action then caused a refresh,
which interrupts with its own update that's blocked before rendering.
Suspended roots like this is interesting because we could in theory
start working on a different root in the meantime which makes this
timeline less linear.
2024-09-20 14:27:12 -04:00