CreaRack-SL

ADR: MeetingRoom pasa de modal flotante a popup window (2026-05-22)

ADR 2026-05-22: MeetingRoom pasa de modal flotante embebido a popup window

Estado

Aceptada — implementada en s80 (22-05-2026).


Contexto

El componente MeetingRoom (introducido en s79 como reemplazo de Jitsi — ver [[decision—20260522—webrtc-reemplaza-jitsi]]) vivía como modal flotante dentro del workspace: createPortal(..., document.body) + position: fixed, draggable, redimensionable, con modo colapsado. Visualmente flotante, pero lógicamente hijo del componente TeamChat que se monta en el home /.

Problema reportado por Edu (22-05-2026)

“El modal de videollamada se pierde cuando cambias de página en el WS. Debería ser un proceso flotante independientemente de la página del navegador, así podremos abrir y trabajar en el WS sin que se pierda el modal de videollamada, incluso esto permitiría desplazarlo al segundo monitor seguramente.”

Causa raíz: el workspace es un sitio Astro output: 'static' sin ClientRouter/View Transitions. Cada navegación entre páginas (home → wiki → /tools → home) hace un full page reload del navegador. El island React que aloja TeamChat se desmonta, y con él el MeetingRoom: tracks de cámara parados, RTCPeerConnection cerradas, mensaje peer_left enviado al signaling. La sesión muere y los otros peers (Dani, Txell) ven al usuario desconectarse aunque sólo estuviera cambiando de pestaña dentro del propio WS.

Opciones evaluadas

OpciónSobrevive a nav WSSegundo monitorCoste implementaciónCompatibilidad
Popup window (window.open(..., 'popup=yes,...'))✅ ventana SO independiente✅ arrastrableBaja (nueva ruta /reunion, refactor componente)Universal (con popup blocker opt-in)
Document Picture-in-Picture API⚠️ muere si pestaña padre navega⚠️ solo en stacking propioMediaSolo Chromium (no Firefox/Safari)
Astro ClientRouter + transition:persist✅ dentro del WS❌ noAlta (refactor layout invasivo)Universal pero F5/URL externa lo mata igualmente

Decisión

Popup window dedicado. Se crea la ruta /reunion (página Astro standalone con AppLayout minimal, sin Sidebar/BottomNav del WS) y TeamChat reemplaza el render inline del modal por window.open('/reunion?author=...&target=...', 'crearack-meeting', 'popup=yes,width=1100,height=720,left=...,top=...').

El popup vive como una ventana del sistema operativo independiente del WS:

  • Sobrevive a navegación del WS — el usuario puede ir a la wiki, abrir /tools/notas, refrescar el home, etc. La reunión sigue viva.
  • Arrastrable al segundo monitor — funcionalidad nativa del SO sin código.
  • Singleton via name='crearack-meeting': si ya hay popup vivo, window.open refoca esa ventana en vez de duplicar.
  • Cerrar el popup = colgar — limpio para el usuario.

Consecuencias

Positivas

  • Resuelve el bug reportado por Edu — la reunión ya no se pierde al cambiar de página.
  • Segundo monitor gratis — sin código extra, regalo del SO.
  • Refactor simplificador: se eliminan ~150 LOC de drag/resize/clampPos/collapsed/createPortal del componente. La ventana del SO ya hace todo eso.
  • Cleanup más robusto: nuevo listener beforeunload + pagehide envía peer_left via navigator.sendBeacon, garantizando notificación al otros peers cuando se cierra el popup desde la X del SO (no solo desde el botón Colgar interno).
  • Net: −65 LOC (refactor simplificador global del PR).

Negativas / riesgos

  • Popup blocker: navegadores estrictos pueden bloquear window.open si no es respuesta directa a un click. Mitigaciones:
    • Botón Video del TeamChat es click directo → siempre permitido.
    • Botón «🎥 Unirme» en mensajes del chat es click directo → siempre permitido.
    • Handler de URL ?meeting=open (caso «click en aviso desde otra pestaña») dispara desde useEffect: puede ser bloqueado. Si falla, openMeetingPopup muestra alert con instrucciones; el botón «Unirme» del chat queda como fallback fiable.
  • Cambio de feel — la reunión deja de estar embebida en el WS, ahora es ventana separada. Aceptado por Edu en la decisión inicial.
  • Solo deja un singleton — name='crearack-meeting' impide múltiples reuniones simultáneas. No es un caso de uso real (la sala es fija team-meeting).

Implementación

Archivos nuevos

ArchivoPropósito
src/pages/reunion.astroRuta standalone, AppLayout sin chrome, html/body 100vh + overflow:hidden
src/components/widgets/MeetingRoomShell.tsxLee query params (author, target, silent), monta MeetingRoom, onClose → window.close()

Archivos modificados

ArchivoCambio
src/components/widgets/MeetingRoom.tsxQuitado createPortal, drag/resize, collapsed, clampPos, refs de posición/tamaño. Añadido listener beforeunload/pagehide con sendBeacon. Ocupa 100% del viewport del popup vía position: fixed; inset: 0.
src/components/widgets/TeamChat.tsxHelper openMeetingPopup(author, target, silent) usa window.open(...) con name 'crearack-meeting'. Eliminados estados meetingOpen/Target/Silent y el render inline <MeetingRoom>. Handler de URL ?meeting=open ahora abre popup en vez de set state.

Trabajo futuro pendiente

  • Monitorizar reportes de popup blocker en uso real (Edu/Dani/Txell).
  • Si el handler ?meeting=open resulta poco fiable por popup blocker en producción, considerar redirigir directamente la pestaña actual a /reunion?silent=1 (perdiendo el WS pero ganando la reunión).

Véase también

  • [[feature—workspace—webrtc-peer-to-peer]]
  • [[entity—meeting—component—meeting-room]]
  • [[entity—meeting—component—meeting-room-shell]]
  • [[decision—20260522—webrtc-reemplaza-jitsi]]