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 legal page never downloads the // authenticated app bundle, and the app never carries it either. The legal pages live outside the // App tree (no /api/me) so Google's consent screen and reviewers can fetch them directly. Reached by // full-page navigation (plain ), so a simple pathname switch is enough — no router needed. // A public /watch link is not here at all: it has its own HTML entry (watch.html → watchMain.tsx), // so the friend it was sent to never loads this file's providers, i18n or CSS imports. const App = lazyWithPreload(() => import("./App")); const PrivacyPolicy = lazy(() => import("./components/legal/PrivacyPolicy")); const Terms = lazy(() => import("./components/legal/Terms")); // The design lab: the living style guide, rendered with the app's real CSS so a design decision // is taken on what will ship. DEV only — Vite folds the condition to `false` in a production build, // the import becomes dead code and the chunk is never emitted (devOnlyGuard fails the build if it is). const DesignLab = import.meta.env.DEV ? lazy(() => import("./design-lab/DesignLab")) : null; const queryClient = createQueryClient(); // Baseline theme so the token CSS vars (--bg/--surface/--accent/…) resolve on first paint — including // the standalone public pages (/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 isLab = DesignLab !== null && path === "/design-lab"; const isPublicPage = path === "/privacy" || path === "/terms" || isLab; // 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 = isLab && DesignLab ? ( ) : path === "/privacy" ? ( ) : path === "/terms" ? ( ) : ( {/* One tree for the whole app: every anchored popover registers as a node so Escape and outside-press act only on the topmost popover. The Modal and the players are not nodes: they order against the popovers through lib/layerStack. */} {/* 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, or a chunk gone twice over after a release (lib/staleBuild), left a blank white page with no way on. */} }>{root} , );