import React, { lazy, Suspense } from "react"; import { createRoot } from "react-dom/client"; import { QueryClientProvider } from "@tanstack/react-query"; import { createQueryClient } from "./lib/queryClient"; import { FloatingTree } from "@floating-ui/react"; import ErrorBoundary from "./components/ErrorBoundary"; import { ConfirmProvider } from "./components/ConfirmProvider"; import BootGate, { BootScreen } from "./components/BootGate"; import { NavigationProvider } from "./components/NavigationProvider"; import { FeedFiltersProvider } from "./components/FeedFiltersProvider"; import { SavedViewsProvider } from "./components/SavedViewsProvider"; import { PlaylistsProvider } from "./components/PlaylistsProvider"; import { PlexProvider } from "./components/PlexProvider"; import { PlexViewBridge } from "./components/PlexViewBridge"; import { ChannelsProvider } from "./components/ChannelsProvider"; import { FeedViewProvider } from "./components/FeedViewProvider"; import { PrefsProvider } from "./components/PrefsProvider"; import { WizardProvider } from "./components/WizardProvider"; import "./i18n"; import "./index.css"; import "flag-icons/css/flag-icons.min.css"; import { lazyWithPreload } from "./lib/lazyWithPreload"; import { installClientLog } from "./lib/logHooks"; import { installStaleBuildReload } from "./lib/staleBuild"; import { appCopyOf } from "./i18n/appCopy"; // First, before any render: a crash or a navigation during boot belongs in the log too. installClientLog(appCopyOf); // A tab from before the last release reloads onto the new build instead of crashing on a lazy chunk. installStaleBuildReload(); // Split by top-level route so each entry point is its own chunk: a public /watch share link or a // legal page never downloads the authenticated app bundle, and the app never carries them either. // The legal pages live outside the App tree (no /api/me) so Google's consent screen and reviewers // can fetch them directly; the watch page is a login-free public player. Reached by full-page // navigation (plain ), so a simple pathname switch is enough — no router needed. const App = lazyWithPreload(() => import("./App")); const PrivacyPolicy = lazy(() => import("./components/legal/PrivacyPolicy")); const Terms = lazy(() => import("./components/legal/Terms")); const WatchPage = lazy(() => import("./components/WatchPage")); const queryClient = createQueryClient(); // Baseline theme so the token CSS vars (--bg/--surface/--accent/…) resolve on first paint — including // the standalone public pages (/watch, /privacy, /terms) that render outside and never call // applyTheme. The app overrides this with the user's saved theme once it mounts. const de = document.documentElement.dataset; de.theme ||= "dark"; de.scheme ||= "midnight"; const path = window.location.pathname; const isPublicPage = path === "/privacy" || path === "/terms" || path.startsWith("/watch/"); // Fetch the app chunk NOW, alongside BootGate's identity probe, instead of only once the gate opens: // the two waits then overlap rather than queue, and the gate usually opens straight onto the app. A // failed fetch is left to the render's own load, which retries it and reports to the ErrorBoundary. if (!isPublicPage) App.preload().catch(() => {}); const root = path === "/privacy" ? ( ) : path === "/terms" ? ( ) : path.startsWith("/watch/") ? ( // FloatingTree so the public player's own popovers dispatch Escape/outside-press topmost-only. ) : ( {/* One tree for the whole app: every anchored popover registers as a node so Escape and outside-press act only on the topmost layer (S3 folds the Modal + players in). */} {/* Nothing below renders until the tab knows which account it is: every provider seeds per-account state in its initializer (see BootGate). */} {/* SavedViewsProvider sits below BOTH filter providers so it can switch its source (feed ⟷ plex) by the active page and read the right live filters. */} {/* The same quiet screen as the gate's, so a chunk that is still loading when the gate opens continues it instead of blanking. */} }> ); createRoot(document.getElementById("root")!).render( {/* Around EVERY entry, the public ones too: a crash on a shared /watch link, or its chunk gone twice over after a release (lib/staleBuild), left a blank white page with no way on. */} }>{root} , );