fix(twenty-front): prevent stale transition page from blocking scroll (#21551)

## Problem

Switching from the app to Settings sometimes makes every settings page
unscrollable until a full page reload.

## Cause

`MainAppLayoutOutlet` cross-fades between the app and settings with
`AnimatePresence`, rendering both pages into the same grid cell
(`grid-area: 1 / 1`). When an exit animation doesn't clean up, the
outgoing page stays mounted on top of the active one. With the default
`pointer-events: auto`, that (now invisible) stale node intercepts
wheel/scroll events before they reach the page underneath — so the page
won't scroll even though its own scroll container is fine. The node
lives in the always-mounted layout, which is why it survives in-app
navigation and only a reload clears it.

A broken vs. working DOM snapshot differs only in the number of children
in the transition grid cell (2 vs 1) and which element sits under the
cursor; the scroll container, its CSS, and the whole flex/height chain
are identical.

## Fix

Set `pointer-events: none` on exiting pages so a stale exit node can't
capture input from the active page.

Tested by reproducing the stale-node-on-top state and confirming
`pointer-events: none` lets wheel/scroll reach the live page across the
content area, while the entering page stays interactive.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21551?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Félix Malfait
2026-06-14 20:46:22 +02:00
committed by GitHub
parent 0baa333809
commit adfef5c830
@@ -61,7 +61,10 @@ const MainAppLayoutOutlet = () => {
key={routeSection}
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
// The outgoing page shares this grid cell with the incoming one, so it
// must not capture pointer/scroll events — including when AnimatePresence
// leaves a stale exit node mounted on top of the active page.
exit={{ opacity: 0, y: -4, pointerEvents: 'none' }}
transition={{
duration: APP_TO_SETTINGS_TRANSITION_DURATION_IN_SECONDS,
ease: 'easeInOut',