);
}
function InitGate({ children }: { children: React.ReactNode }) {
const initialized = useConfigStore((s) => s.initialized);
const loading = useConfigStore((s) => s.loading);
const error = useConfigStore((s) => s.error);
const init = useConfigStore((s) => s.init);
useThemeSync(initialized);
useEffect(() => {
init();
}, [init]);
if (!initialized || loading) {
return ;
}
if (error) {
return ;
}
return <>{children}>;
}
function Root() {
return (
);
}
// ─── UI font size: apply saved value before first paint ─
applySavedFontSize();
function applySavedFontSize() {
const saved = Number(localStorage.getItem("pi-font-size"));
if (saved >= 12 && saved <= 24) {
document.documentElement.style.fontSize = `${saved}px`;
}
}
// ─── UI zoom: apply saved percentage before first paint ─
applySavedZoom();
function applySavedZoom() {
const saved = Number(localStorage.getItem("pi-ui-zoom"));
document.documentElement.style.zoom = "";
if (saved >= 50 && saved <= 200) {
document.documentElement.style.setProperty("--ui-zoom", String(saved / 100));
}
}
// ─── Service worker lifecycle ───────────────────────────
// A production SW can remain registered when the same localhost origin is
// later opened with Vite dev. Actively remove it in development; otherwise a
// normal Cmd+R may be served an old HTML shell while Cmd+Shift+R bypasses it.
const APP_CACHE_PREFIX = "pi-web-switch-";
const DEV_SW_CLEANUP_KEY = "pi-web-switch-dev-sw-cleaned";
async function clearAppCaches() {
if (!("caches" in window)) return;
const keys = await caches.keys();
await Promise.all(
keys.filter((key) => key.startsWith(APP_CACHE_PREFIX)).map((key) => caches.delete(key))
);
}
function getAppBaseUrl() {
const moduleScript = Array.from(document.scripts).find(
(script) => script.type === "module" && script.src
);
return new URL("../", moduleScript?.src ?? document.baseURI);
}
async function configureServiceWorker() {
if (!("serviceWorker" in navigator)) return;
const appBaseUrl = getAppBaseUrl();
if (import.meta.env.DEV) {
try {
const registrations = await navigator.serviceWorker.getRegistrations();
const appRegistrations = registrations.filter(
(registration) => registration.scope === appBaseUrl.href
);
const hadController = Boolean(navigator.serviceWorker.controller);
const removed = await Promise.all(
appRegistrations.map((registration) => registration.unregister())
);
await clearAppCaches();
// If this document was already controlled by the old production worker,
// reload once after unregistering it so the first API/static requests are
// also guaranteed to bypass the old worker. sessionStorage prevents a
// reload loop if the browser reports the controller for one extra tick.
if (
hadController &&
removed.some(Boolean) &&
sessionStorage.getItem(DEV_SW_CLEANUP_KEY) !== "1"
) {
sessionStorage.setItem(DEV_SW_CLEANUP_KEY, "1");
window.location.reload();
return;
}
sessionStorage.removeItem(DEV_SW_CLEANUP_KEY);
} catch (err) {
// Cache cleanup must never prevent React from mounting.
console.warn("SW cleanup failed:", err);
}
return;
}
try {
// Resolve from the built JS asset directory so this also works when the
// app is hosted under a subdirectory instead of the domain root.
const swUrl = new URL("sw.js", appBaseUrl);
const registration = await navigator.serviceWorker.register(swUrl, {
updateViaCache: "none",
});
// Check on every application start instead of waiting for the browser's
// periodic service-worker update window.
await registration.update();
} catch (err) {
console.warn("SW registration failed:", err);
}
}
async function bootstrap() {
if (import.meta.env.DEV) {
await configureServiceWorker();
}
createRoot(document.getElementById("root")!).render(
);
if (import.meta.env.PROD) {
void configureServiceWorker();
}
}
void bootstrap();