Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Optimize gesture by allowing the original work in progress tree to be a suspended commit by sebmarkbage · Pull Request #35510 · react/react · GitHub
Skip to content

Optimize gesture by allowing the original work in progress tree to be a suspended commit - #35510

Merged
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture
Jan 16, 2026
Merged

Optimize gesture by allowing the original work in progress tree to be a suspended commit#35510
sebmarkbage merged 4 commits into
react:mainfrom
sebmarkbage:commitoriginalgesture

Conversation

@sebmarkbage

Copy link
Copy Markdown
Contributor

Stacked on #35487.

This is slightly different because the first suspended commit is on blockers that prevent us from committing which still needs to be resolved first.

If a gesture lane has to be rerendered while the gesture is happening then it reenters this state with a new tree. (Currently this doesn't happen for a ping I think which is not really how it usually works but better in this case.)

@react-sizebot

react-sizebot commented Jan 14, 2026

Copy link
Copy Markdown

Comparing: 35a81ce...3a51d91

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js=608.20 kB608.20 kB=107.66 kB107.66 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.84 kB6.84 kB=1.88 kB1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.14%669.74 kB670.66 kB+0.07%118.00 kB118.08 kB
facebook-www/ReactDOM-prod.classic.js=693.57 kB693.57 kB=122.05 kB122.05 kB
facebook-www/ReactDOM-prod.modern.js=683.95 kB683.95 kB=120.44 kB120.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-experimental/react-art/cjs/react-art.production.js+0.46%356.88 kB358.53 kB+0.38%60.22 kB60.45 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js+0.29%566.12 kB567.78 kB+0.29%88.17 kB88.42 kB
oss-experimental/react-art/cjs/react-art.development.js+0.26%731.96 kB733.87 kB+0.27%115.16 kB115.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js+0.23%860.38 kB862.37 kB+0.22%134.01 kB134.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js+0.21%748.39 kB749.95 kB+0.22%129.36 kB129.64 kB

Generated by 🚫 dangerJS against 3a51d91

startTransition(action);
}
// We cancel the gesture before invoking side-effects to allow the gesture lane to fully commit
// before scheduling new updates.

@sebmarkbagesebmarkbageJan 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might actually be wrong because it wouldn't know what to entangle with until later. Nevermind this works because we entangle all transitions in the same event.

@sebmarkbage

Copy link
Copy Markdown
ContributorAuthor

I also pushed some commits related to performance track logging of these new types of phases and gaps.

@sebmarkbagesebmarkbage changed the title Optimize gesture by allowing the original work in progress tree to be a suspended comimtOptimize gesture by allowing the original work in progress tree to be a suspended commitJan 15, 2026
sebmarkbage added a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
This is equivalent to the "Commit" phase in the regular phases but it's
creating a "ghost" DOM tree that's a partial clone of the old one that's
immediately deleted.
This ensures that we log the end state of the Animating track which
otherwise is ignored inside a commit phase.
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
…35486)
Stacked on #35485.
Before this PR, the `startGestureTransition` API would itself never
commit its state. After the gesture releases it stops the animation in
the next commit which just leaves the DOM tree in the original state. If
there's an actual state change from the Action then that's committed as
the new DOM tree. To avoid animating from the original state to the new
state again, this is DOM without an animation. However, this means that
you can't have the actual action committing be in a slightly different
state and animate between the final gesture state and into the new
action.
Instead, we now actually keep the render tree around and commit it in
the end. Basically we assume that if the Timeline was closer to the end
then visually you're already there and we can commit into that state.
Most of the time this will be at the actual end state when you release
but if you have something else cancelling the gesture (e.g.
`touchcancel`) it can still commit this state even though your gesture
recognizer might not consider this an Action. I think this is ok and
keeps it simple.
When the gesture lane commits, it'll leave a Transition behind as work
from the revert lanes on the Optimistic updates. This means that if you
don't do anything in the Action this will cause another commit right
after which reverts. This revert can animate the snap back.
There's a few fixes needed in follow up PRs:
- Fixed in #35487. ~To support unentangled Transitions we need to
explicitly entangle the revert lane with the Action to avoid committing
a revert followed by a forward instead of committing the forward
entangled with the revert. This just works now since everything is
entangled but won't work with #35392.~
- Fixed in #35510. ~This currently rerenders the gesture lane once
before committing if it was already completed but blocked. We should be
able to commit the already completed tree as is.~
DiffTrain build for [4028aaa](4028aaa)
@sebmarkbage
sebmarkbage merged commit 4cf9063 into react:mainJan 16, 2026
233 of 234 checks passed
github-actionsBot pushed a commit that referenced this pull request Jan 16, 2026
… a suspended commit (#35510)
Stacked on #35487.
This is slightly different because the first suspended commit is on
blockers that prevent us from committing which still needs to be
resolved first.
If a gesture lane has to be rerendered while the gesture is happening
then it reenters this state with a new tree. (Currently this doesn't
happen for a ping I think which is not really how it usually works but
better in this case.)
DiffTrain build for [4cf9063](4cf9063)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@sebmarkbage@react-sizebot@acdlite