diff --git a/bun.lock b/bun.lock
index a5f208e..f655f59 100644
--- a/bun.lock
+++ b/bun.lock
@@ -31,8 +31,8 @@
"facehash": "^0.0.7",
"framer-motion": "^12.23.12",
"pocketbase": "^0.26.2",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0",
"react-imask": "^7.6.1",
"redaxios": "^0.5.1",
"sonner": "^2.0.7",
@@ -49,8 +49,8 @@
"@lingui/cli": "^6.5.0",
"@lingui/vite-plugin": "^6.5.0",
"@types/node": "^22.5.4",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
"@types/web-push": "^3.6.4",
"@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20",
@@ -655,9 +655,9 @@
"@types/node": ["@types/node@22.19.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw=="],
- "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
+ "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
- "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
+ "@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
@@ -1329,9 +1329,9 @@
"raf-schd": ["raf-schd@4.0.3", "", {}, "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="],
- "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
+ "react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
- "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
+ "react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
"react-imask": ["react-imask@7.6.1", "", { "dependencies": { "imask": "^7.6.1", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=0.14.0" } }, "sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA=="],
@@ -1397,7 +1397,7 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
- "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
+ "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
"scmp": ["scmp@2.1.0", "", {}, "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="],
diff --git a/package.json b/package.json
index c7cb483..aa9e2f1 100644
--- a/package.json
+++ b/package.json
@@ -43,8 +43,8 @@
"facehash": "^0.0.7",
"framer-motion": "^12.23.12",
"pocketbase": "^0.26.2",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0",
"react-imask": "^7.6.1",
"redaxios": "^0.5.1",
"sonner": "^2.0.7",
@@ -61,8 +61,8 @@
"@lingui/cli": "^6.5.0",
"@lingui/vite-plugin": "^6.5.0",
"@types/node": "^22.5.4",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
"@types/web-push": "^3.6.4",
"@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20",
diff --git a/scripts/generate-sw.mjs b/scripts/generate-sw.mjs
index a0dad01..943a142 100644
--- a/scripts/generate-sw.mjs
+++ b/scripts/generate-sw.mjs
@@ -13,7 +13,6 @@ const { count, size, warnings } = await generateSW({
'icon-192x192.png',
'icon-512x512.png',
'site.webmanifest',
- 'styles.css',
'push-handlers.js',
],
// Hand-written Web Push handlers (public/push-handlers.js) are pulled into
diff --git a/src/app/router.tsx b/src/app/router.tsx
index 655ea71..5eaced4 100644
--- a/src/app/router.tsx
+++ b/src/app/router.tsx
@@ -3,6 +3,7 @@ import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary";
+import RoutePending from "../components/route-pending";
import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config";
export function getRouter() {
@@ -30,6 +31,9 @@ export function getRouter() {
},
defaultPreload: "intent",
defaultPreloadStaleTime: 60_000,
+ defaultPendingMs: 100,
+ defaultPendingMinMs: 0,
+ defaultPendingComponent: RoutePending,
defaultErrorComponent: DefaultCatchBoundary,
});
diff --git a/src/app/routes/__root.tsx b/src/app/routes/__root.tsx
index a81eb22..3ebd006 100644
--- a/src/app/routes/__root.tsx
+++ b/src/app/routes/__root.tsx
@@ -31,6 +31,7 @@ import { getRootMeta } from "@/lib/i18n/meta";
import FullScreenLoader from "@/components/full-screen-loader";
import { CHROME_COLORS } from "@/lib/mantine/theme-colors";
import mantineCssUrl from '@mantine/core/styles.css?url'
+import appStylesUrl from '../styles.css?url'
import mantineDatesCssUrl from '@mantine/dates/styles.css?url'
import mantineCarouselCssUrl from '@mantine/carousel/styles.css?url'
import mantineTiptapCssUrl from '@mantine/tiptap/styles.css?url'
@@ -101,6 +102,7 @@ export const Route = createRootRouteWithContext<{
},
{ rel: "manifest", href: "/site.webmanifest" },
{ rel: "icon", href: "/favicon.ico" },
+ { rel: 'stylesheet', href: appStylesUrl },
{ rel: 'stylesheet', href: mantineCssUrl },
{ rel: 'stylesheet', href: mantineCarouselCssUrl },
{ rel: 'stylesheet', href: mantineDatesCssUrl },
@@ -183,7 +185,19 @@ function RootComponent() {
ensureSuperTokensFrontend();
initTelemetry(router);
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
- navigator.serviceWorker.register('/sw.js').catch(() => track('sw_register_failed'));
+ // first install claims the page too; only reload on real updates
+ const hadController = !!navigator.serviceWorker.controller;
+ let reloading = false;
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ if (!hadController || reloading) return;
+ reloading = true;
+ window.location.reload();
+ });
+ navigator.serviceWorker.register('/sw.js').then((registration) => {
+ document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible') registration.update().catch(() => {});
+ });
+ }).catch(() => track('sw_register_failed'));
}
}, [router]);
@@ -231,7 +245,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
'try{var d=document.documentElement,t=localStorage.getItem("flxn-theme");if(t&&t!=="default"){d.setAttribute("data-theme",t);}var s=localStorage.getItem("flxn-theme-scheme");if(s){d.setAttribute("data-mantine-color-scheme",s);}var f=JSON.parse(localStorage.getItem("flxn-theme-fonts")||"[]");for(var i=0;i