import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode, } from "react"; import { useLocation } from "react-router-dom"; import { SplashScreen } from "../components/pwa/SplashScreen"; const MIN_SPLASH_MS = 400; const MAX_SPLASH_MS = 8000; const SKIP_SPLASH_KEY = "sproutclaw-warm-boot"; type BootstrapRoute = "chat" | "settings"; interface BootstrapContextValue { markRouteReady: (route: BootstrapRoute) => void; } const BootstrapContext = createContext(null); export function BootstrapProvider({ children }: { children: ReactNode }) { const location = useLocation(); const route: BootstrapRoute = location.pathname.startsWith("/settings") ? "settings" : "chat"; const [readyRoutes, setReadyRoutes] = useState>({ chat: false, settings: false, }); const [splashVisible, setSplashVisible] = useState(true); const [splashExiting, setSplashExiting] = useState(false); const mountTime = useRef(Date.now()); const markRouteReady = useCallback((readyRoute: BootstrapRoute) => { setReadyRoutes((prev) => (prev[readyRoute] ? prev : { ...prev, [readyRoute]: true })); }, []); useEffect(() => { setReadyRoutes((prev) => ({ ...prev, [route]: false })); setSplashVisible(true); setSplashExiting(false); mountTime.current = Date.now(); }, [route]); useEffect(() => { if (readyRoutes[route]) return; const timer = window.setTimeout(() => { setReadyRoutes((prev) => (prev[route] ? prev : { ...prev, [route]: true })); }, MAX_SPLASH_MS); return () => window.clearTimeout(timer); }, [route, readyRoutes]); useEffect(() => { if (!readyRoutes[route]) return; let warmBoot = false; try { warmBoot = sessionStorage.getItem(SKIP_SPLASH_KEY) === "1"; sessionStorage.setItem(SKIP_SPLASH_KEY, "1"); } catch { /* ignore */ } if (warmBoot) { setSplashVisible(false); setSplashExiting(false); return; } const elapsed = Date.now() - mountTime.current; const remaining = Math.max(0, MIN_SPLASH_MS - elapsed); const timer = window.setTimeout(() => { setSplashExiting(true); window.setTimeout(() => setSplashVisible(false), 420); }, remaining); return () => window.clearTimeout(timer); }, [readyRoutes, route]); const value = useMemo(() => ({ markRouteReady }), [markRouteReady]); return ( {children} {splashVisible ? : null} ); } export function useBootstrap(): BootstrapContextValue { const ctx = useContext(BootstrapContext); if (!ctx) throw new Error("useBootstrap must be used within BootstrapProvider"); return ctx; }