import { createRoot } from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' // Шрифты дизайн-системы — self-hosted через @fontsource (без внешних CDN), // импортируются ДО ./index.css, чтобы @font-face объявились раньше стилей. // Агрегатные <вес>.css включают все subset'ы, включая кириллицу (интерфейс // русский). Веса — по DESIGN_SYSTEM.md: Unbounded 400/600/700 (display), // Inter 400/500/600/700 (body), JetBrains Mono 500 (mono). import '@fontsource/unbounded/400.css' import '@fontsource/unbounded/600.css' import '@fontsource/unbounded/700.css' import '@fontsource/inter/400.css' import '@fontsource/inter/500.css' import '@fontsource/inter/600.css' import '@fontsource/inter/700.css' import '@fontsource/jetbrains-mono/500.css' import './index.css' import App from './App.tsx' import { AuthProvider } from '@/auth/AuthProvider' import { ToastProvider } from '@/components/ui/ToastProvider' const queryClient = new QueryClient({ defaultOptions: { queries: { // Данные почти всегда актуализируются через собственные refetchInterval — // не рефетчим агрессивно при каждом фокусе окна. refetchOnWindowFocus: false, retry: 1, }, }, }) /* * Без : намеренно, из-за экрана конференции (RoomPage). * * `@livekit/components-react` (хук useLiveKitRoom) создаёт * `Room` в useEffect и вызывает `room.disconnect()` в cleanup-эффекте при * размонтировании. Двойной вызов эффектов StrictMode в dev (mount → cleanup * → mount) укладывается в ровно тот гоночный сценарий, от которого сама * библиотека предупреждает в комментарии к своему (неиспользуемому внутри * ) хуку useSequentialRoomConnectDisconnect: «room.connect() в * теле эффекта может продолжить выполняться, пока ещё не завершился * room.disconnect() из cleanup предыдущего рендера» — т.е. первый Room * успевает частично подключиться (в т.ч. опубликовать локальный трек камеры * с реальным sid) и почти сразу схлопывается вместе со вторым, из-за чего * реальный (переживший гонку) Room получает событие Disconnected и роняет * нас в /lobby через onDisconnected, а GridLayout/useVisualStableUpdate * параллельно ловит смену идентичности трека (placeholder → реальный трек * → снова placeholder) и логирует `Error while running updatePages()`. * Внутри `` эта гонка не сериализуется (в отличие от * useSequentialRoomConnectDisconnect), поэтому единственный надёжный фикс на * уровне приложения — не выполнять эффекты дважды. В production-сборке * StrictMode всё равно не даёт двойного вызова эффектов, так что для * собранного бандла разницы нет — риск только в dev-режиме, где мы и теряли * подключение к комнате. */ createRoot(document.getElementById('root')!).render( , )