68 lines
4.0 KiB
TypeScript
68 lines
4.0 KiB
TypeScript
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,
|
||
},
|
||
},
|
||
})
|
||
|
||
/*
|
||
* Без <StrictMode>: намеренно, из-за экрана конференции (RoomPage).
|
||
*
|
||
* `@livekit/components-react` <LiveKitRoom> (хук useLiveKitRoom) создаёт
|
||
* `Room` в useEffect и вызывает `room.disconnect()` в cleanup-эффекте при
|
||
* размонтировании. Двойной вызов эффектов StrictMode в dev (mount → cleanup
|
||
* → mount) укладывается в ровно тот гоночный сценарий, от которого сама
|
||
* библиотека предупреждает в комментарии к своему (неиспользуемому внутри
|
||
* <LiveKitRoom>) хуку useSequentialRoomConnectDisconnect: «room.connect() в
|
||
* теле эффекта может продолжить выполняться, пока ещё не завершился
|
||
* room.disconnect() из cleanup предыдущего рендера» — т.е. первый Room
|
||
* успевает частично подключиться (в т.ч. опубликовать локальный трек камеры
|
||
* с реальным sid) и почти сразу схлопывается вместе со вторым, из-за чего
|
||
* реальный (переживший гонку) Room получает событие Disconnected и роняет
|
||
* нас в /lobby через onDisconnected, а GridLayout/useVisualStableUpdate
|
||
* параллельно ловит смену идентичности трека (placeholder → реальный трек
|
||
* → снова placeholder) и логирует `Error while running updatePages()`.
|
||
* Внутри `<LiveKitRoom>` эта гонка не сериализуется (в отличие от
|
||
* useSequentialRoomConnectDisconnect), поэтому единственный надёжный фикс на
|
||
* уровне приложения — не выполнять эффекты дважды. В production-сборке
|
||
* StrictMode всё равно не даёт двойного вызова эффектов, так что для
|
||
* собранного бандла разницы нет — риск только в dev-режиме, где мы и теряли
|
||
* подключение к комнате.
|
||
*/
|
||
createRoot(document.getElementById('root')!).render(
|
||
<BrowserRouter>
|
||
<QueryClientProvider client={queryClient}>
|
||
<ToastProvider>
|
||
<AuthProvider>
|
||
<App />
|
||
</AuthProvider>
|
||
</ToastProvider>
|
||
</QueryClientProvider>
|
||
</BrowserRouter>,
|
||
)
|