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ón | Sobrevive a nav WS | Segundo monitor | Coste implementación | Compatibilidad |
|---|---|---|---|---|
Popup window (window.open(..., 'popup=yes,...')) | ✅ ventana SO independiente | ✅ arrastrable | Baja (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 propio | Media | Solo Chromium (no Firefox/Safari) |
Astro ClientRouter + transition:persist | ✅ dentro del WS | ❌ no | Alta (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.openrefoca 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+pagehideenvíapeer_leftvianavigator.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.opensi 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 desdeuseEffect: puede ser bloqueado. Si falla,openMeetingPopupmuestraalertcon 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 fijateam-meeting).
Implementación
Archivos nuevos
| Archivo | Propósito |
|---|---|
src/pages/reunion.astro | Ruta standalone, AppLayout sin chrome, html/body 100vh + overflow:hidden |
src/components/widgets/MeetingRoomShell.tsx | Lee query params (author, target, silent), monta MeetingRoom, onClose → window.close() |
Archivos modificados
| Archivo | Cambio |
|---|---|
src/components/widgets/MeetingRoom.tsx | Quitado 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.tsx | Helper 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=openresulta 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]]