Compare commits

...

2 Commits

Author SHA1 Message Date
1aa1486d88 fix(sprint-5/etapa-4h): safety net en Sheet — body queda con pointer-events:none stuck tras cerrar
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Bug de Radix/react-remove-scroll: el cleanup que restaura pointer-events del body
al cerrar un Dialog/Sheet puede no ejecutarse (race con re-renders externos durante
la animación de salida). Resultado: toda la página queda no interactiva hasta
refrescar manualmente — confirmado en producción tras guardar perfil pese al
defer de 350ms ya aplicado en ProfileSheet (mitigaba una causa, no era estructural).

Fix a nivel del primitivo Sheet (cubre todos los usos, no solo ProfileSheet):
al cerrar, espera 400ms (> 300ms de la animación) y si no queda ningún
[role="dialog"][data-state="open"] en el DOM pero el body sigue con
pointer-events:none, lo limpia explícitamente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 09:53:14 -03:00
aaf1f79519 fix(sprint-5/etapa-4g): defer updateUser 350ms — espera fin de animacion Radix antes de re-render AuthContext 2026-06-23 10:17:47 +00:00
2 changed files with 31 additions and 7 deletions

View File

@@ -48,15 +48,16 @@ export function ProfileSheet({ open, onOpenChange }: ProfileSheetProps) {
const savedName = name.trim()
const savedCompany = company.trim() || undefined
// Cerramos el Sheet PRIMERO para que Radix inicie la animación de cierre en un
// render limpio. updateUser() se difiere al siguiente macrotask para evitar que
// el re-render de AuthContext (ancestor) interfiera con la animación: si ambas
// actualizaciones ocurren en el mismo tick, React las batchea pero Radix puede
// recibir un render intermedio con open=true que deja el overlay en el DOM.
// Cerramos el Sheet primero. updateUser() se difiere 350ms — más que la animación
// de cierre de Radix (duration-300 = 300ms) — para que el cleanup de
// @radix-ui/react-remove-scroll restaure pointer-events:none del body ANTES
// de que el re-render de AuthContext vuelva a procesar el Sheet. Si updateUser
// corre durante la animación, Radix no ejecuta el cleanup y body queda con
// pointer-events:none permanente, bloqueando toda interacción fuera del canvas BPMN.
onOpenChange(false)
setTimeout(() => {
updateUser({ name: savedName, company: savedCompany })
}, 0)
}, 350)
// Persistir en Supabase en background — fire & forget.
// Usamos raw fetch (no el cliente Supabase) para evitar contención con processLock.

View File

@@ -4,7 +4,30 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
const Sheet = SheetPrimitive.Root
// Bug conocido de Radix/react-remove-scroll: si un re-render externo coincide con la
// animación de cierre, el cleanup que restaura `pointer-events` del body no se ejecuta
// — toda la página queda no interactiva hasta refrescar manualmente. El defer de 350ms
// en algunos callers (ej. ProfileSheet) mitiga UNA causa, pero no es estructural: este
// fallback corrige el estado real del DOM sin asumir cuál fue la causa de la falla.
// 400ms > duración de la animación de salida (300ms, ver sheetVariants).
function Sheet({ onOpenChange, ...props }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Root>) {
return (
<SheetPrimitive.Root
onOpenChange={(open) => {
onOpenChange?.(open)
if (!open) {
window.setTimeout(() => {
const stillHasOpenDialog = document.querySelector('[role="dialog"][data-state="open"]')
if (!stillHasOpenDialog && document.body.style.pointerEvents === 'none') {
document.body.style.pointerEvents = ''
}
}, 400)
}
}}
{...props}
/>
)
}
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal