From 701f679ee0263134232f66a4798aff2a30603c60 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Fri, 22 May 2026 15:08:18 +0200
Subject: [PATCH 01/22] feat: lru_cache and nextjs update

---
 .env.docker.example                           |    1 +
 .env.example                                  |    1 +
 .env.test.example                             |    1 +
 eslint.config.mjs                             |   35 +-
 next.config.ts                                |   29 +-
 package.json                                  |   28 +-
 pnpm-lock.yaml                                | 1276 ++++++++++-------
 .../components/MonitoringOverview.tsx         |   73 -
 .../components/SEOMetricsDashboard.tsx        |  247 ----
 src/app/admin/monitoring/page.tsx             |   15 +-
 src/app/api/cache/metrics/route.ts            |   46 -
 src/app/api/cache/warm/route.ts               |   65 -
 src/app/api/catalog/compatibility/route.ts    |   13 +-
 src/app/api/health/route.ts                   |    9 +-
 src/app/api/proxy-image/route.ts              |    4 -
 src/app/api/trpc/[trpc]/route.ts              |    3 -
 src/app/api/upload/games/route.ts             |    8 -
 src/app/api/upload/profile/route.ts           |    7 -
 src/app/games/new/NewGamePage.tsx             |    8 -
 src/app/layout.tsx                            |   66 +-
 src/app/sitemap.ts                            |   16 -
 src/components/admin/CacheMetrics.tsx         |  152 --
 src/lib/cache/seo-cache.ts                    |  234 ---
 src/lib/captcha/config.ts                     |   20 +-
 src/lib/captcha/verify.test.ts                |   12 +
 src/lib/monitoring/seo-metrics.ts             |  189 ---
 src/{middleware.ts => proxy.ts}               |   78 +-
 src/schemas/cache.ts                          |    9 -
 src/server/api/root.ts                        |    2 -
 src/server/api/routers/cache.ts               |  218 ---
 src/server/api/routers/listings/admin.test.ts |    1 +
 src/server/api/routers/listings/admin.ts      |   14 +-
 src/server/api/routers/mobile/pcListings.ts   |   39 +-
 src/server/api/routers/pcListings.test.ts     |   12 +-
 src/server/api/routers/pcListings.ts          |   74 +-
 src/server/cache/invalidation.test.ts         |   49 +
 src/server/cache/invalidation.ts              |   80 +-
 src/server/cache/warming.ts                   |  208 ---
 src/server/db/seo-queries.test.ts             |  122 ++
 src/server/db/seo-queries.ts                  |  357 ++---
 src/server/notifications/analyticsService.ts  |    7 -
 src/server/services/catalog.service.test.ts   |   88 ++
 src/server/services/catalog.service.ts        |   29 +-
 src/server/tgdb.ts                            |   42 +-
 src/server/utils/cache/MemoryCache.test.ts    |  263 ----
 src/server/utils/cache/MemoryCache.ts         |  210 ---
 src/server/utils/cache/index.ts               |    6 -
 src/server/utils/cache/instances.ts           |  114 +-
 src/server/utils/driver-versions.ts           |    6 +-
 src/server/utils/steamGameBatcher.ts          |   54 +-
 src/server/utils/steamGameSearch.ts           |   83 +-
 src/server/utils/switchGameSearch.ts          |  100 +-
 src/server/utils/threeDsGameSearch.ts         |   33 +-
 tsconfig.json                                 |    2 +-
 54 files changed, 1643 insertions(+), 3215 deletions(-)
 delete mode 100644 src/app/admin/monitoring/components/MonitoringOverview.tsx
 delete mode 100644 src/app/admin/monitoring/components/SEOMetricsDashboard.tsx
 delete mode 100644 src/app/api/cache/metrics/route.ts
 delete mode 100644 src/app/api/cache/warm/route.ts
 delete mode 100644 src/components/admin/CacheMetrics.tsx
 delete mode 100644 src/lib/cache/seo-cache.ts
 delete mode 100644 src/lib/monitoring/seo-metrics.ts
 rename src/{middleware.ts => proxy.ts} (71%)
 delete mode 100644 src/schemas/cache.ts
 delete mode 100644 src/server/api/routers/cache.ts
 create mode 100644 src/server/cache/invalidation.test.ts
 delete mode 100644 src/server/cache/warming.ts
 create mode 100644 src/server/db/seo-queries.test.ts
 create mode 100644 src/server/services/catalog.service.test.ts
 delete mode 100644 src/server/utils/cache/MemoryCache.test.ts
 delete mode 100644 src/server/utils/cache/MemoryCache.ts

diff --git a/.env.docker.example b/.env.docker.example
index f2c2607b1..70841edd0 100644
--- a/.env.docker.example
+++ b/.env.docker.example
@@ -40,6 +40,7 @@ TUNNEL_TOKEN="your_cloudflare_tunnel_token_here"
 # Google reCAPTCHA for form protection
 NEXT_PUBLIC_RECAPTCHA_SITE_KEY="your_recaptcha_site_key_here"
 RECAPTCHA_SECRET_KEY="your_recaptcha_secret_key_here"
+NEXT_PUBLIC_DISABLE_RECAPTCHA="false"
 
 # ===========================================
 # EMAIL SERVICES (Optional)
diff --git a/.env.example b/.env.example
index 8c4f20075..5efdaeff9 100644
--- a/.env.example
+++ b/.env.example
@@ -19,6 +19,7 @@ IGDB_CLIENT_KEY="IGDB-Client-Secret"
 # reCAPTCHA v3
 NEXT_PUBLIC_RECAPTCHA_SITE_KEY=site-key-here
 RECAPTCHA_SECRET_KEY=secret-key-here
+NEXT_PUBLIC_DISABLE_RECAPTCHA=false
 
 # Email Providers (For the future)
 EMAIL_ENABLED=false
diff --git a/.env.test.example b/.env.test.example
index 16229ad90..ff39873e6 100644
--- a/.env.test.example
+++ b/.env.test.example
@@ -9,6 +9,7 @@ CLERK_SECRET_KEY="sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
 # reCAPTCHA V3
 NEXT_PUBLIC_RECAPTCHA_SITE_KEY=
 RECAPTCHA_SECRET_KEY=
+NEXT_PUBLIC_DISABLE_RECAPTCHA=false
 
 # Email (For the future)
 EMAIL_ENABLED=false
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 19ea77fc9..4198461f0 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,16 +1,7 @@
-import path from 'node:path'
-import { fileURLToPath } from 'node:url'
-import { FlatCompat } from '@eslint/eslintrc'
 import typescriptEslint from '@typescript-eslint/eslint-plugin'
 import typescriptParser from '@typescript-eslint/parser'
-import importPlugin from 'eslint-plugin-import'
-
-const __filename = fileURLToPath(import.meta.url)
-const __dirname = path.dirname(__filename)
-
-const compat = new FlatCompat({
-  baseDirectory: __dirname,
-})
+import eslintConfigPrettier from 'eslint-config-prettier'
+import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'
 
 const eslintConfig = [
   {
@@ -37,10 +28,8 @@ const eslintConfig = [
       'test-results/**',
     ],
   },
-  // Next.js + Prettier via FlatCompat
-  ...compat.config({
-    extends: ['next/core-web-vitals', 'next/typescript', 'prettier'],
-  }), // JS and TS global config
+  ...nextCoreWebVitals,
+  eslintConfigPrettier,
   {
     files: ['**/*.{js,jsx,ts,tsx}'],
     languageOptions: {
@@ -55,14 +44,22 @@ const eslintConfig = [
       },
     },
     rules: {
-      // Your custom JS rules
       'prefer-template': 'error',
       'no-useless-escape': 'off',
       'no-case-declarations': 'off',
       'no-prototype-builtins': 'off',
       'no-redeclare': 'off',
+      // Next 16's flat config enables React Compiler rollout checks. Keep this upgrade
+      // focused on framework/caching behavior; enable these after fixing existing violations.
+      'react-hooks/purity': 'off',
+      'react-hooks/static-components': 'off',
+      'react-hooks/immutability': 'off',
+      'react-hooks/incompatible-library': 'off',
+      'react-hooks/preserve-manual-memoization': 'off',
+      'react-hooks/refs': 'off',
+      'react-hooks/set-state-in-effect': 'off',
     },
-  }, // TypeScript config
+  },
   {
     files: ['**/*.{ts,tsx}'],
     languageOptions: {
@@ -74,9 +71,6 @@ const eslintConfig = [
         ecmaFeatures: { jsx: true },
       },
     },
-    plugins: {
-      '@typescript-eslint': typescriptEslint,
-    },
     rules: {
       ...typescriptEslint.configs.recommended.rules,
       '@typescript-eslint/array-type': ['error', { default: 'array' }],
@@ -101,7 +95,6 @@ const eslintConfig = [
   }, // Import plugin
   {
     files: ['**/*.{js,jsx,ts,tsx}'],
-    plugins: { import: importPlugin },
     settings: {
       'import/resolver': {
         typescript: { project: './tsconfig.json' },
diff --git a/next.config.ts b/next.config.ts
index 76bdd4db9..9dff5afa0 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -203,6 +203,31 @@ const nextConfig: NextConfig = {
 
   allowedDevOrigins: ['dev.emuready.com', '127.0.0.1'],
 
+  cacheComponents: true,
+
+  cacheLife: {
+    'seo-record': {
+      stale: 300,
+      revalidate: 3600,
+      expire: 86400,
+    },
+    'seo-report': {
+      stale: 300,
+      revalidate: 1800,
+      expire: 43200,
+    },
+    'seo-sitemap': {
+      stale: 300,
+      revalidate: 21600,
+      expire: 172800,
+    },
+    'seo-miss': {
+      stale: 30,
+      revalidate: 60,
+      expire: 300,
+    },
+  },
+
   turbopack: {
     rules: {
       '*.svg': {
@@ -274,10 +299,6 @@ const nextConfig: NextConfig = {
     ],
   },
 
-  eslint: {
-    dirs: ['src', 'tests'],
-  },
-
   webpack: (config: WebpackConfiguration) => {
     config.module?.rules?.push({
       test: /\.svg$/,
diff --git a/package.json b/package.json
index b49c7ad97..ac82ecd07 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
     "@dnd-kit/sortable": "^10.0.0",
     "@dnd-kit/utilities": "^3.2.2",
     "@hookform/resolvers": "^5.0.1",
-    "@next/third-parties": "15.5.18",
+    "@next/third-parties": "16.2.6",
     "@prisma/adapter-pg": "7.8.0",
     "@prisma/client": "7.8.0",
     "@radix-ui/react-alert-dialog": "^1.1.14",
@@ -99,13 +99,13 @@
     "lucide-react": "^0.511.0",
     "mailersend": "^2.6.0",
     "markdown-it": "14.1.1",
-    "next": "15.5.18",
+    "next": "16.2.6",
     "next-themes": "^0.4.6",
     "pg": "8.21.0",
     "pg-pool": "3.14.0",
     "postcss": "8.5.14",
-    "react": "19.1.4",
-    "react-dom": "19.1.4",
+    "react": "19.2.6",
+    "react-dom": "19.2.6",
     "react-error-boundary": "^6.0.0",
     "react-google-recaptcha-v3": "^1.11.0",
     "react-hook-form": "^7.56.4",
@@ -123,8 +123,8 @@
     "@clerk/types": "4.101.23",
     "@eslint/eslintrc": "^3",
     "@eslint/js": "^9.28.0",
-    "@next/bundle-analyzer": "15.5.18",
-    "@next/eslint-plugin-next": "15.5.18",
+    "@next/bundle-analyzer": "16.2.6",
+    "@next/eslint-plugin-next": "16.2.6",
     "@playwright/test": "^1.60.0",
     "@svgr/webpack": "^8.1.0",
     "@tailwindcss/postcss": "^4.1.6",
@@ -135,8 +135,8 @@
     "@types/jsdom": "^21.1.7",
     "@types/markdown-it": "^14.1.2",
     "@types/node": "^20",
-    "@types/react": "19.1.8",
-    "@types/react-dom": "19.1.6",
+    "@types/react": "19.2.15",
+    "@types/react-dom": "19.2.3",
     "@types/swagger-jsdoc": "^6.0.4",
     "@types/webpack": "^5.28.5",
     "@typescript-eslint/eslint-plugin": "^8.34.0",
@@ -145,7 +145,7 @@
     "dotenv": "^16.5.0",
     "dotenv-cli": "^8.0.0",
     "eslint": "^9",
-    "eslint-config-next": "15.5.18",
+    "eslint-config-next": "16.2.6",
     "eslint-config-prettier": "^10.1.5",
     "eslint-import-resolver-typescript": "^4.4.3",
     "eslint-plugin-import": "^2.31.0",
@@ -173,7 +173,13 @@
     "sharp": "^0.33.5"
   },
   "overrides": {
-    "@types/react": "19.1.8",
-    "@types/react-dom": "19.1.6"
+    "@types/react": "19.2.15",
+    "@types/react-dom": "19.2.3"
+  },
+  "pnpm": {
+    "overrides": {
+      "@types/react": "19.2.15",
+      "@types/react-dom": "19.2.3"
+    }
   }
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4e5e31beb..461bd0c37 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,76 +16,76 @@ importers:
         version: 3.896.0
       '@clerk/backend':
         specifier: 2.33.3
-        version: 2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@clerk/nextjs':
         specifier: 6.39.3
-        version: 6.39.3(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 6.39.3(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@clerk/themes':
         specifier: ^2.2.48
-        version: 2.2.52(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 2.2.52(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@dnd-kit/core':
         specifier: ^6.3.1
-        version: 6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@dnd-kit/sortable':
         specifier: ^10.0.0
-        version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)
+        version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)
       '@dnd-kit/utilities':
         specifier: ^3.2.2
-        version: 3.2.2(react@19.1.4)
+        version: 3.2.2(react@19.2.6)
       '@hookform/resolvers':
         specifier: ^5.0.1
-        version: 5.1.1(react-hook-form@7.58.1(react@19.1.4))
+        version: 5.1.1(react-hook-form@7.58.1(react@19.2.6))
       '@next/third-parties':
-        specifier: 15.5.18
-        version: 15.5.18(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)
+        specifier: 16.2.6
+        version: 16.2.6(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)
       '@prisma/adapter-pg':
         specifier: 7.8.0
         version: 7.8.0
       '@prisma/client':
         specifier: 7.8.0
-        version: 7.8.0(prisma@7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3))(typescript@5.8.3)
+        version: 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3))(typescript@5.8.3)
       '@radix-ui/react-alert-dialog':
         specifier: ^1.1.14
-        version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@radix-ui/react-dialog':
         specifier: ^1.1.14
-        version: 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@radix-ui/react-popover':
         specifier: ^1.1.14
-        version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@radix-ui/react-slot':
         specifier: ^1.2.3
-        version: 1.2.3(@types/react@19.1.8)(react@19.1.4)
+        version: 1.2.3(@types/react@19.2.15)(react@19.2.6)
       '@radix-ui/react-switch':
         specifier: ^1.2.6
-        version: 1.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@radix-ui/react-tooltip':
         specifier: ^1.2.7
-        version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@scalar/api-reference-react':
         specifier: ^0.7.30
-        version: 0.7.30(axios@1.16.0)(react@19.1.4)(tailwindcss@4.1.11)(typescript@5.8.3)
+        version: 0.7.30(axios@1.16.0)(react@19.2.6)(tailwindcss@4.1.11)(typescript@5.8.3)
       '@sendgrid/mail':
         specifier: ^8.1.5
         version: 8.1.5
       '@sentry/nextjs':
         specifier: ^10.53.1
-        version: 10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(webpack@5.100.2)
+        version: 10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(webpack@5.100.2)
       '@tailwindcss/typography':
         specifier: ^0.5.16
         version: 0.5.16(tailwindcss@4.1.11)
       '@tanstack/react-query':
         specifier: 5.100.10
-        version: 5.100.10(react@19.1.4)
+        version: 5.100.10(react@19.2.6)
       '@trpc/client':
         specifier: 11.17.0
         version: 11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3)
       '@trpc/next':
         specifier: 11.17.0
-        version: 11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3)
+        version: 11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3)
       '@trpc/react-query':
         specifier: 11.17.0
-        version: 11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3)
+        version: 11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3)
       '@trpc/server':
         specifier: 11.17.0
         version: 11.17.0(typescript@5.8.3)
@@ -94,10 +94,10 @@ importers:
         version: 15.5.13
       '@vercel/analytics':
         specifier: ^1.5.0
-        version: 1.5.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
+        version: 1.5.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
       '@vercel/speed-insights':
         specifier: ^1.2.0
-        version: 1.2.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
+        version: 1.2.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
       axios:
         specifier: 1.16.0
         version: 1.16.0
@@ -109,7 +109,7 @@ importers:
         version: 2.1.1
       cmdk:
         specifier: ^1.1.1
-        version: 1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       critters:
         specifier: ^0.0.23
         version: 0.0.23
@@ -121,7 +121,7 @@ importers:
         version: 3.4.2
       framer-motion:
         specifier: ^12.11.0
-        version: 12.19.2(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 12.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       franc:
         specifier: ^6.2.0
         version: 6.2.0
@@ -142,7 +142,7 @@ importers:
         version: 11.1.0
       lucide-react:
         specifier: ^0.511.0
-        version: 0.511.0(react@19.1.4)
+        version: 0.511.0(react@19.2.6)
       mailersend:
         specifier: ^2.6.0
         version: 2.6.0
@@ -150,11 +150,11 @@ importers:
         specifier: 14.1.1
         version: 14.1.1
       next:
-        specifier: 15.5.18
-        version: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        specifier: 16.2.6
+        version: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       next-themes:
         specifier: ^0.4.6
-        version: 0.4.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       pg:
         specifier: 8.21.0
         version: 8.21.0
@@ -165,29 +165,29 @@ importers:
         specifier: 8.5.14
         version: 8.5.14
       react:
-        specifier: 19.1.4
-        version: 19.1.4
+        specifier: 19.2.6
+        version: 19.2.6
       react-dom:
-        specifier: 19.1.4
-        version: 19.1.4(react@19.1.4)
+        specifier: 19.2.6
+        version: 19.2.6(react@19.2.6)
       react-error-boundary:
         specifier: ^6.0.0
-        version: 6.0.0(react@19.1.4)
+        version: 6.0.0(react@19.2.6)
       react-google-recaptcha-v3:
         specifier: ^1.11.0
-        version: 1.11.0(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       react-hook-form:
         specifier: ^7.56.4
-        version: 7.58.1(react@19.1.4)
+        version: 7.58.1(react@19.2.6)
       react-syntax-highlighter:
         specifier: ^15.6.6
-        version: 15.6.6(react@19.1.4)
+        version: 15.6.6(react@19.2.6)
       remeda:
         specifier: ^2.22.1
         version: 2.23.1
       sonner:
         specifier: ^2.0.3
-        version: 2.0.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 2.0.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       superjson:
         specifier: ^2.2.2
         version: 2.2.2
@@ -206,10 +206,10 @@ importers:
     devDependencies:
       '@clerk/testing':
         specifier: ^1.8.0
-        version: 1.9.0(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 1.9.0(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@clerk/types':
         specifier: 4.101.23
-        version: 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@eslint/eslintrc':
         specifier: ^3
         version: 3.3.1
@@ -217,11 +217,11 @@ importers:
         specifier: ^9.28.0
         version: 9.30.0
       '@next/bundle-analyzer':
-        specifier: 15.5.18
-        version: 15.5.18
+        specifier: 16.2.6
+        version: 16.2.6
       '@next/eslint-plugin-next':
-        specifier: 15.5.18
-        version: 15.5.18
+        specifier: 16.2.6
+        version: 16.2.6
       '@playwright/test':
         specifier: ^1.60.0
         version: 1.60.0
@@ -239,7 +239,7 @@ importers:
         version: 6.9.1
       '@testing-library/react':
         specifier: ^16.3.2
-        version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+        version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       '@testing-library/user-event':
         specifier: ^14.6.1
         version: 14.6.1(@testing-library/dom@10.4.1)
@@ -253,11 +253,11 @@ importers:
         specifier: ^20
         version: 20.19.1
       '@types/react':
-        specifier: 19.1.8
-        version: 19.1.8
+        specifier: 19.2.15
+        version: 19.2.15
       '@types/react-dom':
-        specifier: 19.1.6
-        version: 19.1.6(@types/react@19.1.8)
+        specifier: 19.2.3
+        version: 19.2.3(@types/react@19.2.15)
       '@types/swagger-jsdoc':
         specifier: ^6.0.4
         version: 6.0.4
@@ -283,8 +283,8 @@ importers:
         specifier: ^9
         version: 9.29.0(jiti@2.7.0)
       eslint-config-next:
-        specifier: 15.5.18
-        version: 15.5.18(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+        specifier: 16.2.6
+        version: 16.2.6(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
       eslint-config-prettier:
         specifier: ^10.1.5
         version: 10.1.5(eslint@9.29.0(jiti@2.7.0))
@@ -314,7 +314,7 @@ importers:
         version: 3.6.2
       prisma:
         specifier: 7.8.0
-        version: 7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3)
+        version: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3)
       swagger-jsdoc:
         specifier: ^6.2.8
         version: 6.2.8(openapi-types@12.1.3)
@@ -1434,10 +1434,20 @@ packages:
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
 
+  '@eslint-community/eslint-utils@4.9.1':
+    resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
   '@eslint-community/regexpp@4.12.1':
     resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
     engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
 
+  '@eslint-community/regexpp@4.12.2':
+    resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
   '@eslint/config-array@0.20.1':
     resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1904,71 +1914,71 @@ packages:
   '@napi-rs/wasm-runtime@0.2.12':
     resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
 
-  '@next/bundle-analyzer@15.5.18':
-    resolution: {integrity: sha512-v5/UNFwYbBlRQg/Bt+wU65XuxCxPu1AeCOI6s4s6Cludsj7FdVO9E9uzr7GIj8OykSrYtGuEQAUX0Ulje8W2yw==}
+  '@next/bundle-analyzer@16.2.6':
+    resolution: {integrity: sha512-amPkVtHCTJAdBwyhhl5+qztHk24O4JlASgrWqh15AmnYi74apfZR46NGC0u4pM6BMAU1mYld4WdzD3cRBP3dOA==}
 
-  '@next/env@15.5.18':
-    resolution: {integrity: sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==}
+  '@next/env@16.2.6':
+    resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==}
 
-  '@next/eslint-plugin-next@15.5.18':
-    resolution: {integrity: sha512-w4MYq8M26a8PNrfto0JosLf5/3ssln1rsyP96g2DkC8uFVymStM5DLSz5ElxxrPRg2XnTMnFo3kREFlhYvxhWw==}
+  '@next/eslint-plugin-next@16.2.6':
+    resolution: {integrity: sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==}
 
-  '@next/swc-darwin-arm64@15.5.18':
-    resolution: {integrity: sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ==}
+  '@next/swc-darwin-arm64@16.2.6':
+    resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [darwin]
 
-  '@next/swc-darwin-x64@15.5.18':
-    resolution: {integrity: sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og==}
+  '@next/swc-darwin-x64@16.2.6':
+    resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
-  '@next/swc-linux-arm64-gnu@15.5.18':
-    resolution: {integrity: sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw==}
+  '@next/swc-linux-arm64-gnu@16.2.6':
+    resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
 
-  '@next/swc-linux-arm64-musl@15.5.18':
-    resolution: {integrity: sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw==}
+  '@next/swc-linux-arm64-musl@16.2.6':
+    resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
 
-  '@next/swc-linux-x64-gnu@15.5.18':
-    resolution: {integrity: sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A==}
+  '@next/swc-linux-x64-gnu@16.2.6':
+    resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
 
-  '@next/swc-linux-x64-musl@15.5.18':
-    resolution: {integrity: sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA==}
+  '@next/swc-linux-x64-musl@16.2.6':
+    resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
     libc: [musl]
 
-  '@next/swc-win32-arm64-msvc@15.5.18':
-    resolution: {integrity: sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA==}
+  '@next/swc-win32-arm64-msvc@16.2.6':
+    resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
 
-  '@next/swc-win32-x64-msvc@15.5.18':
-    resolution: {integrity: sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==}
+  '@next/swc-win32-x64-msvc@16.2.6':
+    resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
 
-  '@next/third-parties@15.5.18':
-    resolution: {integrity: sha512-Kp51CbeHgYelpkasFI1gAeAeMPfMTctLcmVIfE2MiRRfC6oYbR3+lO4S0NbNRppOZ7RkbB5P6uW/qOewxth6iA==}
+  '@next/third-parties@16.2.6':
+    resolution: {integrity: sha512-PDPIPVj1NX6Taxsl8OJteAUJ7iwR+QrokwWig68eh0cOmuNjC6MBL+ZzBjO8Bv0n/HOSqjGArZpM5KMSUxm+MQ==}
     peerDependencies:
-      next: ^13.0.0 || ^14.0.0 || ^15.0.0
+      next: ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0
       react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
 
   '@nodelib/fs.scandir@2.1.5':
@@ -2780,9 +2790,6 @@ packages:
   '@rtsao/scc@1.1.0':
     resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
 
-  '@rushstack/eslint-patch@1.12.0':
-    resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==}
-
   '@scalar/api-client@2.5.16':
     resolution: {integrity: sha512-pSsCm0jXqP7doctFL1l47Ctq22zaaTbSy5052hKkxKXosQfv7B77CNdfcIw+ibrt4xx+Epx2q9uc2Tolmcp/9w==}
     engines: {node: '>=20'}
@@ -3630,10 +3637,10 @@ packages:
   '@types/pg@8.20.0':
     resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
 
-  '@types/react-dom@19.1.6':
-    resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==}
+  '@types/react-dom@19.2.3':
+    resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
     peerDependencies:
-      '@types/react': ^19.0.0
+      '@types/react': ^19.2.0
 
   '@types/react-syntax-highlighter@15.5.13':
     resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==}
@@ -3641,6 +3648,9 @@ packages:
   '@types/react@19.1.8':
     resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==}
 
+  '@types/react@19.2.15':
+    resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==}
+
   '@types/swagger-jsdoc@6.0.4':
     resolution: {integrity: sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==}
 
@@ -3681,6 +3691,14 @@ packages:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/eslint-plugin@8.59.4':
+    resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^8.59.4
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/parser@8.35.0':
     resolution: {integrity: sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3695,6 +3713,13 @@ packages:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/parser@8.59.4':
+    resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/project-service@8.35.0':
     resolution: {integrity: sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3707,6 +3732,12 @@ packages:
     peerDependencies:
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/project-service@8.59.4':
+    resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/scope-manager@8.35.0':
     resolution: {integrity: sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3715,6 +3746,10 @@ packages:
     resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/scope-manager@8.59.4':
+    resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@typescript-eslint/tsconfig-utils@8.35.0':
     resolution: {integrity: sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3727,6 +3762,12 @@ packages:
     peerDependencies:
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/tsconfig-utils@8.59.4':
+    resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/type-utils@8.35.0':
     resolution: {integrity: sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3741,6 +3782,13 @@ packages:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/type-utils@8.59.4':
+    resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/types@8.35.0':
     resolution: {integrity: sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3749,6 +3797,10 @@ packages:
     resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/types@8.59.4':
+    resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@typescript-eslint/typescript-estree@8.35.0':
     resolution: {integrity: sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3761,6 +3813,12 @@ packages:
     peerDependencies:
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/typescript-estree@8.59.4':
+    resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/utils@8.35.0':
     resolution: {integrity: sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3775,6 +3833,13 @@ packages:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/utils@8.59.4':
+    resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+      typescript: '>=4.8.4 <6.1.0'
+
   '@typescript-eslint/visitor-keys@8.35.0':
     resolution: {integrity: sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3783,6 +3848,10 @@ packages:
     resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/visitor-keys@8.59.4':
+    resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@ungap/structured-clone@1.3.0':
     resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
     deprecated: Potential CWE-502 - Update to 1.3.1 or higher
@@ -4329,6 +4398,11 @@ packages:
     resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
     engines: {node: 18 || 20 || >=22}
 
+  baseline-browser-mapping@2.10.31:
+    resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
   better-result@2.9.2:
     resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==}
 
@@ -4612,6 +4686,9 @@ packages:
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
+  csstype@3.2.3:
+    resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
   cva@1.0.0-beta.2:
     resolution: {integrity: sha512-dqcOFe247I5pKxfuzqfq3seLL5iMYsTgo40Uw7+pKZAntPgFtR7Tmy59P5IVIq/XgB0NQWoIvYDt9TwHkuK8Cg==}
     peerDependencies:
@@ -4666,6 +4743,15 @@ packages:
       supports-color:
         optional: true
 
+  debug@4.4.3:
+    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
   decimal.js@10.5.0:
     resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
 
@@ -4883,10 +4969,10 @@ packages:
     resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
     engines: {node: '>=12'}
 
-  eslint-config-next@15.5.18:
-    resolution: {integrity: sha512-HuoJU6uUPD00eyiud78IBnT4HLhztFj2V+ild2Uon5ZUrYZKe0Olu2QRD99e9IgL4/H1eg5Onka3BsfRW2U0Xw==}
+  eslint-config-next@16.2.6:
+    resolution: {integrity: sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==}
     peerDependencies:
-      eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
+      eslint: '>=9.0.0'
       typescript: '>=3.3.1'
     peerDependenciesMeta:
       typescript:
@@ -4979,6 +5065,12 @@ packages:
     peerDependencies:
       eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
 
+  eslint-plugin-react-hooks@7.1.1:
+    resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0
+
   eslint-plugin-react@7.37.5:
     resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
     engines: {node: '>=4'}
@@ -5001,6 +5093,10 @@ packages:
     resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  eslint-visitor-keys@5.0.1:
+    resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+    engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
   eslint@9.29.0:
     resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -5284,6 +5380,10 @@ packages:
     resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
     engines: {node: '>=18'}
 
+  globals@16.4.0:
+    resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
+    engines: {node: '>=18'}
+
   globalthis@1.0.4:
     resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
     engines: {node: '>= 0.4'}
@@ -5399,6 +5499,12 @@ packages:
   hastscript@9.0.1:
     resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
 
+  hermes-estree@0.25.1:
+    resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+  hermes-parser@0.25.1:
+    resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
   highlight.js@10.7.3:
     resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
 
@@ -6230,9 +6336,9 @@ packages:
       react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
       react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
 
-  next@15.5.18:
-    resolution: {integrity: sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==}
-    engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+  next@16.2.6:
+    resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
+    engines: {node: '>=20.9.0'}
     hasBin: true
     peerDependencies:
       '@opentelemetry/api': ^1.1.0
@@ -6617,10 +6723,10 @@ packages:
   rc9@3.0.1:
     resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==}
 
-  react-dom@19.1.4:
-    resolution: {integrity: sha512-s2868ab/xo2SI6H4106A7aFI8Mrqa4xC6HZT/pBzYyQ3cBLqa88hu47xYD8xf+uECleN698Awn7RCWlkTiKnqQ==}
+  react-dom@19.2.6:
+    resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==}
     peerDependencies:
-      react: ^19.1.4
+      react: ^19.2.6
 
   react-error-boundary@6.0.0:
     resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==}
@@ -6684,8 +6790,8 @@ packages:
     peerDependencies:
       react: '>= 0.14.0'
 
-  react@19.1.4:
-    resolution: {integrity: sha512-DHINL3PAmPUiK1uszfbKiXqfE03eszdt5BpVSuEAHb5nfmNPwnsy7g39h2t8aXFc/Bv99GH81s+j8dobtD+jOw==}
+  react@19.2.6:
+    resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==}
     engines: {node: '>=0.10.0'}
 
   readdirp@3.6.0:
@@ -6840,8 +6946,8 @@ packages:
     resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
     engines: {node: '>=v12.22.7'}
 
-  scheduler@0.26.0:
-    resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
+  scheduler@0.27.0:
+    resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
 
   schema-utils@4.3.2:
     resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==}
@@ -7238,6 +7344,12 @@ packages:
     peerDependencies:
       typescript: '>=4.8.4'
 
+  ts-api-utils@2.5.0:
+    resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
+    engines: {node: '>=18.12'}
+    peerDependencies:
+      typescript: '>=4.8.4'
+
   ts-deepmerge@7.0.3:
     resolution: {integrity: sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==}
     engines: {node: '>=14.13.1'}
@@ -7315,6 +7427,13 @@ packages:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <5.9.0'
 
+  typescript-eslint@8.59.4:
+    resolution: {integrity: sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+      typescript: '>=4.8.4 <6.1.0'
+
   typescript@5.8.3:
     resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
     engines: {node: '>=14.17'}
@@ -7744,6 +7863,12 @@ packages:
     peerDependencies:
       zod: ^3.24.1
 
+  zod-validation-error@4.0.2:
+    resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+    engines: {node: '>=18.0.0'}
+    peerDependencies:
+      zod: ^3.25.0 || ^4.0.0
+
   zod@3.24.1:
     resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
 
@@ -9010,52 +9135,52 @@ snapshots:
       '@babel/helper-string-parser': 7.27.1
       '@babel/helper-validator-identifier': 7.27.1
 
-  '@clerk/backend@2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/backend@2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+      '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       standardwebhooks: 1.0.0
       tslib: 2.8.1
     transitivePeerDependencies:
       - react
       - react-dom
 
-  '@clerk/clerk-react@5.61.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/clerk-react@5.61.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
       tslib: 2.8.1
 
-  '@clerk/nextjs@6.39.3(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/nextjs@6.39.3(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@clerk/backend': 2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@clerk/clerk-react': 5.61.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@clerk/backend': 2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@clerk/clerk-react': 5.61.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
       server-only: 0.0.1
       tslib: 2.8.1
 
-  '@clerk/shared@3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/shared@3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       csstype: 3.1.3
       dequal: 2.0.3
       glob-to-regexp: 0.4.1
       js-cookie: 3.0.5
       std-env: 3.9.0
-      swr: 2.3.4(react@19.1.4)
+      swr: 2.3.4(react@19.2.6)
     optionalDependencies:
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
 
-  '@clerk/testing@1.9.0(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/testing@1.9.0(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@clerk/backend': 2.33.3(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+      '@clerk/backend': 2.33.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       dotenv: 16.4.7
     optionalDependencies:
       '@playwright/test': 1.60.0
@@ -9063,17 +9188,17 @@ snapshots:
       - react
       - react-dom
 
-  '@clerk/themes@2.2.52(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/themes@2.2.52(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@clerk/types': 4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+      '@clerk/types': 4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       tslib: 2.8.1
     transitivePeerDependencies:
       - react
       - react-dom
 
-  '@clerk/types@4.101.23(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@clerk/types@4.101.23(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@clerk/shared': 3.47.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+      '@clerk/shared': 3.47.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
     transitivePeerDependencies:
       - react
       - react-dom
@@ -9204,29 +9329,29 @@ snapshots:
 
   '@discoveryjs/json-ext@0.5.7': {}
 
-  '@dnd-kit/accessibility@3.1.1(react@19.1.4)':
+  '@dnd-kit/accessibility@3.1.1(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
       tslib: 2.8.1
 
-  '@dnd-kit/core@6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@dnd-kit/accessibility': 3.1.1(react@19.1.4)
-      '@dnd-kit/utilities': 3.2.2(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@dnd-kit/accessibility': 3.1.1(react@19.2.6)
+      '@dnd-kit/utilities': 3.2.2(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
       tslib: 2.8.1
 
-  '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)':
+  '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@dnd-kit/core': 6.3.1(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@dnd-kit/utilities': 3.2.2(react@19.1.4)
-      react: 19.1.4
+      '@dnd-kit/core': 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@dnd-kit/utilities': 3.2.2(react@19.2.6)
+      react: 19.2.6
       tslib: 2.8.1
 
-  '@dnd-kit/utilities@3.2.2(react@19.1.4)':
+  '@dnd-kit/utilities@3.2.2(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
       tslib: 2.8.1
 
   '@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)':
@@ -9335,8 +9460,15 @@ snapshots:
       eslint: 9.29.0(jiti@2.7.0)
       eslint-visitor-keys: 3.4.3
 
+  '@eslint-community/eslint-utils@4.9.1(eslint@9.29.0(jiti@2.7.0))':
+    dependencies:
+      eslint: 9.29.0(jiti@2.7.0)
+      eslint-visitor-keys: 3.4.3
+
   '@eslint-community/regexpp@4.12.1': {}
 
+  '@eslint-community/regexpp@4.12.2': {}
+
   '@eslint/config-array@0.20.1':
     dependencies:
       '@eslint/object-schema': 2.1.6
@@ -9399,11 +9531,11 @@ snapshots:
       '@floating-ui/core': 1.7.5
       '@floating-ui/utils': 0.2.11
 
-  '@floating-ui/react-dom@2.1.8(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@floating-ui/dom': 1.7.6
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
 
   '@floating-ui/utils@0.2.11': {}
 
@@ -9429,10 +9561,10 @@ snapshots:
     dependencies:
       hono: 4.12.21
 
-  '@hookform/resolvers@5.1.1(react-hook-form@7.58.1(react@19.1.4))':
+  '@hookform/resolvers@5.1.1(react-hook-form@7.58.1(react@19.2.6))':
     dependencies:
       '@standard-schema/utils': 0.3.0
-      react-hook-form: 7.58.1(react@19.1.4)
+      react-hook-form: 7.58.1(react@19.2.6)
 
   '@humanfs/core@0.19.1': {}
 
@@ -9743,47 +9875,47 @@ snapshots:
       '@tybys/wasm-util': 0.10.1
     optional: true
 
-  '@next/bundle-analyzer@15.5.18':
+  '@next/bundle-analyzer@16.2.6':
     dependencies:
       webpack-bundle-analyzer: 4.10.1
     transitivePeerDependencies:
       - bufferutil
       - utf-8-validate
 
-  '@next/env@15.5.18': {}
+  '@next/env@16.2.6': {}
 
-  '@next/eslint-plugin-next@15.5.18':
+  '@next/eslint-plugin-next@16.2.6':
     dependencies:
       fast-glob: 3.3.1
 
-  '@next/swc-darwin-arm64@15.5.18':
+  '@next/swc-darwin-arm64@16.2.6':
     optional: true
 
-  '@next/swc-darwin-x64@15.5.18':
+  '@next/swc-darwin-x64@16.2.6':
     optional: true
 
-  '@next/swc-linux-arm64-gnu@15.5.18':
+  '@next/swc-linux-arm64-gnu@16.2.6':
     optional: true
 
-  '@next/swc-linux-arm64-musl@15.5.18':
+  '@next/swc-linux-arm64-musl@16.2.6':
     optional: true
 
-  '@next/swc-linux-x64-gnu@15.5.18':
+  '@next/swc-linux-x64-gnu@16.2.6':
     optional: true
 
-  '@next/swc-linux-x64-musl@15.5.18':
+  '@next/swc-linux-x64-musl@16.2.6':
     optional: true
 
-  '@next/swc-win32-arm64-msvc@15.5.18':
+  '@next/swc-win32-arm64-msvc@16.2.6':
     optional: true
 
-  '@next/swc-win32-x64-msvc@15.5.18':
+  '@next/swc-win32-x64-msvc@16.2.6':
     optional: true
 
-  '@next/third-parties@15.5.18(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)':
+  '@next/third-parties@16.2.6(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)':
     dependencies:
-      next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
+      next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
       third-party-capital: 1.0.20
 
   '@nodelib/fs.scandir@2.1.5':
@@ -10049,11 +10181,11 @@ snapshots:
 
   '@prisma/client-runtime-utils@7.8.0': {}
 
-  '@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3))(typescript@5.8.3)':
+  '@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3))(typescript@5.8.3)':
     dependencies:
       '@prisma/client-runtime-utils': 7.8.0
     optionalDependencies:
-      prisma: 7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3)
+      prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3)
       typescript: 5.8.3
 
   '@prisma/config@7.8.0':
@@ -10134,13 +10266,13 @@ snapshots:
       env-paths: 3.0.0
       proper-lockfile: 4.1.2
 
-  '@prisma/studio-core@0.27.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@prisma/studio-core@0.27.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@types/react': 19.1.8
+      '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@types/react': 19.2.15
       chart.js: 4.5.1
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     transitivePeerDependencies:
       - '@types/react-dom'
 
@@ -10148,336 +10280,336 @@ snapshots:
 
   '@radix-ui/primitive@1.1.3': {}
 
-  '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.2
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-context@1.1.2(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.2
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6)
       aria-hidden: 1.2.6
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
-      react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
+      react-remove-scroll: 2.7.1(@types/react@19.2.15)(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6)
       aria-hidden: 1.2.6
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
-      react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
+      react-remove-scroll: 2.7.1(@types/react@19.2.15)(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.2
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-focus-guards@1.1.2(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-id@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-popover@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.2
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6)
       aria-hidden: 1.2.6
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
-      react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
+      react-remove-scroll: 2.7.1(@types/react@19.2.15)(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
-
-  '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
-    dependencies:
-      '@floating-ui/react-dom': 2.1.8(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.4)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
+
+  '@radix-ui/react-popper@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+    dependencies:
+      '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6)
       '@radix-ui/rect': 1.1.1
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-presence@1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-slot@1.2.3(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-switch@1.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@radix-ui/primitive': 1.1.2
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
-  '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
+      '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
       '@radix-ui/rect': 1.1.1
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.4)':
+  '@radix-ui/react-use-size@1.1.1(@types/react@19.2.15)(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      react: 19.1.4
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      react: 19.2.6
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
   '@radix-ui/rect@1.1.1': {}
 
@@ -10589,8 +10721,6 @@ snapshots:
 
   '@rtsao/scc@1.1.0': {}
 
-  '@rushstack/eslint-patch@1.12.0': {}
-
   '@scalar/api-client@2.5.16(axios@1.16.0)(tailwindcss@4.1.11)(typescript@5.8.3)':
     dependencies:
       '@headlessui/tailwindcss': 0.2.2(tailwindcss@4.1.11)
@@ -10644,11 +10774,11 @@ snapshots:
       - typescript
       - universal-cookie
 
-  '@scalar/api-reference-react@0.7.30(axios@1.16.0)(react@19.1.4)(tailwindcss@4.1.11)(typescript@5.8.3)':
+  '@scalar/api-reference-react@0.7.30(axios@1.16.0)(react@19.2.6)(tailwindcss@4.1.11)(typescript@5.8.3)':
     dependencies:
       '@scalar/api-reference': 1.32.6(axios@1.16.0)(tailwindcss@4.1.11)(typescript@5.8.3)
       '@scalar/types': 0.2.8
-      react: 19.1.4
+      react: 19.2.6
     transitivePeerDependencies:
       - '@vue/composition-api'
       - async-validator
@@ -11016,7 +11146,7 @@ snapshots:
 
   '@sentry/core@10.53.1': {}
 
-  '@sentry/nextjs@10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(webpack@5.100.2)':
+  '@sentry/nextjs@10.53.1(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(webpack@5.100.2)':
     dependencies:
       '@opentelemetry/api': 1.9.1
       '@opentelemetry/semantic-conventions': 1.40.0
@@ -11026,10 +11156,10 @@ snapshots:
       '@sentry/core': 10.53.1
       '@sentry/node': 10.53.1
       '@sentry/opentelemetry': 10.53.1(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0)
-      '@sentry/react': 10.53.1(react@19.1.4)
+      '@sentry/react': 10.53.1(react@19.2.6)
       '@sentry/vercel-edge': 10.53.1
       '@sentry/webpack-plugin': 5.3.0(webpack@5.100.2)
-      next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+      next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       rollup: 4.60.3
       stacktrace-parser: 0.1.11
     transitivePeerDependencies:
@@ -11096,11 +11226,11 @@ snapshots:
       '@opentelemetry/semantic-conventions': 1.40.0
       '@sentry/core': 10.53.1
 
-  '@sentry/react@10.53.1(react@19.1.4)':
+  '@sentry/react@10.53.1(react@19.2.6)':
     dependencies:
       '@sentry/browser': 10.53.1
       '@sentry/core': 10.53.1
-      react: 19.1.4
+      react: 19.2.6
 
   '@sentry/vercel-edge@10.53.1':
     dependencies:
@@ -11641,10 +11771,10 @@ snapshots:
 
   '@tanstack/query-core@5.100.10': {}
 
-  '@tanstack/react-query@5.100.10(react@19.1.4)':
+  '@tanstack/react-query@5.100.10(react@19.2.6)':
     dependencies:
       '@tanstack/query-core': 5.100.10
-      react: 19.1.4
+      react: 19.2.6
 
   '@tanstack/virtual-core@3.13.12': {}
 
@@ -11673,15 +11803,15 @@ snapshots:
       picocolors: 1.1.1
       redent: 3.0.0
 
-  '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)':
+  '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
     dependencies:
       '@babel/runtime': 7.27.6
       '@testing-library/dom': 10.4.1
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
-      '@types/react-dom': 19.1.6(@types/react@19.1.8)
+      '@types/react': 19.2.15
+      '@types/react-dom': 19.2.3(@types/react@19.2.15)
 
   '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
     dependencies:
@@ -11692,24 +11822,24 @@ snapshots:
       '@trpc/server': 11.17.0(typescript@5.8.3)
       typescript: 5.8.3
 
-  '@trpc/next@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3)':
+  '@trpc/next@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3)':
     dependencies:
       '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3)
       '@trpc/server': 11.17.0(typescript@5.8.3)
-      next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
       typescript: 5.8.3
     optionalDependencies:
-      '@tanstack/react-query': 5.100.10(react@19.1.4)
-      '@trpc/react-query': 11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3)
+      '@tanstack/react-query': 5.100.10(react@19.2.6)
+      '@trpc/react-query': 11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3)
 
-  '@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.1.4))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.1.4)(typescript@5.8.3)':
+  '@trpc/react-query@11.17.0(@tanstack/react-query@5.100.10(react@19.2.6))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.17.0(typescript@5.8.3))(react@19.2.6)(typescript@5.8.3)':
     dependencies:
-      '@tanstack/react-query': 5.100.10(react@19.1.4)
+      '@tanstack/react-query': 5.100.10(react@19.2.6)
       '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.8.3))(typescript@5.8.3)
       '@trpc/server': 11.17.0(typescript@5.8.3)
-      react: 19.1.4
+      react: 19.2.6
       typescript: 5.8.3
 
   '@trpc/server@11.17.0(typescript@5.8.3)':
@@ -11843,9 +11973,9 @@ snapshots:
       pg-protocol: 1.14.0
       pg-types: 2.2.0
 
-  '@types/react-dom@19.1.6(@types/react@19.1.8)':
+  '@types/react-dom@19.2.3(@types/react@19.2.15)':
     dependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
   '@types/react-syntax-highlighter@15.5.13':
     dependencies:
@@ -11855,6 +11985,10 @@ snapshots:
     dependencies:
       csstype: 3.1.3
 
+  '@types/react@19.2.15':
+    dependencies:
+      csstype: 3.2.3
+
   '@types/swagger-jsdoc@6.0.4': {}
 
   '@types/tedious@4.0.14':
@@ -11917,6 +12051,22 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
+    dependencies:
+      '@eslint-community/regexpp': 4.12.2
+      '@typescript-eslint/parser': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      '@typescript-eslint/scope-manager': 8.59.4
+      '@typescript-eslint/type-utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      '@typescript-eslint/utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      '@typescript-eslint/visitor-keys': 8.59.4
+      eslint: 9.29.0(jiti@2.7.0)
+      ignore: 7.0.5
+      natural-compare: 1.4.0
+      ts-api-utils: 2.5.0(typescript@5.8.3)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
     dependencies:
       '@typescript-eslint/scope-manager': 8.35.0
@@ -11941,6 +12091,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
+    dependencies:
+      '@typescript-eslint/scope-manager': 8.59.4
+      '@typescript-eslint/types': 8.59.4
+      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3)
+      '@typescript-eslint/visitor-keys': 8.59.4
+      debug: 4.4.3
+      eslint: 9.29.0(jiti@2.7.0)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/project-service@8.35.0(typescript@5.8.3)':
     dependencies:
       '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.8.3)
@@ -11959,6 +12121,15 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/project-service@8.59.4(typescript@5.8.3)':
+    dependencies:
+      '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.8.3)
+      '@typescript-eslint/types': 8.59.4
+      debug: 4.4.3
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/scope-manager@8.35.0':
     dependencies:
       '@typescript-eslint/types': 8.35.0
@@ -11969,6 +12140,11 @@ snapshots:
       '@typescript-eslint/types': 8.39.0
       '@typescript-eslint/visitor-keys': 8.39.0
 
+  '@typescript-eslint/scope-manager@8.59.4':
+    dependencies:
+      '@typescript-eslint/types': 8.59.4
+      '@typescript-eslint/visitor-keys': 8.59.4
+
   '@typescript-eslint/tsconfig-utils@8.35.0(typescript@5.8.3)':
     dependencies:
       typescript: 5.8.3
@@ -11977,6 +12153,10 @@ snapshots:
     dependencies:
       typescript: 5.8.3
 
+  '@typescript-eslint/tsconfig-utils@8.59.4(typescript@5.8.3)':
+    dependencies:
+      typescript: 5.8.3
+
   '@typescript-eslint/type-utils@8.35.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
     dependencies:
       '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3)
@@ -12000,10 +12180,24 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/type-utils@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
+    dependencies:
+      '@typescript-eslint/types': 8.59.4
+      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3)
+      '@typescript-eslint/utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      debug: 4.4.3
+      eslint: 9.29.0(jiti@2.7.0)
+      ts-api-utils: 2.5.0(typescript@5.8.3)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/types@8.35.0': {}
 
   '@typescript-eslint/types@8.39.0': {}
 
+  '@typescript-eslint/types@8.59.4': {}
+
   '@typescript-eslint/typescript-estree@8.35.0(typescript@5.8.3)':
     dependencies:
       '@typescript-eslint/project-service': 8.35.0(typescript@5.8.3)
@@ -12036,6 +12230,21 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/typescript-estree@8.59.4(typescript@5.8.3)':
+    dependencies:
+      '@typescript-eslint/project-service': 8.59.4(typescript@5.8.3)
+      '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.8.3)
+      '@typescript-eslint/types': 8.59.4
+      '@typescript-eslint/visitor-keys': 8.59.4
+      debug: 4.4.3
+      minimatch: 10.2.5
+      semver: 7.7.3
+      tinyglobby: 0.2.15
+      ts-api-utils: 2.5.0(typescript@5.8.3)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/utils@8.35.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
     dependencies:
       '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0))
@@ -12058,6 +12267,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/utils@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)':
+    dependencies:
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.29.0(jiti@2.7.0))
+      '@typescript-eslint/scope-manager': 8.59.4
+      '@typescript-eslint/types': 8.59.4
+      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3)
+      eslint: 9.29.0(jiti@2.7.0)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/visitor-keys@8.35.0':
     dependencies:
       '@typescript-eslint/types': 8.35.0
@@ -12068,6 +12288,11 @@ snapshots:
       '@typescript-eslint/types': 8.39.0
       eslint-visitor-keys: 4.2.1
 
+  '@typescript-eslint/visitor-keys@8.59.4':
+    dependencies:
+      '@typescript-eslint/types': 8.59.4
+      eslint-visitor-keys: 5.0.1
+
   '@ungap/structured-clone@1.3.0': {}
 
   '@unhead/dom@1.11.20':
@@ -12152,17 +12377,17 @@ snapshots:
   '@unrs/resolver-binding-win32-x64-msvc@1.9.2':
     optional: true
 
-  '@vercel/analytics@1.5.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))':
+  '@vercel/analytics@1.5.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))':
     optionalDependencies:
-      next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
+      next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
       vue: 3.5.17(typescript@5.8.3)
       vue-router: 4.5.1(vue@3.5.17(typescript@5.8.3))
 
-  '@vercel/speed-insights@1.2.0(next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4))(react@19.1.4)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))':
+  '@vercel/speed-insights@1.2.0(next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))':
     optionalDependencies:
-      next: 15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
+      next: 16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
       vue: 3.5.17(typescript@5.8.3)
       vue-router: 4.5.1(vue@3.5.17(typescript@5.8.3))
 
@@ -12619,6 +12844,8 @@ snapshots:
 
   balanced-match@4.0.4: {}
 
+  baseline-browser-mapping@2.10.31: {}
+
   better-result@2.9.2: {}
 
   binary-extensions@2.3.0: {}
@@ -12759,14 +12986,14 @@ snapshots:
 
   clsx@2.1.1: {}
 
-  cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4):
+  cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.4)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
     transitivePeerDependencies:
       - '@types/react'
       - '@types/react-dom'
@@ -12906,6 +13133,8 @@ snapshots:
 
   csstype@3.1.3: {}
 
+  csstype@3.2.3: {}
+
   cva@1.0.0-beta.2(typescript@5.8.3):
     dependencies:
       clsx: 2.1.1
@@ -12953,6 +13182,10 @@ snapshots:
     optionalDependencies:
       supports-color: 5.5.0
 
+  debug@4.4.3:
+    dependencies:
+      ms: 2.1.3
+
   decimal.js@10.5.0: {}
 
   decode-named-character-reference@1.2.0:
@@ -13232,22 +13465,22 @@ snapshots:
 
   escape-string-regexp@5.0.0: {}
 
-  eslint-config-next@15.5.18(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3):
+  eslint-config-next@16.2.6(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3):
     dependencies:
-      '@next/eslint-plugin-next': 15.5.18
-      '@rushstack/eslint-patch': 1.12.0
-      '@typescript-eslint/eslint-plugin': 8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
-      '@typescript-eslint/parser': 8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      '@next/eslint-plugin-next': 16.2.6
       eslint: 9.29.0(jiti@2.7.0)
       eslint-import-resolver-node: 0.3.9
       eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.7.0))
       eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.7.0))
       eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.7.0))
       eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.7.0))
-      eslint-plugin-react-hooks: 5.2.0(eslint@9.29.0(jiti@2.7.0))
+      eslint-plugin-react-hooks: 7.1.1(eslint@9.29.0(jiti@2.7.0))
+      globals: 16.4.0
+      typescript-eslint: 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
     optionalDependencies:
       typescript: 5.8.3
     transitivePeerDependencies:
+      - '@typescript-eslint/parser'
       - eslint-import-resolver-webpack
       - eslint-plugin-import-x
       - supports-color
@@ -13404,6 +13637,17 @@ snapshots:
     dependencies:
       eslint: 9.29.0(jiti@2.7.0)
 
+  eslint-plugin-react-hooks@7.1.1(eslint@9.29.0(jiti@2.7.0)):
+    dependencies:
+      '@babel/core': 7.27.7
+      '@babel/parser': 7.27.7
+      eslint: 9.29.0(jiti@2.7.0)
+      hermes-parser: 0.25.1
+      zod: 3.25.67
+      zod-validation-error: 4.0.2(zod@3.25.67)
+    transitivePeerDependencies:
+      - supports-color
+
   eslint-plugin-react@7.37.5(eslint@9.29.0(jiti@2.7.0)):
     dependencies:
       array-includes: 3.1.9
@@ -13440,6 +13684,8 @@ snapshots:
 
   eslint-visitor-keys@4.2.1: {}
 
+  eslint-visitor-keys@5.0.1: {}
+
   eslint@9.29.0(jiti@2.7.0):
     dependencies:
       '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0))
@@ -13620,14 +13866,14 @@ snapshots:
 
   forwarded-parse@2.1.2: {}
 
-  framer-motion@12.19.2(react-dom@19.1.4(react@19.1.4))(react@19.1.4):
+  framer-motion@12.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
     dependencies:
       motion-dom: 12.19.0
       motion-utils: 12.19.0
       tslib: 2.8.1
     optionalDependencies:
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
 
   franc-min@6.2.0:
     dependencies:
@@ -13746,6 +13992,8 @@ snapshots:
 
   globals@14.0.0: {}
 
+  globals@16.4.0: {}
+
   globalthis@1.0.4:
     dependencies:
       define-properties: 1.2.1
@@ -13933,6 +14181,12 @@ snapshots:
       property-information: 7.1.0
       space-separated-tokens: 2.0.2
 
+  hermes-estree@0.25.1: {}
+
+  hermes-parser@0.25.1:
+    dependencies:
+      hermes-estree: 0.25.1
+
   highlight.js@10.7.3: {}
 
   highlight.js@11.11.1: {}
@@ -14455,9 +14709,9 @@ snapshots:
 
   lru.min@1.1.4: {}
 
-  lucide-react@0.511.0(react@19.1.4):
+  lucide-react@0.511.0(react@19.2.6):
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
 
   lz-string@1.5.0: {}
 
@@ -14891,29 +15145,30 @@ snapshots:
 
   neo-async@2.6.2: {}
 
-  next-themes@0.4.6(react-dom@19.1.4(react@19.1.4))(react@19.1.4):
+  next-themes@0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
     dependencies:
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
 
-  next@15.5.18(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.1.4(react@19.1.4))(react@19.1.4):
+  next@16.2.6(@babel/core@7.27.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
     dependencies:
-      '@next/env': 15.5.18
+      '@next/env': 16.2.6
       '@swc/helpers': 0.5.15
+      baseline-browser-mapping: 2.10.31
       caniuse-lite: 1.0.30001726
       postcss: 8.4.31
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
-      styled-jsx: 5.1.6(@babel/core@7.27.7)(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
+      styled-jsx: 5.1.6(@babel/core@7.27.7)(react@19.2.6)
     optionalDependencies:
-      '@next/swc-darwin-arm64': 15.5.18
-      '@next/swc-darwin-x64': 15.5.18
-      '@next/swc-linux-arm64-gnu': 15.5.18
-      '@next/swc-linux-arm64-musl': 15.5.18
-      '@next/swc-linux-x64-gnu': 15.5.18
-      '@next/swc-linux-x64-musl': 15.5.18
-      '@next/swc-win32-arm64-msvc': 15.5.18
-      '@next/swc-win32-x64-msvc': 15.5.18
+      '@next/swc-darwin-arm64': 16.2.6
+      '@next/swc-darwin-x64': 16.2.6
+      '@next/swc-linux-arm64-gnu': 16.2.6
+      '@next/swc-linux-arm64-musl': 16.2.6
+      '@next/swc-linux-x64-gnu': 16.2.6
+      '@next/swc-linux-x64-musl': 16.2.6
+      '@next/swc-win32-arm64-msvc': 16.2.6
+      '@next/swc-win32-x64-msvc': 16.2.6
       '@opentelemetry/api': 1.9.1
       '@playwright/test': 1.60.0
       sharp: 0.34.5
@@ -15197,12 +15452,12 @@ snapshots:
     dependencies:
       parse-ms: 3.0.0
 
-  prisma@7.8.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)(typescript@5.8.3):
+  prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.8.3):
     dependencies:
       '@prisma/config': 7.8.0
       '@prisma/dev': 0.24.3(typescript@5.8.3)
       '@prisma/engines': 7.8.0
-      '@prisma/studio-core': 0.27.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.4(react@19.1.4))(react@19.1.4)
+      '@prisma/studio-core': 0.27.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
       mysql2: 3.15.3
       postgres: 3.4.7
     optionalDependencies:
@@ -15286,25 +15541,25 @@ snapshots:
       defu: 6.1.7
       destr: 2.0.5
 
-  react-dom@19.1.4(react@19.1.4):
+  react-dom@19.2.6(react@19.2.6):
     dependencies:
-      react: 19.1.4
-      scheduler: 0.26.0
+      react: 19.2.6
+      scheduler: 0.27.0
 
-  react-error-boundary@6.0.0(react@19.1.4):
+  react-error-boundary@6.0.0(react@19.2.6):
     dependencies:
       '@babel/runtime': 7.27.6
-      react: 19.1.4
+      react: 19.2.6
 
-  react-google-recaptcha-v3@1.11.0(react-dom@19.1.4(react@19.1.4))(react@19.1.4):
+  react-google-recaptcha-v3@1.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
     dependencies:
       hoist-non-react-statics: 3.3.2
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
 
-  react-hook-form@7.58.1(react@19.1.4):
+  react-hook-form@7.58.1(react@19.2.6):
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
 
   react-is@16.13.1: {}
 
@@ -15312,44 +15567,44 @@ snapshots:
 
   react-refresh@0.17.0: {}
 
-  react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.4):
+  react-remove-scroll-bar@2.3.8(@types/react@19.2.15)(react@19.2.6):
     dependencies:
-      react: 19.1.4
-      react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.4)
+      react: 19.2.6
+      react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6)
       tslib: 2.8.1
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.4):
+  react-remove-scroll@2.7.1(@types/react@19.2.15)(react@19.2.6):
     dependencies:
-      react: 19.1.4
-      react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.4)
-      react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.4)
+      react: 19.2.6
+      react-remove-scroll-bar: 2.3.8(@types/react@19.2.15)(react@19.2.6)
+      react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6)
       tslib: 2.8.1
-      use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.4)
-      use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.4)
+      use-callback-ref: 1.3.3(@types/react@19.2.15)(react@19.2.6)
+      use-sidecar: 1.1.3(@types/react@19.2.15)(react@19.2.6)
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.4):
+  react-style-singleton@2.2.3(@types/react@19.2.15)(react@19.2.6):
     dependencies:
       get-nonce: 1.0.1
-      react: 19.1.4
+      react: 19.2.6
       tslib: 2.8.1
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  react-syntax-highlighter@15.6.6(react@19.1.4):
+  react-syntax-highlighter@15.6.6(react@19.2.6):
     dependencies:
       '@babel/runtime': 7.27.6
       highlight.js: 10.7.3
       highlightjs-vue: 1.0.0
       lowlight: 1.20.0
       prismjs: 1.30.0
-      react: 19.1.4
+      react: 19.2.6
       refractor: 3.6.0
 
-  react@19.1.4: {}
+  react@19.2.6: {}
 
   readdirp@3.6.0:
     dependencies:
@@ -15588,7 +15843,7 @@ snapshots:
     dependencies:
       xmlchars: 2.2.0
 
-  scheduler@0.26.0: {}
+  scheduler@0.27.0: {}
 
   schema-utils@4.3.2:
     dependencies:
@@ -15762,10 +16017,10 @@ snapshots:
       dot-case: 3.0.4
       tslib: 2.8.1
 
-  sonner@2.0.5(react-dom@19.1.4(react@19.1.4))(react@19.1.4):
+  sonner@2.0.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
     dependencies:
-      react: 19.1.4
-      react-dom: 19.1.4(react@19.1.4)
+      react: 19.2.6
+      react-dom: 19.2.6(react@19.2.6)
 
   source-map-js@1.2.1: {}
 
@@ -15895,10 +16150,10 @@ snapshots:
 
   style-mod@4.1.2: {}
 
-  styled-jsx@5.1.6(@babel/core@7.27.7)(react@19.1.4):
+  styled-jsx@5.1.6(@babel/core@7.27.7)(react@19.2.6):
     dependencies:
       client-only: 0.0.1
-      react: 19.1.4
+      react: 19.2.6
     optionalDependencies:
       '@babel/core': 7.27.7
 
@@ -15968,11 +16223,11 @@ snapshots:
     transitivePeerDependencies:
       - openapi-types
 
-  swr@2.3.4(react@19.1.4):
+  swr@2.3.4(react@19.2.6):
     dependencies:
       dequal: 2.0.3
-      react: 19.1.4
-      use-sync-external-store: 1.6.0(react@19.1.4)
+      react: 19.2.6
+      use-sync-external-store: 1.6.0(react@19.2.6)
 
   symbol-tree@3.2.4: {}
 
@@ -16065,6 +16320,10 @@ snapshots:
     dependencies:
       typescript: 5.8.3
 
+  ts-api-utils@2.5.0(typescript@5.8.3):
+    dependencies:
+      typescript: 5.8.3
+
   ts-deepmerge@7.0.3: {}
 
   ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3):
@@ -16158,6 +16417,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  typescript-eslint@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3):
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      '@typescript-eslint/parser': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.8.3)
+      '@typescript-eslint/utils': 8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3)
+      eslint: 9.29.0(jiti@2.7.0)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
+
   typescript@5.8.3: {}
 
   uc.micro@2.1.0: {}
@@ -16270,24 +16540,24 @@ snapshots:
       querystringify: 2.2.0
       requires-port: 1.0.0
 
-  use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.4):
+  use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6):
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
       tslib: 2.8.1
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.4):
+  use-sidecar@1.1.3(@types/react@19.2.15)(react@19.2.6):
     dependencies:
       detect-node-es: 1.1.0
-      react: 19.1.4
+      react: 19.2.6
       tslib: 2.8.1
     optionalDependencies:
-      '@types/react': 19.1.8
+      '@types/react': 19.2.15
 
-  use-sync-external-store@1.6.0(react@19.1.4):
+  use-sync-external-store@1.6.0(react@19.2.6):
     dependencies:
-      react: 19.1.4
+      react: 19.2.6
 
   util-deprecate@1.0.2: {}
 
@@ -16587,6 +16857,10 @@ snapshots:
     dependencies:
       zod: 3.25.67
 
+  zod-validation-error@4.0.2(zod@3.25.67):
+    dependencies:
+      zod: 3.25.67
+
   zod@3.24.1: {}
 
   zod@3.25.67: {}
diff --git a/src/app/admin/monitoring/components/MonitoringOverview.tsx b/src/app/admin/monitoring/components/MonitoringOverview.tsx
deleted file mode 100644
index 0bfe643e7..000000000
--- a/src/app/admin/monitoring/components/MonitoringOverview.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-'use client'
-
-import { Activity, Database, Gauge, TrendingUp } from 'lucide-react'
-import { useEffect, useState } from 'react'
-import { Card } from '@/components/ui'
-import { seoMetrics, exportMetrics } from '@/lib/monitoring/seo-metrics'
-
-export function MonitoringOverview() {
-  const [metrics, setMetrics] = useState({
-    cacheHitRate: 0,
-    avgResponseTime: 0,
-    bundleSize: 0,
-    performanceScore: 0,
-  })
-
-  useEffect(() => {
-    const updateMetrics = () => {
-      const seoData = seoMetrics.getAggregatedMetrics()
-      const exportData = exportMetrics()
-
-      setMetrics({
-        cacheHitRate: seoData.cacheHitRate,
-        avgResponseTime: seoData.avgTotalTime,
-        bundleSize: exportData.cache.totalSize,
-        performanceScore: Math.round(100 - seoData.avgTotalTime / 10), // Simple calculation for now
-      })
-    }
-
-    updateMetrics()
-    const interval = setInterval(updateMetrics, 5000)
-    return () => clearInterval(interval)
-  }, [])
-
-  return (
-    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
-      <Card className="p-6">
-        <div className="flex flex-row items-center justify-between space-y-0 pb-2">
-          <h3 className="text-sm font-medium">Cache Hit Rate</h3>
-          <Gauge className="h-4 w-4 text-muted-foreground" />
-        </div>
-        <div className="text-2xl font-bold">{metrics.cacheHitRate.toFixed(1)}%</div>
-        <p className="text-xs text-muted-foreground">Real-time data</p>
-      </Card>
-
-      <Card className="p-6">
-        <div className="flex flex-row items-center justify-between space-y-0 pb-2">
-          <h3 className="text-sm font-medium">Avg Response Time</h3>
-          <Activity className="h-4 w-4 text-muted-foreground" />
-        </div>
-        <div className="text-2xl font-bold">{metrics.avgResponseTime}ms</div>
-        <p className="text-xs text-muted-foreground">Real-time data</p>
-      </Card>
-
-      <Card className="p-6">
-        <div className="flex flex-row items-center justify-between space-y-0 pb-2">
-          <h3 className="text-sm font-medium">Cache Size</h3>
-          <Database className="h-4 w-4 text-muted-foreground" />
-        </div>
-        <div className="text-2xl font-bold">{(metrics.bundleSize / 1024).toFixed(1)} KB</div>
-        <p className="text-xs text-muted-foreground">Total cache storage</p>
-      </Card>
-
-      <Card className="p-6">
-        <div className="flex flex-row items-center justify-between space-y-0 pb-2">
-          <h3 className="text-sm font-medium">Performance Score</h3>
-          <TrendingUp className="h-4 w-4 text-muted-foreground" />
-        </div>
-        <div className="text-2xl font-bold">{metrics.performanceScore}/100</div>
-        <p className="text-xs text-muted-foreground">Based on response time</p>
-      </Card>
-    </div>
-  )
-}
diff --git a/src/app/admin/monitoring/components/SEOMetricsDashboard.tsx b/src/app/admin/monitoring/components/SEOMetricsDashboard.tsx
deleted file mode 100644
index 3d0e86120..000000000
--- a/src/app/admin/monitoring/components/SEOMetricsDashboard.tsx
+++ /dev/null
@@ -1,247 +0,0 @@
-'use client'
-
-import { BarChart as BarChartIcon, Clock, Database, TrendingUp } from 'lucide-react'
-import { useState, useEffect } from 'react'
-import { Card, Button, LineChart, DonutChart } from '@/components/ui'
-import { exportMetrics, seoMetrics } from '@/lib/monitoring/seo-metrics'
-import { cn } from '@/lib/utils'
-import { ms } from '@/utils/time'
-
-export function SEOMetricsDashboard() {
-  const [metrics, setMetrics] = useState(seoMetrics.getAggregatedMetrics())
-  const [exportData, setExportData] = useState(exportMetrics())
-  const [timeSeriesData, setTimeSeriesData] = useState<{ x: number; y: number; label: string }[]>(
-    [],
-  )
-
-  useEffect(() => {
-    const updateMetrics = () => {
-      const newMetrics = seoMetrics.getAggregatedMetrics()
-      const newExportData = exportMetrics()
-
-      setMetrics(newMetrics)
-      setExportData(newExportData)
-
-      // Update time series data for response time chart
-      setTimeSeriesData((prev) => {
-        const now = Date.now()
-        const newPoint = {
-          x: now,
-          y: newMetrics.avgTotalTime,
-          label: `${formatMs(newMetrics.avgTotalTime)} at ${new Date(now).toLocaleTimeString()}`,
-        }
-
-        // Keep only last 20 points
-        return [...prev, newPoint].slice(-20)
-      })
-    }
-
-    updateMetrics()
-
-    const interval = setInterval(updateMetrics, ms.seconds(30))
-
-    return () => {
-      clearInterval(interval)
-    }
-  }, [])
-
-  const formatMs = (ms: number) => {
-    if (ms < 1000) return `${ms}ms`
-    return `${(ms / 1000).toFixed(2)}s`
-  }
-
-  const formatBytes = (bytes: number) => {
-    if (bytes < 1024) return `${bytes} B`
-    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
-    return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
-  }
-
-  return (
-    <Card className="p-6">
-      <div className="mb-6">
-        <div className="flex items-center justify-between">
-          <div>
-            <h3 className="text-lg font-semibold">SEO Metrics</h3>
-            <p className="text-sm text-muted-foreground">Real-time performance monitoring</p>
-          </div>
-          <Button variant="outline" size="sm" onClick={() => seoMetrics.reset()}>
-            Reset Metrics
-          </Button>
-        </div>
-      </div>
-      <div className="space-y-6">
-        {/* Performance Overview */}
-        <div className="grid gap-4 md:grid-cols-2">
-          <div className="space-y-2">
-            <div className="flex items-center gap-2 text-sm text-muted-foreground">
-              <Clock className="h-4 w-4" />
-              <span>Average Response Time</span>
-            </div>
-            <p className="text-2xl font-bold">{formatMs(metrics.avgTotalTime)}</p>
-            <div className="flex gap-4 text-xs text-muted-foreground">
-              <span>Cache: {formatMs(metrics.avgCacheLookupTime)}</span>
-              <span>DB: {formatMs(metrics.avgDatabaseQueryTime)}</span>
-            </div>
-          </div>
-
-          <div className="space-y-2">
-            <div className="flex items-center gap-2 text-sm text-muted-foreground">
-              <BarChartIcon className="h-4 w-4" />
-              <span>Cache Performance</span>
-            </div>
-            <div className="flex items-center gap-4">
-              <DonutChart
-                data={[
-                  {
-                    label: 'Cache Hits',
-                    value: metrics.cacheHitRate,
-                    color: '#10b981',
-                  },
-                  {
-                    label: 'Cache Misses',
-                    value: 100 - metrics.cacheHitRate,
-                    color: '#ef4444',
-                  },
-                ]}
-                size={80}
-                strokeWidth={8}
-                showLabels={false}
-                centerText={`${metrics.cacheHitRate.toFixed(1)}%`}
-              />
-              <div className="space-y-1">
-                <div className="flex gap-4 text-xs text-muted-foreground">
-                  <span>Stale: {metrics.staleServeRate.toFixed(1)}%</span>
-                </div>
-                <div className="text-xs text-muted-foreground">Sample: {metrics.sampleSize}</div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        {/* Response Time Trend */}
-        {timeSeriesData.length > 1 && (
-          <div className="space-y-2">
-            <div className="flex items-center gap-2 text-sm text-muted-foreground">
-              <TrendingUp className="h-4 w-4" />
-              <span>Response Time Trend (Last 10 minutes)</span>
-            </div>
-            <div className="rounded-lg border p-4">
-              <LineChart
-                data={timeSeriesData}
-                width={400}
-                height={120}
-                color="#3b82f6"
-                showGrid={true}
-                yAxisLabel="Time (ms)"
-              />
-            </div>
-          </div>
-        )}
-
-        {/* Cache Details */}
-        <div className="space-y-2">
-          <div className="flex items-center gap-2 text-sm text-muted-foreground">
-            <Database className="h-4 w-4" />
-            <span>Cache Storage Details</span>
-          </div>
-          <div className="rounded-lg border p-4">
-            <div className="grid gap-2 text-sm">
-              <div className="flex justify-between items-center">
-                <span className="font-medium">Total Size</span>
-                <span>{formatBytes(exportData.cache.totalSize)}</span>
-              </div>
-              <div className="flex justify-between items-center">
-                <span className="font-medium">Total Entries</span>
-                <span>{exportData.cache.totalEntries}</span>
-              </div>
-              <div className="flex justify-between items-center">
-                <span className="font-medium">Average Entry Size</span>
-                <span>{formatBytes(exportData.cache.averageSize)}</span>
-              </div>
-              <div className="flex justify-between items-center">
-                <span className="font-medium">Oldest Entry Age</span>
-                <span>{formatMs(exportData.cache.oldestEntry)}</span>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        {/* Response Time Statistics */}
-        <div className="space-y-2">
-          <div className="flex items-center gap-2 text-sm text-muted-foreground">
-            <TrendingUp className="h-4 w-4" />
-            <span>Response Time Distribution</span>
-          </div>
-          <div className="grid gap-4 grid-cols-1 xl:grid-cols-2">
-            <div className="rounded-lg border p-4">
-              <div className="grid grid-cols-2 gap-4 text-sm">
-                <div>
-                  <div className="font-medium">Average</div>
-                  <div className="text-lg font-bold">{formatMs(metrics.avgTotalTime)}</div>
-                </div>
-                <div>
-                  <div className="font-medium">Median</div>
-                  <div className="text-lg font-bold">{formatMs(metrics.medianResponseTime)}</div>
-                </div>
-                <div>
-                  <div className="font-medium">P95</div>
-                  <div className="text-lg font-bold">{formatMs(metrics.p95ResponseTime)}</div>
-                </div>
-                <div>
-                  <div className="font-medium">Max</div>
-                  <div className="text-lg font-bold">{formatMs(metrics.maxResponseTime)}</div>
-                </div>
-              </div>
-            </div>
-            <div className="rounded-lg border p-4">
-              <div className="grid gap-2">
-                <PerformanceIndicator
-                  label="Cache Efficiency"
-                  value={metrics.cacheHitRate}
-                  threshold={80}
-                />
-                <PerformanceIndicator
-                  label="P95 Performance"
-                  value={Math.max(0, 100 - metrics.p95ResponseTime / 10)}
-                  threshold={70}
-                />
-                <PerformanceIndicator
-                  label="Stale Serve Rate"
-                  value={100 - metrics.staleServeRate}
-                  threshold={90}
-                />
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </Card>
-  )
-}
-
-function PerformanceIndicator({
-  label,
-  value,
-  threshold,
-}: {
-  label: string
-  value: number
-  threshold: number
-}) {
-  const isGood = value >= threshold
-
-  return (
-    <div className="flex items-center justify-between">
-      <span className="text-sm">{label}</span>
-      <div className="flex items-center gap-2">
-        <div className="w-32 h-2 bg-muted rounded-full overflow-hidden">
-          <div
-            className={cn('h-full transition-all', isGood ? 'bg-green-500' : 'bg-amber-500')}
-            style={{ width: `${Math.min(100, value)}%` }}
-          />
-        </div>
-        <span className="text-sm font-medium w-12 text-right">{value.toFixed(0)}%</span>
-      </div>
-    </div>
-  )
-}
diff --git a/src/app/admin/monitoring/page.tsx b/src/app/admin/monitoring/page.tsx
index 8b7bf631d..740da45fc 100644
--- a/src/app/admin/monitoring/page.tsx
+++ b/src/app/admin/monitoring/page.tsx
@@ -1,33 +1,22 @@
 import { type Metadata } from 'next'
 import { AdminPageLayout } from '@/components/admin'
-import { CacheMetrics } from '@/components/admin/CacheMetrics'
 import { BundleSizeMonitor } from './components/BundleSizeMonitor'
 import { DatabaseConnectionMonitor } from './components/DatabaseConnectionMonitor'
-import { MonitoringOverview } from './components/MonitoringOverview'
 import { PerformanceMetrics } from './components/PerformanceMetrics'
-import { SEOMetricsDashboard } from './components/SEOMetricsDashboard'
 
 export const metadata: Metadata = {
   title: 'System Monitoring - Admin Dashboard',
-  description: 'Monitor system performance, cache metrics, and bundle sizes',
+  description: 'Monitor system performance and bundle sizes',
 }
 
 export default function MonitoringDashboard() {
   return (
     <AdminPageLayout
       title="System Monitoring"
-      description="Monitor system performance, cache metrics, and bundle sizes"
+      description="Monitor system performance and bundle sizes"
     >
-      <MonitoringOverview />
-
       <DatabaseConnectionMonitor />
 
-      {/* Main Dashboard Sections */}
-      <div className="grid gap-6 lg:grid-cols-2">
-        <SEOMetricsDashboard />
-        <CacheMetrics />
-      </div>
-
       <PerformanceMetrics />
 
       <BundleSizeMonitor />
diff --git a/src/app/api/cache/metrics/route.ts b/src/app/api/cache/metrics/route.ts
deleted file mode 100644
index 563c1763e..000000000
--- a/src/app/api/cache/metrics/route.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { auth } from '@clerk/nextjs/server'
-import { NextResponse } from 'next/server'
-import { getCacheMetrics } from '@/lib/cache/seo-cache'
-import { exportMetrics, seoMetrics } from '@/lib/monitoring/seo-metrics'
-import { hasRolePermission } from '@/utils/permissions'
-import { Role } from '@orm'
-
-/**
- * API endpoint to get cache metrics
- * Only accessible by moderators and above
- */
-export async function GET() {
-  const session = await auth()
-
-  if (!session?.userId) {
-    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
-  }
-
-  // Check if user has moderator permissions or higher
-  const { sessionClaims } = session
-  const metadata = sessionClaims?.metadata as { role?: Role } | undefined
-  const userRole = metadata?.role
-
-  if (!userRole || !hasRolePermission(userRole, Role.MODERATOR)) {
-    return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
-  }
-
-  const cacheMetrics = getCacheMetrics()
-  const seoData = seoMetrics.getAggregatedMetrics()
-  const exportData = exportMetrics()
-
-  // Combine all metrics
-  const combinedMetrics = {
-    // Cache metrics from seo-cache
-    ...cacheMetrics,
-    // Add seo-metrics data
-    entries: exportData.cache.entries.length,
-    size: exportData.cache.totalSize,
-    hitRate: seoData.cacheHitRate,
-    missRate: 100 - seoData.cacheHitRate,
-    avgResponseTime: seoData.avgTotalTime,
-    timestamp: new Date().toISOString(),
-  }
-
-  return NextResponse.json(combinedMetrics)
-}
diff --git a/src/app/api/cache/warm/route.ts b/src/app/api/cache/warm/route.ts
deleted file mode 100644
index fab4ce8e6..000000000
--- a/src/app/api/cache/warm/route.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { auth } from '@clerk/nextjs/server'
-import { type NextRequest, NextResponse } from 'next/server'
-import {
-  warmAllCaches,
-  warmPopularGames,
-  warmRecentListings,
-  warmSitemapData,
-} from '@/server/cache/warming'
-import { hasRolePermission } from '@/utils/permissions'
-import { Role } from '@orm'
-
-/**
- * API endpoint to manually trigger cache warming
- * Only accessible by admins
- */
-export async function POST(request: NextRequest) {
-  const session = await auth()
-
-  if (!session?.userId) {
-    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
-  }
-
-  // Check if user has admin permissions
-  const { sessionClaims } = session
-  const metadata = sessionClaims?.metadata as { role?: Role } | undefined
-  const userRole = metadata?.role
-
-  if (!userRole || !hasRolePermission(userRole, Role.ADMIN)) {
-    return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
-  }
-
-  try {
-    const body = await request.json()
-    const { strategy = 'all' } = body
-
-    let result
-
-    switch (strategy) {
-      case 'games':
-        result = await warmPopularGames(50)
-        break
-      case 'listings':
-        result = await warmRecentListings(100)
-        break
-      case 'sitemap':
-        result = await warmSitemapData()
-        break
-      case 'all':
-      default:
-        result = await warmAllCaches()
-        break
-    }
-
-    return NextResponse.json({
-      success: true,
-      ...result,
-      strategy,
-      timestamp: new Date().toISOString(),
-    })
-  } catch (error) {
-    console.error('Cache warming error:', error)
-    // Don't expose internal error details to client
-    return NextResponse.json({ error: 'Failed to warm cache' }, { status: 500 })
-  }
-}
diff --git a/src/app/api/catalog/compatibility/route.ts b/src/app/api/catalog/compatibility/route.ts
index d7b71bc0e..77c748a96 100644
--- a/src/app/api/catalog/compatibility/route.ts
+++ b/src/app/api/catalog/compatibility/route.ts
@@ -24,10 +24,8 @@ import { getDeviceCompatibility } from '@/server/services/catalog.service'
  */
 export async function GET(request: NextRequest) {
   try {
-    // Validate API key and enforce quota limits
     const apiKey = await validateAndConsumeApiKey(request)
 
-    // Parse query parameters
     const searchParams = request.nextUrl.searchParams
 
     const deviceId = searchParams.get('deviceId') ?? undefined
@@ -37,13 +35,11 @@ export async function GET(request: NextRequest) {
     const includeEmulatorBreakdownParam = searchParams.get('includeEmulatorBreakdown')
     const minListingCountParam = searchParams.get('minListingCount')
 
-    // Parse array and boolean parameters
     const systemIds = systemIdsParam ? systemIdsParam.split(',').filter(Boolean) : undefined
     const includeEmulatorBreakdown =
       includeEmulatorBreakdownParam !== null ? includeEmulatorBreakdownParam === 'true' : undefined
     const minListingCount = minListingCountParam ? parseInt(minListingCountParam, 10) : undefined
 
-    // Validate input with Zod schema
     const input = GetDeviceCompatibilitySchema.parse({
       deviceId,
       deviceModelName,
@@ -59,15 +55,15 @@ export async function GET(request: NextRequest) {
       userId: apiKey.user.id,
     })
 
-    // Return JSON response
     return NextResponse.json(result, {
       status: 200,
       headers: {
-        'Cache-Control': 'public, s-maxage=600, stale-while-revalidate=300',
+        // This endpoint consumes API-key quota and may include role-scoped visibility.
+        // Keep intermediary caches out of the path; the service still uses its own LRU.
+        'Cache-Control': 'private, no-store',
       },
     })
   } catch (error) {
-    // Handle TRPCError (includes auth, quota, and other custom errors)
     if (error instanceof TRPCError) {
       const statusCodeMap: Record<string, number> = {
         BAD_REQUEST: 400,
@@ -89,7 +85,6 @@ export async function GET(request: NextRequest) {
       )
     }
 
-    // Handle validation errors
     if (error instanceof ZodError) {
       return NextResponse.json(
         {
@@ -103,7 +98,6 @@ export async function GET(request: NextRequest) {
       )
     }
 
-    // Handle not found errors
     if (error instanceof Error && error.message.includes('not found')) {
       return NextResponse.json(
         {
@@ -116,7 +110,6 @@ export async function GET(request: NextRequest) {
       )
     }
 
-    // Handle unexpected errors
     console.error('Catalog compatibility API error:', error)
     return NextResponse.json(
       {
diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts
index 4dc5dfb9a..d36e5b28c 100644
--- a/src/app/api/health/route.ts
+++ b/src/app/api/health/route.ts
@@ -2,8 +2,6 @@ import { NextResponse } from 'next/server'
 import { prisma } from '@/server/db'
 import type { NextRequest } from 'next/server'
 
-export const dynamic = 'force-dynamic'
-
 interface HealthResponse {
   status: 'healthy' | 'unhealthy'
   timestamp: string
@@ -112,18 +110,15 @@ interface HealthResponse {
  */
 export async function GET(_request: NextRequest) {
   try {
-    // Test database connectivity
     const dbStart = Date.now()
     await prisma.$queryRaw`SELECT 1`
     const dbLatency = Date.now() - dbStart
 
-    // Get memory usage
     const memUsage = process.memoryUsage()
     const memoryUsed = memUsage.rss
     const memoryTotal = memUsage.rss + memUsage.external
     const memoryPercentage = Math.round((memoryUsed / memoryTotal) * 100)
 
-    // Check if Clerk environment variables are set
     const authAvailable = !!(
       process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY && process.env.CLERK_SECRET_KEY
     )
@@ -145,8 +140,8 @@ export async function GET(_request: NextRequest) {
       },
       system: {
         memory: {
-          used: Math.round(memoryUsed / 1024 / 1024), // MB
-          total: Math.round(memoryTotal / 1024 / 1024), // MB
+          used: Math.round(memoryUsed / 1024 / 1024),
+          total: Math.round(memoryTotal / 1024 / 1024),
           percentage: memoryPercentage,
         },
         nodeVersion: process.version,
diff --git a/src/app/api/proxy-image/route.ts b/src/app/api/proxy-image/route.ts
index 477d7865e..9d9872d9c 100644
--- a/src/app/api/proxy-image/route.ts
+++ b/src/app/api/proxy-image/route.ts
@@ -1,11 +1,8 @@
 import { type NextRequest } from 'next/server'
 import { logger } from '@/lib/logger'
 
-export const dynamic = 'force-dynamic'
-
 /**
  * Only allow http and https URLs to prevent SSRF attacks
- * @param raw
  */
 function isAllowedUrl(raw?: string | null): URL | null {
   if (!raw) return null
@@ -48,7 +45,6 @@ export async function GET(req: NextRequest) {
       headers: {
         'Content-Type': contentType,
         'Cache-Control': cacheControl,
-        // Help common CDNs respect our intent
         'CDN-Cache-Control': cacheControl,
         'Vercel-CDN-Cache-Control': cacheControl,
       },
diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts
index 386d9546e..63d0cbf2f 100644
--- a/src/app/api/trpc/[trpc]/route.ts
+++ b/src/app/api/trpc/[trpc]/route.ts
@@ -3,9 +3,6 @@ import { type NextRequest } from 'next/server'
 import { appRouter } from '@/server/api/root'
 import { createAppRouterTRPCContext } from '@/server/api/trpc'
 
-// Ensure this route is always treated as dynamic and never cached by Next
-export const dynamic = 'force-dynamic'
-
 const handler = async (req: NextRequest) => {
   return fetchRequestHandler({
     endpoint: '/api/trpc',
diff --git a/src/app/api/upload/games/route.ts b/src/app/api/upload/games/route.ts
index a48c2ffde..50f69bd72 100644
--- a/src/app/api/upload/games/route.ts
+++ b/src/app/api/upload/games/route.ts
@@ -4,34 +4,26 @@ import { handleFileUpload } from '@/lib/upload'
 import getErrorMessage from '@/utils/getErrorMessage'
 import type { NextRequest } from 'next/server'
 
-// Set route to be dynamic to prevent caching
-export const dynamic = 'force-dynamic'
-
 export async function POST(request: NextRequest) {
   try {
-    // Check authentication
     const { userId } = await auth()
     if (!userId) {
       return NextResponse.json({ error: 'Unauthorized access' }, { status: 401 })
     }
 
-    // Get form data
     const formData = await request.formData()
 
-    // Handle upload using shared utility
     const result = await handleFileUpload(formData, userId, 'games')
 
     if (!result.success) {
       return NextResponse.json({ error: result.error }, { status: result.status })
     }
 
-    // Return success response with cache headers
     const response = NextResponse.json({
       success: true,
       imageUrl: result.imageUrl,
     })
 
-    // Set cache control headers to prevent caching
     response.headers.set('Cache-Control', 'no-store, max-age=0')
 
     return response
diff --git a/src/app/api/upload/profile/route.ts b/src/app/api/upload/profile/route.ts
index 60ea9dfcf..9a8026463 100644
--- a/src/app/api/upload/profile/route.ts
+++ b/src/app/api/upload/profile/route.ts
@@ -3,33 +3,26 @@ import { NextResponse, type NextRequest } from 'next/server'
 import { handleFileUpload } from '@/lib/upload'
 import getErrorMessage from '@/utils/getErrorMessage'
 
-export const dynamic = 'force-dynamic'
-
 export async function POST(request: NextRequest) {
   try {
-    // Check authentication
     const { userId } = await auth()
     if (!userId) {
       return NextResponse.json({ error: 'Unauthorized access' }, { status: 401 })
     }
 
-    // Get form data
     const formData = await request.formData()
 
-    // Handle upload using shared utility
     const result = await handleFileUpload(formData, userId, 'profiles')
 
     if (!result.success) {
       return NextResponse.json({ error: result.error }, { status: result.status })
     }
 
-    // Return success response
     const response = NextResponse.json({
       success: true,
       imageUrl: result.imageUrl,
     })
 
-    // Set cache control headers to prevent caching
     response.headers.set('Cache-Control', 'no-store, max-age=0')
 
     return response
diff --git a/src/app/games/new/NewGamePage.tsx b/src/app/games/new/NewGamePage.tsx
index a1f0d33ab..176b584ff 100644
--- a/src/app/games/new/NewGamePage.tsx
+++ b/src/app/games/new/NewGamePage.tsx
@@ -20,9 +20,6 @@ import { hasRolePermission } from '@/utils/permissions'
 import { Role } from '@orm'
 import NotSignedInMessage from './components/NotSignedInMessage'
 
-export const dynamic = 'force-dynamic'
-
-// Define the System type for Autocomplete
 interface SystemOption extends AutocompleteOptionBase {
   id: string
   name: string
@@ -43,10 +40,8 @@ function AddGamePage() {
   const systemsQuery = api.systems.get.useQuery()
   const createGame = api.games.create.useMutation()
 
-  // Get user role from database using TRPC
   const userQuery = api.users.me.useQuery(undefined, { enabled: !!user })
 
-  // Get the selected system's name based on systemId
   const selectedSystem = systemId
     ? systemsQuery.data?.find((system) => system.id === systemId)
     : null
@@ -60,7 +55,6 @@ function AddGamePage() {
     return () => clearTimeout(timer)
   }, [success, error])
 
-  // Redirect non-authors users to IGDB search page
   useEffect(() => {
     if (
       isLoaded &&
@@ -78,7 +72,6 @@ function AddGamePage() {
 
   const isAuthor = hasRolePermission(userQuery.data.role, Role.AUTHOR)
 
-  // If not author, show loading while redirecting
   if (!isAuthor) return <LoadingSpinner />
 
   const handleSubmit = async (ev: FormEvent) => {
@@ -95,7 +88,6 @@ function AddGamePage() {
         isErotic,
       })
 
-      // Invalidate games queries to refresh the list
       await utils.games.get.invalidate()
       await utils.games.checkExistingByTgdbIds.invalidate()
       await utils.games.checkExistingByNamesAndSystems.invalidate()
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3058ddad7..3639b1033 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -7,7 +7,8 @@ import { SpeedInsights } from '@vercel/speed-insights/next'
 import { type Metadata, type Viewport } from 'next'
 import { Inter } from 'next/font/google'
 import Script from 'next/script'
-import { type PropsWithChildren } from 'react'
+import { connection } from 'next/server'
+import { Suspense, type PropsWithChildren } from 'react'
 import { Toaster } from 'sonner'
 import CookieConsent from '@/components/CookieConsent'
 import Footer from '@/components/footer/Footer'
@@ -33,31 +34,28 @@ export const metadata: Metadata = defaultMetadata
 
 export default function RootLayout(props: PropsWithChildren) {
   return (
-    <ClerkProvider appearance={{ baseTheme: shadesOfPurple }}>
-      <html lang="en" suppressHydrationWarning>
-        <head>
-          {/* Service Worker Registration / Unregister in dev
-           * - In dev (not production), always load to proactively unregister any SW and clear caches
-           * - In prod, only load when explicitly enabled via NEXT_PUBLIC_ENABLE_SW
-           */}
-          {(env.ENABLE_SW || !env.IS_PROD) && (
-            <Script src="/sw-register.js" strategy="afterInteractive" />
-          )}
+    <html lang="en" suppressHydrationWarning>
+      <head>
+        {/* Service Worker Registration / Unregister in dev
+         * - In dev (not production), always load to proactively unregister any SW and clear caches
+         * - In prod, only load when explicitly enabled via NEXT_PUBLIC_ENABLE_SW
+         */}
+        {(env.ENABLE_SW || !env.IS_PROD) && (
+          <Script src="/sw-register.js" strategy="afterInteractive" />
+        )}
 
-          {/* Initialize dataLayer for Google Analytics */}
-          {env.IS_PROD && env.GA_ID && (
-            <Script id="google-analytics-dataLayer" strategy="beforeInteractive">
-              {`
+        {env.IS_PROD && env.GA_ID && (
+          <Script id="google-analytics-dataLayer" strategy="beforeInteractive">
+            {`
                 window.dataLayer = window.dataLayer || [];
                 function gtag(){dataLayer.push(arguments);}
                 gtag('js', new Date());
               `}
-            </Script>
-          )}
-
-          {/* Google Analytics Configuration */}
-        </head>
-        <body className={cn(inter.className, 'min-h-screen bg-background font-sans antialiased')}>
+          </Script>
+        )}
+      </head>
+      <body className={cn(inter.className, 'min-h-screen bg-background font-sans antialiased')}>
+        <ClerkBoundary>
           <Providers>
             <Toaster richColors closeButton />
             {env.IS_PROD && !env.DISABLE_COOKIE_BANNER && <CookieConsent />}
@@ -77,8 +75,28 @@ export default function RootLayout(props: PropsWithChildren) {
             </>
           )}
           {env.VERCEL_ANALYTICS_ENABLED && <Analytics />}
-        </body>
-      </html>
-    </ClerkProvider>
+        </ClerkBoundary>
+      </body>
+    </html>
   )
 }
+
+function ClerkBoundary(props: PropsWithChildren) {
+  if (process.env.NODE_ENV !== 'development') {
+    return (
+      <ClerkProvider appearance={{ baseTheme: shadesOfPurple }}>{props.children}</ClerkProvider>
+    )
+  }
+
+  return (
+    <Suspense fallback={null}>
+      <DevelopmentClerkProvider>{props.children}</DevelopmentClerkProvider>
+    </Suspense>
+  )
+}
+
+async function DevelopmentClerkProvider(props: PropsWithChildren) {
+  await connection()
+
+  return <ClerkProvider appearance={{ baseTheme: shadesOfPurple }}>{props.children}</ClerkProvider>
+}
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
index d083fb22c..14316e79a 100644
--- a/src/app/sitemap.ts
+++ b/src/app/sitemap.ts
@@ -5,66 +5,53 @@ import {
   getApprovedPcListingsForSitemap,
 } from '@/server/db/seo-queries'
 
-// Revalidate sitemap every 6 hours (6 * 60 * 60 = 21600 seconds)
-export const revalidate = 21600
-
 const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://emuready.com'
 
 export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
-  // Static pages
   const staticPages: MetadataRoute.Sitemap = [
     {
       url: appUrl,
-      lastModified: new Date(),
       changeFrequency: 'daily',
       priority: 1,
     },
     {
       url: `${appUrl}/games`,
-      lastModified: new Date(),
       changeFrequency: 'daily',
       priority: 0.9,
     },
     {
       url: `${appUrl}/listings`,
-      lastModified: new Date(),
       changeFrequency: 'hourly',
       priority: 0.9,
     },
     {
       url: `${appUrl}/pc-listings`,
-      lastModified: new Date(),
       changeFrequency: 'hourly',
       priority: 0.9,
     },
     {
       url: `${appUrl}/emulators`,
-      lastModified: new Date(),
       changeFrequency: 'weekly',
       priority: 0.8,
     },
     {
       url: `${appUrl}/devices`,
-      lastModified: new Date(),
       changeFrequency: 'weekly',
       priority: 0.8,
     },
     {
       url: `${appUrl}/terms`,
-      lastModified: new Date(),
       changeFrequency: 'monthly',
       priority: 0.3,
     },
     {
       url: `${appUrl}/privacy`,
-      lastModified: new Date(),
       changeFrequency: 'monthly',
       priority: 0.3,
     },
   ]
 
   try {
-    // Fetch games for dynamic sitemap entries
     const games = await getApprovedGamesForSitemap(1000)
 
     const gamePages: MetadataRoute.Sitemap = games
@@ -76,7 +63,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
         }))
       : []
 
-    // Fetch recent listings
     const listings = await getApprovedListingsForSitemap(500)
 
     const listingPages: MetadataRoute.Sitemap = listings
@@ -88,7 +74,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
         }))
       : []
 
-    // Fetch PC listings
     const pcListings = await getApprovedPcListingsForSitemap(500)
 
     const pcListingPages: MetadataRoute.Sitemap = pcListings
@@ -102,7 +87,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
 
     return [...staticPages, ...gamePages, ...listingPages, ...pcListingPages]
   } catch (error) {
-    // Return only static pages if there's an error fetching dynamic content
     console.error('Error generating sitemap:', error)
     return staticPages
   }
diff --git a/src/components/admin/CacheMetrics.tsx b/src/components/admin/CacheMetrics.tsx
deleted file mode 100644
index 10c4544e0..000000000
--- a/src/components/admin/CacheMetrics.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-'use client'
-
-import { Loader2, Activity, TrendingUp, Database, Timer } from 'lucide-react'
-import { Divider, Card, DonutChart, BarChart } from '@/components/ui'
-import { POLLING_INTERVALS } from '@/data/constants'
-import { api } from '@/lib/api'
-
-export function CacheMetrics() {
-  const {
-    data: metrics,
-    isLoading,
-    error,
-  } = api.cache.getMetrics.useQuery(undefined, {
-    refetchInterval: POLLING_INTERVALS.DEFAULT,
-    refetchOnWindowFocus: false,
-  })
-
-  if (isLoading) {
-    return (
-      <Card className="flex items-center justify-center py-8">
-        <Loader2 className="h-6 w-6 animate-spin" />
-      </Card>
-    )
-  }
-
-  if (error || !metrics) {
-    return (
-      <Card className="py-8">
-        <p className="text-center text-muted-foreground">
-          {error?.message || 'Unable to load cache metrics'}
-        </p>
-      </Card>
-    )
-  }
-
-  const cacheMetrics = metrics.cache
-  const cacheUsagePercent = (cacheMetrics.size / cacheMetrics.maxSize) * 100
-  const totalRequests = cacheMetrics.hits + cacheMetrics.misses
-
-  return (
-    <Card className="p-6">
-      <div className="space-y-6">
-        <div>
-          <h3 className="text-lg font-semibold flex items-center gap-2">
-            <Activity className="h-5 w-5" />
-            SEO Cache Performance
-          </h3>
-          <p className="text-sm text-muted-foreground mt-1">
-            Real-time cache metrics and performance indicators
-          </p>
-        </div>
-        <div className="grid gap-4 grid-cols-2 xl:grid-cols-4">
-          <div className="space-y-2">
-            <div className="flex items-center gap-2 text-sm text-muted-foreground">
-              <TrendingUp className="h-4 w-4" />
-              Hit Rate
-            </div>
-            <div className="text-2xl font-bold">{cacheMetrics.hitRate}</div>
-            <p className="text-xs text-muted-foreground">
-              {totalRequests.toLocaleString()} total requests
-            </p>
-          </div>
-
-          <div className="space-y-2">
-            <div className="flex items-center gap-2 text-sm text-muted-foreground">
-              <Database className="h-4 w-4" />
-              Cache Size
-            </div>
-            <div className="text-2xl font-bold">
-              {cacheMetrics.size} / {cacheMetrics.maxSize}
-            </div>
-            <div className="w-full bg-gray-200 rounded-full h-2 mt-2">
-              <div
-                className="bg-blue-500 h-2 rounded-full transition-all duration-300"
-                style={{ width: `${Math.min(cacheUsagePercent, 100)}%` }}
-              />
-            </div>
-          </div>
-        </div>
-
-        <Divider />
-
-        <div className="grid gap-4 grid-cols-2 xl:grid-cols-4">
-          <div className="space-y-2">
-            <div className="flex items-center gap-2 text-sm text-muted-foreground">
-              <Timer className="h-4 w-4" />
-              Stale Hits
-            </div>
-            <div className="text-2xl font-bold">{cacheMetrics.staleHits.toLocaleString()}</div>
-            <p className="text-xs text-muted-foreground">Served while revalidating</p>
-          </div>
-
-          <div className="space-y-2">
-            <p className="text-sm text-muted-foreground">Cache Performance</p>
-            <DonutChart
-              data={[
-                { label: 'Hits', value: cacheMetrics.hits, color: '#10b981' },
-                {
-                  label: 'Misses',
-                  value: cacheMetrics.misses,
-                  color: '#ef4444',
-                },
-                {
-                  label: 'Stale Hits',
-                  value: cacheMetrics.staleHits,
-                  color: '#f59e0b',
-                },
-              ]}
-              size={120}
-              strokeWidth={12}
-              showLabels={false}
-            />
-          </div>
-        </div>
-
-        {/* Cache Distribution Chart */}
-        <div className="space-y-2">
-          <p className="text-sm text-muted-foreground">Cache Request Distribution</p>
-          <div className="rounded-lg border p-4">
-            <BarChart
-              data={[
-                {
-                  label: 'Cache Hits',
-                  value: cacheMetrics.hits,
-                  color: '#10b981',
-                },
-                {
-                  label: 'Cache Misses',
-                  value: cacheMetrics.misses,
-                  color: '#ef4444',
-                },
-                {
-                  label: 'Stale Hits',
-                  value: cacheMetrics.staleHits,
-                  color: '#f59e0b',
-                },
-              ]}
-              width={350}
-              height={200}
-              showGrid={true}
-              showValues={true}
-            />
-          </div>
-        </div>
-
-        <div className="text-xs text-muted-foreground">
-          Last updated: {new Date(metrics.timestamp).toLocaleTimeString()}
-        </div>
-      </div>
-    </Card>
-  )
-}
diff --git a/src/lib/cache/seo-cache.ts b/src/lib/cache/seo-cache.ts
deleted file mode 100644
index 4efd8e61f..000000000
--- a/src/lib/cache/seo-cache.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import { LRUCache } from 'lru-cache'
-import { ms } from '@/utils/time'
-
-/**
- * caching solution for SEO data
- * Uses in-memory LRU cache with Redis support when available
- * TODO: implement Redis support for distributed caching
- */
-
-interface CacheOptions {
-  ttl?: number // Time to live in milliseconds
-  staleWhileRevalidate?: number // Serve stale content while revalidating
-}
-
-interface CachedItem<T> {
-  data: T
-  timestamp: number
-  ttl: number
-  staleWhileRevalidate: number
-}
-
-// In-memory LRU cache configuration
-const DEFAULT_TTL = ms.hours(1)
-const DEFAULT_STALE_WHILE_REVALIDATE = ms.days(1)
-const MAX_CACHE_SIZE = 500 // Maximum number of items
-const MAX_CACHE_AGE = ms.days(1)
-
-// Initialize LRU cache
-export const cache = new LRUCache<string, CachedItem<unknown>>({
-  max: MAX_CACHE_SIZE,
-  ttl: MAX_CACHE_AGE,
-  // Update age on get to keep hot items in cache
-  updateAgeOnGet: true,
-  // Track performance metrics
-  fetchMethod: async (key: string) => {
-    console.log(`[SEO Cache] Cache miss for key: ${key}`)
-    return undefined
-  },
-})
-
-// Performance metrics
-let cacheHits = 0
-let cacheMisses = 0
-let cacheStaleHits = 0
-let deduplicatedRequests = 0
-
-// Request deduplication - tracks in-flight requests
-const inFlightRequests = new Map<string, Promise<unknown>>()
-
-export function getCacheMetrics() {
-  const hitRate = cacheHits / (cacheHits + cacheMisses) || 0
-  return {
-    hits: cacheHits,
-    misses: cacheMisses,
-    staleHits: cacheStaleHits,
-    deduplicatedRequests,
-    hitRate: `${(hitRate * 100).toFixed(2)}%`,
-    size: cache.size,
-    maxSize: MAX_CACHE_SIZE,
-    inFlightRequests: inFlightRequests.size,
-  }
-}
-
-/**
- * Get item from cache with stale-while-revalidate support
- */
-export async function getFromCache<T>(
-  key: string,
-  fetchFn: () => Promise<T | null>,
-  options: CacheOptions = {},
-): Promise<T | null> {
-  const ttl = options.ttl ?? DEFAULT_TTL
-  const staleWhileRevalidate = options.staleWhileRevalidate ?? DEFAULT_STALE_WHILE_REVALIDATE
-
-  try {
-    const cached = cache.get(key)
-    const now = Date.now()
-
-    if (cached) {
-      const age = now - cached.timestamp
-      const isStale = age > cached.ttl
-      const isExpired = age > cached.ttl + cached.staleWhileRevalidate
-
-      if (!isExpired) {
-        if (isStale) {
-          // Serve stale content and revalidate in background
-          cacheStaleHits++
-          console.log(`[SEO Cache] Serving stale content for: ${key}`)
-
-          // Revalidate in background without blocking
-          revalidateInBackground(key, fetchFn, ttl, staleWhileRevalidate)
-        } else {
-          // Fresh cache hit
-          cacheHits++
-        }
-        return cached.data as T
-      }
-    }
-
-    // Check if there's already an in-flight request for this key
-    const existingRequest = inFlightRequests.get(key)
-    if (existingRequest) {
-      console.log(`[SEO Cache] Deduplicating request for key: ${key}`)
-      deduplicatedRequests++
-      return existingRequest as Promise<T | null>
-    }
-
-    // Cache miss or expired - create new request
-    cacheMisses++
-
-    // Create promise and store it for deduplication
-    const requestPromise = (async () => {
-      try {
-        const data = await fetchFn()
-
-        if (data !== null) {
-          setCacheItem(key, data, ttl, staleWhileRevalidate)
-        }
-
-        return data
-      } finally {
-        // Remove from in-flight map when done
-        inFlightRequests.delete(key)
-      }
-    })()
-
-    // Store the promise for deduplication
-    inFlightRequests.set(key, requestPromise)
-
-    return requestPromise
-  } catch (error) {
-    console.error(`[SEO Cache] Error for key ${key}:`, error)
-    // On error, try to return stale data if available
-    const cached = cache.get(key)
-    return (cached?.data as T) ?? null
-  }
-}
-
-/**
- * Set item in cache
- */
-function setCacheItem<T>(key: string, data: T, ttl: number, staleWhileRevalidate: number): void {
-  cache.set(key, {
-    data,
-    timestamp: Date.now(),
-    ttl,
-    staleWhileRevalidate,
-  })
-}
-
-/**
- * Revalidate cache in background
- */
-async function revalidateInBackground<T>(
-  key: string,
-  fetchFn: () => Promise<T | null>,
-  ttl: number,
-  staleWhileRevalidate: number,
-): Promise<void> {
-  // Check if there's already an in-flight request for this key
-  if (inFlightRequests.has(key)) {
-    console.log(
-      `[SEO Cache] Skipping background revalidation, request already in-flight for: ${key}`,
-    )
-    return
-  }
-
-  // Use setImmediate to ensure this doesn't block
-  setImmediate(async () => {
-    // Double-check in case another request started
-    if (inFlightRequests.has(key)) {
-      return
-    }
-
-    const requestPromise = (async () => {
-      try {
-        console.log(`[SEO Cache] Revalidating in background: ${key}`)
-        const data = await fetchFn()
-        if (data !== null) {
-          setCacheItem(key, data, ttl, staleWhileRevalidate)
-        }
-      } catch (error) {
-        console.error(`[SEO Cache] Background revalidation failed for ${key}:`, error)
-      } finally {
-        inFlightRequests.delete(key)
-      }
-    })()
-
-    // Store the promise for deduplication
-    inFlightRequests.set(key, requestPromise)
-  })
-}
-
-/**
- * Invalidate cache entries by pattern
- */
-export function invalidateCache(pattern: string | RegExp): number {
-  let invalidated = 0
-
-  for (const key of cache.keys()) {
-    if (typeof pattern === 'string' ? key.includes(pattern) : pattern.test(key)) {
-      cache.delete(key)
-      invalidated++
-    }
-  }
-
-  console.log(`[SEO Cache] Invalidated ${invalidated} entries matching pattern`)
-  return invalidated
-}
-
-/**
- * Clear entire cache
- */
-export function clearCache(): void {
-  cache.clear()
-  console.log('[SEO Cache] Cache cleared')
-}
-
-/**
- * Warm cache with popular content
- */
-export async function warmCache<T>(
-  items: { key: string; fetchFn: () => Promise<T | null> }[],
-  options: CacheOptions = {},
-): Promise<void> {
-  console.log(`[SEO Cache] Warming cache with ${items.length} items`)
-
-  // Process in batches to avoid overloading
-  const batchSize = 10
-  for (let i = 0; i < items.length; i += batchSize) {
-    const batch = items.slice(i, i + batchSize)
-    await Promise.all(batch.map(({ key, fetchFn }) => getFromCache(key, fetchFn, options)))
-  }
-}
diff --git a/src/lib/captcha/config.ts b/src/lib/captcha/config.ts
index 039ad5081..90c833031 100644
--- a/src/lib/captcha/config.ts
+++ b/src/lib/captcha/config.ts
@@ -1,10 +1,7 @@
 export const RECAPTCHA_CONFIG = {
   siteKey: process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? '',
   secretKey: process.env.RECAPTCHA_SECRET_KEY ?? '',
-  // Score threshold (0.0 = likely bot, 1.0 = likely human)
-  // reCAPTCHA v3 recommends 0.5 as a starting point
   scoreThreshold: 0.5,
-  // Actions for different parts of the application
   actions: {
     CREATE_LISTING: 'create_listing',
     VOTE: 'vote',
@@ -14,23 +11,32 @@ export const RECAPTCHA_CONFIG = {
   },
 } as const
 
-// Validate configuration
 if (
   process.env.NODE_ENV !== 'test' &&
   typeof window === 'undefined' &&
+  !isCaptchaDisabled() &&
   !RECAPTCHA_CONFIG.secretKey
 ) {
   console.warn('RECAPTCHA_SECRET_KEY is not set. CAPTCHA verification will be disabled.')
 }
 
-if (process.env.NODE_ENV !== 'test' && typeof window !== 'undefined' && !RECAPTCHA_CONFIG.siteKey) {
+if (
+  process.env.NODE_ENV !== 'test' &&
+  typeof window !== 'undefined' &&
+  !isCaptchaDisabled() &&
+  !RECAPTCHA_CONFIG.siteKey
+) {
   console.warn('NEXT_PUBLIC_RECAPTCHA_SITE_KEY is not set. CAPTCHA will be disabled.')
 }
 
+export function isCaptchaDisabled(): boolean {
+  return process.env.NEXT_PUBLIC_DISABLE_RECAPTCHA === 'true'
+}
+
 export function isCaptchaClientEnabled(): boolean {
-  return Boolean(RECAPTCHA_CONFIG.siteKey)
+  return !isCaptchaDisabled() && Boolean(RECAPTCHA_CONFIG.siteKey)
 }
 
 export function isCaptchaVerificationEnabled(): boolean {
-  return Boolean(RECAPTCHA_CONFIG.secretKey)
+  return !isCaptchaDisabled() && Boolean(RECAPTCHA_CONFIG.secretKey)
 }
diff --git a/src/lib/captcha/verify.test.ts b/src/lib/captcha/verify.test.ts
index 8b1d67af4..5fbf869e5 100644
--- a/src/lib/captcha/verify.test.ts
+++ b/src/lib/captcha/verify.test.ts
@@ -18,6 +18,18 @@ describe('captcha config', () => {
     expect(isCaptchaClientEnabled()).toBe(true)
     expect(isCaptchaVerificationEnabled()).toBe(false)
   })
+
+  it('uses the same explicit disable flag for client and server captcha', async () => {
+    vi.stubEnv('NEXT_PUBLIC_DISABLE_RECAPTCHA', 'true')
+    vi.stubEnv('NEXT_PUBLIC_RECAPTCHA_SITE_KEY', 'site-key')
+    vi.stubEnv('RECAPTCHA_SECRET_KEY', 'secret-key')
+    vi.resetModules()
+
+    const { isCaptchaClientEnabled, isCaptchaVerificationEnabled } = await import('./config')
+
+    expect(isCaptchaClientEnabled()).toBe(false)
+    expect(isCaptchaVerificationEnabled()).toBe(false)
+  })
 })
 
 describe('verifyRecaptcha', () => {
diff --git a/src/lib/monitoring/seo-metrics.ts b/src/lib/monitoring/seo-metrics.ts
deleted file mode 100644
index 3278430d5..000000000
--- a/src/lib/monitoring/seo-metrics.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-import { cache } from '@/lib/cache/seo-cache'
-
-/**
- * SEO Performance Monitoring
- * Integrates with existing monitoring infrastructure
- */
-
-interface PerformanceMetrics {
-  cacheLookupTime: number
-  databaseQueryTime: number
-  totalTime: number
-  cacheHit: boolean
-  staleServed: boolean
-}
-
-// Type guard for cached items
-interface CachedItem<T> {
-  data: T
-  timestamp: number
-  ttl: number
-  staleWhileRevalidate: number
-}
-
-function isCachedItem(value: unknown): value is CachedItem<unknown> {
-  return (
-    value !== null &&
-    typeof value === 'object' &&
-    'data' in value &&
-    'timestamp' in value &&
-    'ttl' in value &&
-    'staleWhileRevalidate' in value &&
-    typeof (value as Record<string, unknown>).timestamp === 'number' &&
-    typeof (value as Record<string, unknown>).ttl === 'number'
-  )
-}
-
-class SEOMetricsCollector {
-  private metrics: PerformanceMetrics[] = []
-  private maxMetrics = 1000 // Keep last 1000 operations
-
-  recordMetric(metric: PerformanceMetrics) {
-    this.metrics.push(metric)
-
-    // Keep only recent metrics
-    if (this.metrics.length > this.maxMetrics) {
-      this.metrics = this.metrics.slice(-this.maxMetrics)
-    }
-
-    // Log slow queries
-    if (metric.totalTime > 100) {
-      console.warn('[SEO Performance] Slow operation detected:', {
-        totalTime: `${metric.totalTime}ms`,
-        cacheHit: metric.cacheHit,
-        dbTime: `${metric.databaseQueryTime}ms`,
-      })
-    }
-  }
-
-  getAggregatedMetrics() {
-    if (this.metrics.length === 0) {
-      return {
-        avgCacheLookupTime: 0,
-        avgDatabaseQueryTime: 0,
-        avgTotalTime: 0,
-        cacheHitRate: 0,
-        staleServeRate: 0,
-        sampleSize: 0,
-        p95ResponseTime: 0,
-        maxResponseTime: 0,
-        minResponseTime: 0,
-        medianResponseTime: 0,
-      }
-    }
-
-    const cacheHits = this.metrics.filter((m) => m.cacheHit).length
-    const staleServed = this.metrics.filter((m) => m.staleServed).length
-
-    const sumMetrics = this.metrics.reduce(
-      (acc, m) => ({
-        cacheLookup: acc.cacheLookup + m.cacheLookupTime,
-        dbQuery: acc.dbQuery + m.databaseQueryTime,
-        total: acc.total + m.totalTime,
-      }),
-      { cacheLookup: 0, dbQuery: 0, total: 0 },
-    )
-
-    // Calculate percentiles and min/max
-    const sortedTimes = this.metrics.map((m) => m.totalTime).sort((a, b) => a - b)
-
-    const p95Index = Math.floor(sortedTimes.length * 0.95)
-    const medianIndex = Math.floor(sortedTimes.length * 0.5)
-
-    return {
-      avgCacheLookupTime: Math.round(sumMetrics.cacheLookup / this.metrics.length),
-      avgDatabaseQueryTime: Math.round(sumMetrics.dbQuery / this.metrics.length),
-      avgTotalTime: Math.round(sumMetrics.total / this.metrics.length),
-      cacheHitRate: (cacheHits / this.metrics.length) * 100,
-      staleServeRate: (staleServed / this.metrics.length) * 100,
-      sampleSize: this.metrics.length,
-      p95ResponseTime: Math.round(sortedTimes[p95Index] || 0),
-      maxResponseTime: Math.round(sortedTimes[sortedTimes.length - 1] || 0),
-      minResponseTime: Math.round(sortedTimes[0] || 0),
-      medianResponseTime: Math.round(sortedTimes[medianIndex] || 0),
-    }
-  }
-
-  reset() {
-    this.metrics = []
-  }
-}
-
-export const seoMetrics = new SEOMetricsCollector()
-
-/**
- * Performance monitoring wrapper
- */
-export async function withMetrics<T>(
-  operation: string,
-  fn: () => Promise<T>,
-  options: { cacheKey?: string } = {},
-): Promise<T> {
-  const startTime = performance.now()
-  let cacheHit = false
-  let staleServed = false
-  let dbQueryTime = 0
-
-  try {
-    // Check if this is a cache operation
-    if (options.cacheKey) {
-      const cachedValue = cache.get(options.cacheKey)
-      if (isCachedItem(cachedValue)) {
-        cacheHit = true
-        const age = Date.now() - cachedValue.timestamp
-        staleServed = age > cachedValue.ttl
-      }
-    }
-
-    const dbStart = performance.now()
-    const result = await fn()
-    dbQueryTime = performance.now() - dbStart
-
-    return result
-  } finally {
-    const totalTime = performance.now() - startTime
-
-    seoMetrics.recordMetric({
-      cacheLookupTime: cacheHit ? totalTime - dbQueryTime : 0,
-      databaseQueryTime: cacheHit ? 0 : dbQueryTime,
-      totalTime,
-      cacheHit,
-      staleServed,
-    })
-  }
-}
-
-/**
- * Export metrics for external monitoring systems
- */
-export function exportMetrics() {
-  const cacheMetrics = cache.dump().map(([key, value]) => {
-    // Safely access the cached item properties
-    let age = 0
-    if (isCachedItem(value.value)) {
-      age = Date.now() - value.value.timestamp
-    }
-
-    return {
-      key,
-      size: JSON.stringify(value).length,
-      age,
-    }
-  })
-
-  // Handle empty cache scenario
-  const totalSize = cacheMetrics.reduce((sum, m) => sum + m.size, 0)
-  const oldestEntry = cacheMetrics.length > 0 ? Math.max(...cacheMetrics.map((m) => m.age)) : 0
-
-  return {
-    cache: {
-      entries: cacheMetrics,
-      totalSize,
-      oldestEntry,
-      averageSize: cacheMetrics.length > 0 ? Math.round(totalSize / cacheMetrics.length) : 0,
-      totalEntries: cacheMetrics.length,
-    },
-    performance: seoMetrics.getAggregatedMetrics(),
-    timestamp: new Date().toISOString(),
-  }
-}
diff --git a/src/middleware.ts b/src/proxy.ts
similarity index 71%
rename from src/middleware.ts
rename to src/proxy.ts
index 66faf56bc..0bf3eb786 100644
--- a/src/middleware.ts
+++ b/src/proxy.ts
@@ -1,17 +1,17 @@
 import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
 import { NextResponse } from 'next/server'
-import { getAllowedOrigins, isAllowedRequestOrigin } from '@/lib/cors'
+import { getAllowedOrigins, getOriginFromUrl, isAllowedRequestOrigin } from '@/lib/cors'
 import { ms } from '@/utils/time'
 import type { NextRequest, NextFetchEvent } from 'next/server'
 
-// In-memory rate limiting with automatic cleanup
-// TODO: For distributed deployments, consider Redis or database-backed rate limiting
+// Process-local rate limiting only; counts are not shared across server instances.
 const rateLimitMap = new Map<string, { count: number; resetTime: number }>()
 
-// Rate limiting configuration
-const RATE_LIMIT_REQUESTS = process.env.NODE_ENV === 'test' ? 10000 : 100 // Much higher limit for tests
+const RATE_LIMIT_REQUESTS = process.env.NODE_ENV === 'test' ? 10000 : 100
 const RATE_LIMIT_WINDOW = ms.minutes(3)
+const RATE_LIMIT_CLEANUP_SAMPLE_RATE = 0.01
 const DEV_NO_STORE_HOSTS = new Set(['dev.emuready.com'])
+const LOCAL_RATE_LIMIT_BYPASS_IDENTIFIERS = new Set(['::1', '127.0.0.1', 'localhost', 'unknown'])
 
 function applyDevNoStoreHeader<T extends NextResponse | Response>(
   response: T,
@@ -29,39 +29,29 @@ function applyDevNoStoreHeader<T extends NextResponse | Response>(
 }
 
 function getClientIdentifier(req: NextRequest): string {
-  // Use IP from forwarded headers or fallback
   const forwarded = req.headers.get('x-forwarded-for')
   const realIp = req.headers.get('x-real-ip')
-  const cfConnectingIp = req.headers.get('cf-connecting-ip') // Cloudflare
+  const cfConnectingIp = req.headers.get('cf-connecting-ip')
 
   if (forwarded) return forwarded.split(',')[0].trim()
 
   return realIp || cfConnectingIp || 'unknown'
 }
 
-function checkRateLimit(identifier: string): boolean {
-  // Skip rate limiting if explicitly disabled (for E2E tests)
-  if (process.env.DISABLE_RATE_LIMIT === 'true') {
-    return true
-  }
+function shouldBypassRateLimit(identifier: string): boolean {
+  if (process.env.DISABLE_RATE_LIMIT === 'true') return true
 
-  // Skip rate limiting for localhost in test/development environments
-  if (
-    (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') &&
-    (identifier === '::1' ||
-      identifier === '127.0.0.1' ||
-      identifier === 'localhost' ||
-      identifier === 'unknown')
-  ) {
-    return true
-  }
+  if (process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'development') return false
+
+  return LOCAL_RATE_LIMIT_BYPASS_IDENTIFIERS.has(identifier)
+}
+
+function checkRateLimit(identifier: string): boolean {
+  if (shouldBypassRateLimit(identifier)) return true
 
   const now = Date.now()
 
-  // Clean up expired entries to prevent memory leaks
-  // Only clean every 100-ish requests to avoid performance impact
-  if (Math.random() < 0.01) {
-    // 1% chance per request
+  if (Math.random() < RATE_LIMIT_CLEANUP_SAMPLE_RATE) {
     for (const [key, value] of rateLimitMap.entries()) {
       if (now > value.resetTime) rateLimitMap.delete(key)
     }
@@ -70,14 +60,13 @@ function checkRateLimit(identifier: string): boolean {
   const userLimit = rateLimitMap.get(identifier)
 
   if (!userLimit || now > userLimit.resetTime) {
-    // Reset or create new limit window
     rateLimitMap.set(identifier, {
       count: 1,
       resetTime: now + RATE_LIMIT_WINDOW,
     })
     return true
   }
-  // Slow down boy, rate limit exceeded
+
   if (userLimit.count >= RATE_LIMIT_REQUESTS) return false
 
   userLimit.count++
@@ -90,6 +79,10 @@ function isValidOrigin(req: NextRequest): boolean {
 
   const allowedOrigins = getAllowedOrigins()
 
+  if (isSameOriginSource(req, origin) || isSameOriginSource(req, referer)) {
+    return true
+  }
+
   if (isAllowedRequestOrigin({ allowedOrigins, source: origin })) {
     return true
   }
@@ -98,7 +91,6 @@ function isValidOrigin(req: NextRequest): boolean {
     return true
   }
 
-  // Allow requests with no origin/referer (server-side, mobile apps, etc.) but check for the API key
   if (!origin && !referer) {
     const apiKey = req.headers.get('x-api-key')
     const internalApiKey = process.env.INTERNAL_API_KEY
@@ -108,28 +100,28 @@ function isValidOrigin(req: NextRequest): boolean {
   return false
 }
 
+function isSameOriginSource(req: NextRequest, source: string | null): boolean {
+  const sourceOrigin = getOriginFromUrl(source ?? '')
+  if (!sourceOrigin) return false
+
+  const host = req.headers.get('host')
+  if (!host) return false
+
+  return sourceOrigin === `${req.nextUrl.protocol}//${host}`
+}
+
 function protectTRPCAPI(req: NextRequest): NextResponse | null {
   const pathname = req.nextUrl.pathname
 
-  // Skip protection for mobile routes - they have their own CORS handling
   if (pathname.startsWith('/api/mobile/trpc/')) return null
 
-  // Only protect TRPC API routes
   if (!pathname.startsWith('/api/trpc/')) return null
 
-  // Skip protection for mobile procedures in the main tRPC router
   if (pathname.startsWith('/api/trpc/mobile.')) return null
 
   const clientId = getClientIdentifier(req)
 
-  // Check rate limit (skip if disabled or in test/dev environment for localhost)
-  const skipRateLimit =
-    process.env.DISABLE_RATE_LIMIT === 'true' ||
-    ((process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') &&
-      (clientId === '::1' ||
-        clientId === '127.0.0.1' ||
-        clientId === 'localhost' ||
-        clientId === 'unknown'))
+  const skipRateLimit = shouldBypassRateLimit(clientId)
 
   if (!skipRateLimit && !checkRateLimit(clientId)) {
     console.warn(`Rate limit exceeded for client: ${clientId}, path: ${pathname}`)
@@ -149,7 +141,6 @@ function protectTRPCAPI(req: NextRequest): NextResponse | null {
     )
   }
 
-  // Check origin for public API routes (skip in test environment)
   if (process.env.NODE_ENV !== 'test' && !isValidOrigin(req)) {
     console.warn(
       `Invalid origin for client: ${clientId}, origin: ${req.headers.get('origin')}, referer: ${req.headers.get('referer')}, path: ${pathname}`,
@@ -169,7 +160,6 @@ function protectTRPCAPI(req: NextRequest): NextResponse | null {
     )
   }
 
-  // Add security headers to successful requests
   const response = applyDevNoStoreHeader(NextResponse.next(), req)
   response.headers.set('X-Content-Type-Options', 'nosniff')
   response.headers.set('X-Frame-Options', 'DENY')
@@ -190,7 +180,7 @@ const handleClerkAuth = clerkMiddleware(async (auth, req) => {
   return
 })
 
-export default async function middleware(req: NextRequest, evt: NextFetchEvent) {
+export async function proxy(req: NextRequest, evt: NextFetchEvent) {
   const pathname = req.nextUrl.pathname
 
   if (pathname.startsWith('/api/mobile/')) {
@@ -212,9 +202,7 @@ export default async function middleware(req: NextRequest, evt: NextFetchEvent)
 
 export const config = {
   matcher: [
-    // Skip Next.js internals and all static files, unless found in search params
     '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
-    // Always run for API routes
     '/(api|trpc)(.*)',
   ],
 }
diff --git a/src/schemas/cache.ts b/src/schemas/cache.ts
deleted file mode 100644
index 102a37a48..000000000
--- a/src/schemas/cache.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { z } from 'zod'
-
-export const GetCacheEntrySchema = z.object({
-  key: z.string(),
-})
-
-export const DeleteCacheEntrySchema = z.object({
-  key: z.string(),
-})
diff --git a/src/server/api/root.ts b/src/server/api/root.ts
index 09badaaed..923774dbe 100644
--- a/src/server/api/root.ts
+++ b/src/server/api/root.ts
@@ -8,7 +8,6 @@ import { apiKeysRouter } from './routers/apiKeys'
 import { auditLogsRouter } from './routers/auditLogs'
 import { badgesRouter } from './routers/badges'
 import { bookmarksRouter } from './routers/bookmarks'
-import { cacheRouter } from './routers/cache'
 import { cpusRouter } from './routers/cpus'
 import { customFieldCategoryRouter } from './routers/customFieldCategories'
 import { customFieldDefinitionRouter } from './routers/customFieldDefinitions'
@@ -63,7 +62,6 @@ export const appRouter = createTRPCRouter({
   userPreferences: userPreferencesRouter,
   userBans: userBansRouter,
   badges: badgesRouter,
-  cache: cacheRouter,
   notifications: notificationsRouter,
   customFieldCategories: customFieldCategoryRouter,
   customFieldDefinitions: customFieldDefinitionRouter,
diff --git a/src/server/api/routers/cache.ts b/src/server/api/routers/cache.ts
deleted file mode 100644
index 76042885d..000000000
--- a/src/server/api/routers/cache.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-import { cache, getCacheMetrics } from '@/lib/cache/seo-cache'
-import { AppError } from '@/lib/errors'
-import { seoMetrics, exportMetrics } from '@/lib/monitoring/seo-metrics'
-import { GetCacheEntrySchema, DeleteCacheEntrySchema } from '@/schemas/cache'
-import { createTRPCRouter, superAdminProcedure } from '@/server/api/trpc'
-import {
-  createQueryCacheKey,
-  QueryPerformanceMonitor,
-  suggestIndexes,
-  analyzeQueryComplexity,
-} from '@/server/utils/query-performance'
-import { ApprovalStatus, ReportStatus } from '@orm/client'
-
-export const cacheRouter = createTRPCRouter({
-  /**
-   * Get cache metrics and statistics
-   */
-  getMetrics: superAdminProcedure.query(async () => {
-    const cacheMetrics = getCacheMetrics()
-    const seoData = seoMetrics.getAggregatedMetrics()
-    const exportData = exportMetrics()
-    const queryMetrics = QueryPerformanceMonitor.getMetrics()
-
-    return {
-      cache: cacheMetrics,
-      seo: seoData,
-      export: exportData,
-      query: queryMetrics,
-      timestamp: new Date().toISOString(),
-    }
-  }),
-
-  /**
-   * Clear all cache entries
-   */
-  clear: superAdminProcedure.mutation(async () => {
-    try {
-      cache.clear()
-      seoMetrics.reset()
-
-      return {
-        success: true,
-        message: 'Cache cleared successfully',
-        timestamp: new Date().toISOString(),
-      }
-    } catch {
-      AppError.internalError('Failed to clear cache')
-    }
-  }),
-
-  /**
-   * Warm the cache with critical data
-   */
-  warm: superAdminProcedure.mutation(async ({ ctx }) => {
-    try {
-      // Warm critical paths
-      const warmedPaths = []
-
-      // Fetch popular games for cache warming
-      const popularGames = await ctx.prisma.game.findMany({
-        take: 10,
-        orderBy: { listings: { _count: 'desc' } },
-        select: { id: true, title: true },
-      })
-
-      // Warm game metadata
-      for (const game of popularGames) {
-        const cacheKey = createQueryCacheKey('seo', 'game', { id: game.id })
-        const cacheData = {
-          title: game.title,
-          description: `${game.title} compatibility information`,
-        }
-        cache.set(cacheKey, {
-          data: cacheData,
-          timestamp: Date.now(),
-          ttl: 3600 * 1000, // Convert to milliseconds
-          staleWhileRevalidate: 7200 * 1000,
-        })
-        warmedPaths.push(cacheKey)
-      }
-
-      // Fetch popular listings
-      const popularListings = await ctx.prisma.listing.findMany({
-        take: 10,
-        where: { status: ApprovalStatus.APPROVED },
-        orderBy: { votes: { _count: 'desc' } },
-        include: {
-          game: { select: { title: true } },
-          device: { include: { brand: true } },
-          emulator: true,
-        },
-      })
-
-      // Warm listing metadata
-      for (const listing of popularListings) {
-        const cacheKey = createQueryCacheKey('seo', 'listing', {
-          id: listing.id,
-        })
-        const cacheData = {
-          title: `${listing.game.title} on ${listing.device.brand.name} ${listing.device.modelName}`,
-          description: `${listing.game.title} running on ${listing.emulator.name}`,
-        }
-        cache.set(cacheKey, {
-          data: cacheData,
-          timestamp: Date.now(),
-          ttl: 3600 * 1000, // Convert to milliseconds
-          staleWhileRevalidate: 7200 * 1000,
-        })
-        warmedPaths.push(cacheKey)
-      }
-
-      return {
-        success: true,
-        message: `Cache warmed with ${warmedPaths.length} entries`,
-        warmedPaths,
-        timestamp: new Date().toISOString(),
-      }
-    } catch (error) {
-      console.error('Cache warming error:', error)
-      AppError.internalError('Failed to warm cache')
-    }
-  }),
-
-  /**
-   * Get specific cache entry
-   */
-  get: superAdminProcedure.input(GetCacheEntrySchema).query(async ({ input }) => {
-    const value = cache.get(input.key)
-    return {
-      key: input.key,
-      value,
-      exists: value !== undefined,
-      timestamp: new Date().toISOString(),
-    }
-  }),
-
-  /**
-   * Delete specific cache entry
-   */
-  delete: superAdminProcedure.input(DeleteCacheEntrySchema).mutation(async ({ input }) => {
-    const existed = cache.has(input.key)
-    cache.delete(input.key)
-
-    return {
-      success: true,
-      existed,
-      message: existed ? 'Cache entry deleted' : 'Entry did not exist',
-      timestamp: new Date().toISOString(),
-    }
-  }),
-
-  /**
-   * Analyze query patterns and suggest database indexes
-   */
-  analyzeQueries: superAdminProcedure.query(async () => {
-    // Common query patterns in the application
-    const queryPatterns = [
-      {
-        name: 'Listings by status and author',
-        model: 'listing',
-        where: { status: ApprovalStatus.APPROVED, authorId: 'user_id' },
-        orderBy: { createdAt: 'desc' },
-      },
-      {
-        name: 'Listings by game and device',
-        model: 'listing',
-        where: { gameId: 'game_id', deviceId: 'device_id' },
-        orderBy: { createdAt: 'desc' },
-      },
-      {
-        name: 'Games by system with approval',
-        model: 'game',
-        where: { systemId: 'system_id', status: ApprovalStatus.APPROVED },
-        orderBy: { title: 'asc' },
-      },
-      {
-        name: 'PC Listings by CPU and GPU',
-        model: 'pcListing',
-        where: {
-          cpuId: 'cpu_id',
-          gpuId: 'gpu_id',
-          status: ApprovalStatus.APPROVED,
-        },
-        orderBy: { createdAt: 'desc' },
-      },
-      {
-        name: 'Users with listings count',
-        model: 'user',
-        where: { listings: { some: { status: ApprovalStatus.APPROVED } } },
-      },
-      {
-        name: 'Reports by status',
-        model: 'listingReport',
-        where: { status: ReportStatus.PENDING },
-        orderBy: { createdAt: 'desc' },
-      },
-    ]
-
-    const suggestions = queryPatterns.map((pattern) => ({
-      pattern: pattern.name,
-      model: pattern.model,
-      complexity: analyzeQueryComplexity(
-        pattern.model === 'user' ? { listings: true } : undefined,
-        pattern.where,
-      ),
-      indexSuggestions: suggestIndexes(pattern.model, pattern.where, pattern.orderBy),
-    }))
-
-    // Get actual query performance metrics
-    const performanceMetrics = QueryPerformanceMonitor.getMetrics()
-
-    return {
-      suggestions,
-      performanceMetrics,
-      timestamp: new Date().toISOString(),
-    }
-  }),
-})
diff --git a/src/server/api/routers/listings/admin.test.ts b/src/server/api/routers/listings/admin.test.ts
index a3d80f030..cf7043700 100644
--- a/src/server/api/routers/listings/admin.test.ts
+++ b/src/server/api/routers/listings/admin.test.ts
@@ -51,6 +51,7 @@ vi.mock('@/server/cache/invalidation', () => ({
 
 vi.mock('@/server/utils/cache/instances', () => ({
   listingStatsCache: { delete: vi.fn(), get: vi.fn(), set: vi.fn() },
+  invalidateCatalogCompatibilityCacheForDevice: vi.fn(),
 }))
 
 vi.mock('@/server/utils/emulator-config/emulator-detector', () => ({
diff --git a/src/server/api/routers/listings/admin.ts b/src/server/api/routers/listings/admin.ts
index 607c198bf..81fc7fb02 100644
--- a/src/server/api/routers/listings/admin.ts
+++ b/src/server/api/routers/listings/admin.ts
@@ -222,9 +222,10 @@ export const adminRouter = createTRPCRouter({
     await revalidateByTag(`device-${listingToApprove.deviceId}`)
     await revalidateByTag(`emulator-${listingToApprove.emulatorId}`)
 
-    // Invalidate catalog compatibility cache for this device
-    const { catalogCompatibilityCache } = await import('@/server/utils/cache/instances')
-    catalogCompatibilityCache.invalidatePattern(`device:${listingToApprove.deviceId}:*`)
+    const { invalidateCatalogCompatibilityCacheForDevice } = await import(
+      '@/server/utils/cache/instances'
+    )
+    invalidateCatalogCompatibilityCacheForDevice(listingToApprove.deviceId)
 
     // Apply trust action for listing approval to the author
     if (listingToApprove.authorId) {
@@ -342,9 +343,10 @@ export const adminRouter = createTRPCRouter({
     // Invalidate listing stats cache
     listingStatsCache.delete(LISTING_STATS_CACHE_KEY)
 
-    // Invalidate catalog compatibility cache for this device
-    const { catalogCompatibilityCache } = await import('@/server/utils/cache/instances')
-    catalogCompatibilityCache.invalidatePattern(`device:${listingToReject.deviceId}:*`)
+    const { invalidateCatalogCompatibilityCacheForDevice } = await import(
+      '@/server/utils/cache/instances'
+    )
+    invalidateCatalogCompatibilityCacheForDevice(listingToReject.deviceId)
 
     return updatedListing
   }),
diff --git a/src/server/api/routers/mobile/pcListings.ts b/src/server/api/routers/mobile/pcListings.ts
index 49bfb40a8..047beca35 100644
--- a/src/server/api/routers/mobile/pcListings.ts
+++ b/src/server/api/routers/mobile/pcListings.ts
@@ -14,9 +14,8 @@ import {
 } from '@/server/api/mobileContext'
 import { pcListingInclude, buildPcListingWhere } from '@/server/api/utils/pcListingHelpers'
 import {
-  invalidateListPages,
-  invalidateSitemap,
-  revalidateByTag,
+  invalidatePcListingSeo,
+  invalidatePcListingSeoForUpdate,
 } from '@/server/cache/invalidation'
 import { PcListingsRepository } from '@/server/repositories/pc-listings.repository'
 import { listingStatsCache } from '@/server/utils/cache'
@@ -181,11 +180,13 @@ export const mobilePcListingsRouter = createMobileTRPCRouter({
     // Invalidate stats cache
     listingStatsCache.delete('pc-listing-stats')
 
-    // Invalidate pages if approved
     if (created.status === ApprovalStatus.APPROVED) {
-      await invalidateListPages()
-      await invalidateSitemap()
-      await revalidateByTag('pc-listings')
+      await invalidatePcListingSeo({
+        id: created.id,
+        gameId: created.gameId,
+        cpuId: created.cpuId,
+        gpuId: created.gpuId,
+      })
     }
 
     return created
@@ -197,10 +198,9 @@ export const mobilePcListingsRouter = createMobileTRPCRouter({
   update: mobileProtectedProcedure.input(UpdatePcListingSchema).mutation(async ({ ctx, input }) => {
     const { id, customFieldValues, ...updateData } = input
 
-    // Check if user owns the listing
     const existing = await ctx.prisma.pcListing.findUnique({
       where: { id },
-      select: { authorId: true },
+      select: { authorId: true, status: true, gameId: true, cpuId: true, gpuId: true },
     })
 
     if (!existing) return ResourceError.pcListing.notFound()
@@ -209,7 +209,7 @@ export const mobilePcListingsRouter = createMobileTRPCRouter({
       return ResourceError.pcListing.canOnlyEditOwn()
     }
 
-    return await ctx.prisma.pcListing.update({
+    const updated = await ctx.prisma.pcListing.update({
       where: { id },
       data: {
         ...updateData,
@@ -233,6 +233,25 @@ export const mobilePcListingsRouter = createMobileTRPCRouter({
         _count: { select: { reports: true, developerVerifications: true } },
       },
     })
+
+    if (existing.status === ApprovalStatus.APPROVED) {
+      await invalidatePcListingSeoForUpdate(
+        {
+          id,
+          gameId: existing.gameId,
+          cpuId: existing.cpuId,
+          gpuId: existing.gpuId,
+        },
+        {
+          id,
+          gameId: updated.gameId,
+          cpuId: updated.cpuId,
+          gpuId: updated.gpuId,
+        },
+      )
+    }
+
+    return updated
   }),
 
   /**
diff --git a/src/server/api/routers/pcListings.test.ts b/src/server/api/routers/pcListings.test.ts
index 30b0d87d6..7e26f8532 100644
--- a/src/server/api/routers/pcListings.test.ts
+++ b/src/server/api/routers/pcListings.test.ts
@@ -1,6 +1,7 @@
 import { describe, expect, it, beforeEach, vi } from 'vitest'
 import { RISK_SIGNAL_TYPES } from '@/schemas/authorRisk'
 import { SUBMISSION_RISK_SIGNAL_TYPES } from '@/schemas/submissionRisk'
+import { invalidatePcListingsSeo } from '@/server/cache/invalidation'
 import { PERMISSIONS } from '@/utils/permission-system'
 import { ApprovalStatus, PcOs, Role, TrustAction } from '@orm/client'
 import type * as AuthorRiskService from '@/server/services/author-risk.service'
@@ -62,9 +63,9 @@ vi.mock('@/server/utils/cache', () => ({
 }))
 
 vi.mock('@/server/cache/invalidation', () => ({
-  invalidateListPages: vi.fn().mockResolvedValue(undefined),
-  invalidateSitemap: vi.fn().mockResolvedValue(undefined),
-  revalidateByTag: vi.fn().mockResolvedValue(undefined),
+  invalidatePcListingSeo: vi.fn().mockResolvedValue(undefined),
+  invalidatePcListingSeoForUpdate: vi.fn().mockResolvedValue(undefined),
+  invalidatePcListingsSeo: vi.fn().mockResolvedValue(undefined),
 }))
 
 vi.mock('@/lib/analytics', () => ({
@@ -811,11 +812,15 @@ describe('pcListings trust integration', () => {
       const listing1 = {
         id: LISTING_ID,
         gameId: '00000000-0000-4000-a000-000000000040',
+        cpuId: '00000000-0000-4000-a000-000000000070',
+        gpuId: '00000000-0000-4000-a000-000000000080',
         authorId: AUTHOR_ID,
       }
       const listing2 = {
         id: '00000000-0000-4000-a000-000000000011',
         gameId: '00000000-0000-4000-a000-000000000041',
+        cpuId: '00000000-0000-4000-a000-000000000071',
+        gpuId: null,
         authorId: '00000000-0000-4000-a000-000000000050',
       }
 
@@ -836,6 +841,7 @@ describe('pcListings trust integration', () => {
         action: TrustAction.LISTING_APPROVED,
         context: expect.objectContaining({ pcListingId: '00000000-0000-4000-a000-000000000011' }),
       })
+      expect(invalidatePcListingsSeo).toHaveBeenCalledWith([listing1, listing2])
     })
   })
 
diff --git a/src/server/api/routers/pcListings.ts b/src/server/api/routers/pcListings.ts
index 3676c4df1..863829813 100644
--- a/src/server/api/routers/pcListings.ts
+++ b/src/server/api/routers/pcListings.ts
@@ -57,9 +57,9 @@ import {
 } from '@/server/api/utils/pcListingHelpers'
 import { canManageCommentPins } from '@/server/api/utils/pinPermissions'
 import {
-  invalidateListPages,
-  invalidateSitemap,
-  revalidateByTag,
+  invalidatePcListingSeo,
+  invalidatePcListingSeoForUpdate,
+  invalidatePcListingsSeo,
 } from '@/server/cache/invalidation'
 import { NOTIFICATION_EVENTS, notificationEventEmitter } from '@/server/notifications/eventEmitter'
 import { PcListingsRepository } from '@/server/repositories/pc-listings.repository'
@@ -340,16 +340,13 @@ export const pcListingsRouter = createTRPCRouter({
     // Invalidate stats cache when PC listing is created
     listingStatsCache.delete('pc-listing-stats')
 
-    // Invalidate SEO cache if listing is approved
     if (newListing.status === ApprovalStatus.APPROVED) {
-      await invalidateListPages()
-      await invalidateSitemap()
-      await revalidateByTag('pc-listings')
-      await revalidateByTag(`game-${payload.gameId}`)
-      await revalidateByTag(`cpu-${payload.cpuId}`)
-      if (payload.gpuId) {
-        await revalidateByTag(`gpu-${payload.gpuId}`)
-      }
+      await invalidatePcListingSeo({
+        id: newListing.id,
+        gameId: payload.gameId,
+        cpuId: payload.cpuId,
+        gpuId: payload.gpuId ?? null,
+      })
     }
 
     return newListing
@@ -371,19 +368,28 @@ export const pcListingsRouter = createTRPCRouter({
       where: { id: input.id },
     })
 
-    // Invalidate stats cache when PC listing is deleted
     listingStatsCache.delete('pc-listing-stats')
 
+    if (pcListing.status === ApprovalStatus.APPROVED) {
+      await invalidatePcListingSeo(pcListing)
+    }
+
     return deletedListing
   }),
 
   update: protectedProcedure.input(UpdatePcListingUserSchema).mutation(async ({ ctx, input }) => {
     const EDIT_TIME_LIMIT_MINUTES = 60
 
-    // First check if user can edit this PC listing
     const pcListing = await ctx.prisma.pcListing.findUnique({
       where: { id: input.id },
-      select: { authorId: true, status: true, processedAt: true },
+      select: {
+        authorId: true,
+        status: true,
+        processedAt: true,
+        gameId: true,
+        cpuId: true,
+        gpuId: true,
+      },
     })
 
     if (!pcListing) return ResourceError.pcListing.notFound()
@@ -473,6 +479,23 @@ export const pcListingsRouter = createTRPCRouter({
       }
     }
 
+    if (pcListing.status === ApprovalStatus.APPROVED) {
+      await invalidatePcListingSeoForUpdate(
+        {
+          id,
+          gameId: pcListing.gameId,
+          cpuId: pcListing.cpuId,
+          gpuId: pcListing.gpuId,
+        },
+        {
+          id,
+          gameId: updatedPcListing.gameId,
+          cpuId: updatedPcListing.cpuId,
+          gpuId: updatedPcListing.gpuId,
+        },
+      )
+    }
+
     return updatedPcListing
   }),
 
@@ -599,9 +622,15 @@ export const pcListingsRouter = createTRPCRouter({
       })
     }
 
-    // Invalidate stats cache when PC listing is approved
     listingStatsCache.delete('pc-listing-stats')
 
+    await invalidatePcListingSeo({
+      id: input.pcListingId,
+      gameId: pcListing.gameId,
+      cpuId: pcListing.cpuId,
+      gpuId: pcListing.gpuId,
+    })
+
     notificationEventEmitter.emitNotificationEvent({
       eventType: NOTIFICATION_EVENTS.PC_LISTING_APPROVED,
       entityType: 'pcListing',
@@ -708,6 +737,15 @@ export const pcListingsRouter = createTRPCRouter({
 
       listingStatsCache.delete('pc-listing-stats')
 
+      if (pcListing.status === ApprovalStatus.APPROVED) {
+        await invalidatePcListingSeo({
+          id: input.pcListingId,
+          gameId: pcListing.gameId,
+          cpuId: pcListing.cpuId,
+          gpuId: pcListing.gpuId,
+        })
+      }
+
       return updatedListing
     }),
 
@@ -725,7 +763,7 @@ export const pcListingsRouter = createTRPCRouter({
 
       const pendingListings = await ctx.prisma.pcListing.findMany({
         where: { id: { in: input.pcListingIds }, status: ApprovalStatus.PENDING },
-        select: { id: true, gameId: true, authorId: true },
+        select: { id: true, gameId: true, cpuId: true, gpuId: true, authorId: true },
       })
 
       const result = await ctx.prisma.pcListing.updateMany({
@@ -757,6 +795,8 @@ export const pcListingsRouter = createTRPCRouter({
 
       listingStatsCache.delete('pc-listing-stats')
 
+      await invalidatePcListingsSeo(pendingListings)
+
       for (const listing of pendingListings) {
         notificationEventEmitter.emitNotificationEvent({
           eventType: NOTIFICATION_EVENTS.PC_LISTING_APPROVED,
diff --git a/src/server/cache/invalidation.test.ts b/src/server/cache/invalidation.test.ts
new file mode 100644
index 000000000..0adf383c8
--- /dev/null
+++ b/src/server/cache/invalidation.test.ts
@@ -0,0 +1,49 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+const nextCacheMocks = vi.hoisted(() => ({
+  revalidatePath: vi.fn(),
+  revalidateTag: vi.fn(),
+}))
+
+vi.mock('next/cache', () => nextCacheMocks)
+
+const { invalidatePcListing, invalidatePcListingSeoForUpdate, revalidateByTag } = await import(
+  './invalidation'
+)
+
+describe('cache invalidation', () => {
+  beforeEach(() => {
+    vi.clearAllMocks()
+  })
+
+  it('uses the stale-while-revalidate profile for tag invalidation', async () => {
+    await revalidateByTag('games')
+
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('games', 'max')
+  })
+
+  it('invalidates PC listing paths and tags consistently', async () => {
+    await invalidatePcListing('pc-listing-1')
+
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/pc-listings/pc-listing-1')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('pc-listing-pc-listing-1', 'max')
+  })
+
+  it('deduplicates related PC listing SEO tags for updates', async () => {
+    await invalidatePcListingSeoForUpdate(
+      { id: 'pc-listing-1', gameId: 'old-game', cpuId: 'old-cpu', gpuId: 'gpu-1' },
+      { id: 'pc-listing-1', gameId: 'new-game', cpuId: 'new-cpu', gpuId: 'gpu-1' },
+    )
+
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/pc-listings/pc-listing-1')
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/pc-listings')
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/sitemap.xml')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('pc-listings', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('game-old-game', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('game-new-game', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('cpu-old-cpu', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('cpu-new-cpu', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('gpu-gpu-1', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledTimes(8)
+  })
+})
diff --git a/src/server/cache/invalidation.ts b/src/server/cache/invalidation.ts
index 8b148c352..e4b0d5122 100644
--- a/src/server/cache/invalidation.ts
+++ b/src/server/cache/invalidation.ts
@@ -1,18 +1,18 @@
 import { revalidatePath, revalidateTag } from 'next/cache'
-import { invalidateCache } from '@/lib/cache/seo-cache'
 
-/**
- * Cache invalidation strategies for SEO content
- * Coordinates between in-memory cache and Next.js ISR cache
- */
+interface PcListingSeoTarget {
+  id: string
+  gameId: string
+  cpuId: string
+  gpuId: string | null
+}
 
 export async function invalidateGame(gameId: string) {
   const startTime = performance.now()
 
-  invalidateCache(`seo:game:${gameId}`)
-
   try {
     revalidatePath(`/games/${gameId}`)
+    revalidateTag(`game-${gameId}`, 'max')
   } catch (error) {
     console.error(`Failed to revalidate game path: ${gameId}`, error)
   }
@@ -24,40 +24,36 @@ export async function invalidateGame(gameId: string) {
 }
 
 export async function invalidateListing(listingId: string) {
-  invalidateCache(`seo:listing:${listingId}`)
-
   try {
     revalidatePath(`/listings/${listingId}`)
+    revalidateTag(`listing-${listingId}`, 'max')
   } catch (error) {
     console.error(`Failed to revalidate listing path: ${listingId}`, error)
   }
 }
 
 export async function invalidatePcListing(pcListingId: string) {
-  invalidateCache(`seo:pclisting:${pcListingId}`)
-
   try {
     revalidatePath(`/pc-listings/${pcListingId}`)
+    revalidateTag(`pc-listing-${pcListingId}`, 'max')
   } catch (error) {
     console.error(`Failed to revalidate PC listing path: ${pcListingId}`, error)
   }
 }
 
 export async function invalidateUser(userId: string) {
-  invalidateCache(`seo:user:${userId}`)
-
   try {
     revalidatePath(`/users/${userId}`)
+    revalidateTag(`user-${userId}`, 'max')
   } catch (error) {
     console.error(`Failed to revalidate user path: ${userId}`, error)
   }
 }
 
 export async function invalidateSitemap() {
-  invalidateCache(/^seo:sitemap:/)
-
   try {
     revalidatePath('/sitemap.xml')
+    revalidateTag('sitemap', 'max')
   } catch (error) {
     console.error('Failed to revalidate sitemap', error)
   }
@@ -77,23 +73,61 @@ export async function invalidateGameRelatedContent(gameId: string) {
   const startTime = performance.now()
 
   await invalidateGame(gameId)
-
-  const listingsInvalidated = invalidateCache(
-    new RegExp(`seo:(listing|pclisting):.*game:${gameId}`),
-  )
-
   await invalidateListPages()
 
   const duration = performance.now() - startTime
-  console.log(
-    `[SEO] Batch invalidation for game ${gameId}: ${listingsInvalidated} listings cleared in ${duration.toFixed(2)}ms`,
+  console.log(`[SEO] Batch invalidation for game ${gameId} completed in ${duration.toFixed(2)}ms`)
+}
+
+export async function invalidatePcListingSeo(listing: PcListingSeoTarget) {
+  await invalidatePcListingSeoTargets([listing.id], [listing])
+}
+
+export async function invalidatePcListingSeoForUpdate(
+  previous: PcListingSeoTarget,
+  next: PcListingSeoTarget,
+) {
+  await invalidatePcListingSeoTargets([previous.id], [previous, next])
+}
+
+export async function invalidatePcListingsSeo(listings: PcListingSeoTarget[]) {
+  await invalidatePcListingSeoTargets(
+    listings.map((listing) => listing.id),
+    listings,
   )
 }
 
 export async function revalidateByTag(tag: string) {
   try {
-    revalidateTag(tag)
+    revalidateTag(tag, 'max')
   } catch (error) {
     console.error(`Failed to revalidate tag: ${tag}`, error)
   }
 }
+
+async function invalidatePcListingSeoTargets(
+  listingIds: string[],
+  tagTargets: PcListingSeoTarget[],
+) {
+  if (listingIds.length === 0 && tagTargets.length === 0) return
+
+  const uniqueListingIds = [...new Set(listingIds)]
+  const tags = collectPcListingSeoTags(tagTargets)
+
+  await Promise.all(uniqueListingIds.map((listingId) => invalidatePcListing(listingId)))
+  await invalidateListPages()
+  await invalidateSitemap()
+  await Promise.all([...tags].map((tag) => revalidateByTag(tag)))
+}
+
+function collectPcListingSeoTags(listings: PcListingSeoTarget[]): Set<string> {
+  const tags = new Set<string>(['pc-listings'])
+
+  for (const listing of listings) {
+    tags.add(`game-${listing.gameId}`)
+    tags.add(`cpu-${listing.cpuId}`)
+    if (listing.gpuId) tags.add(`gpu-${listing.gpuId}`)
+  }
+
+  return tags
+}
diff --git a/src/server/cache/warming.ts b/src/server/cache/warming.ts
deleted file mode 100644
index 548f5bfc9..000000000
--- a/src/server/cache/warming.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import { warmCache } from '@/lib/cache/seo-cache'
-import { prisma } from '@/server/db'
-import {
-  getGameForSEO,
-  getListingForSEO,
-  getPcListingForSEO,
-  getApprovedGamesForSitemap,
-  getApprovedListingsForSitemap,
-  getApprovedPcListingsForSitemap,
-} from '@/server/db/seo-queries'
-import { ListingsRepository } from '@/server/repositories/listings.repository'
-import { ms } from '@/utils/time'
-import { ApprovalStatus } from '@orm/client'
-
-/**
- * Cache warming strategies for popular content
- * Runs periodically to pre-populate cache with frequently accessed data
- */
-
-interface WarmingResult {
-  warmedItems: number
-  duration: number
-  errors: string[]
-}
-
-export async function warmPopularGames(limit = 50): Promise<WarmingResult> {
-  const startTime = Date.now()
-  const errors: string[] = []
-  let warmedItems = 0
-
-  try {
-    // Get most popular games (those with most listings)
-    const popularGames = await prisma.game.findMany({
-      where: { status: ApprovalStatus.APPROVED },
-      select: {
-        id: true,
-        _count: { select: { listings: true, pcListings: true } },
-      },
-      orderBy: [{ listings: { _count: 'desc' } }],
-      take: limit,
-    })
-
-    // Warm cache for popular games
-    const gameWarmingTasks = popularGames.map((game) => ({
-      key: `seo:game:${game.id}`,
-      fetchFn: () => getGameForSEO(game.id),
-    }))
-
-    await warmCache(gameWarmingTasks, {
-      ttl: ms.hours(1),
-      staleWhileRevalidate: ms.days(1),
-    })
-
-    warmedItems += gameWarmingTasks.length
-  } catch (error) {
-    errors.push(`Failed to warm popular games: ${error}`)
-  }
-
-  return {
-    warmedItems,
-    duration: Date.now() - startTime,
-    errors,
-  }
-}
-
-export async function warmRecentListings(limit = 100): Promise<WarmingResult> {
-  const startTime = Date.now()
-  const errors: string[] = []
-  let warmedItems = 0
-
-  try {
-    const repository = new ListingsRepository(prisma)
-
-    // Get recent approved listings using repository
-    const recentListingsResult = await repository.listRecent(limit)
-    const recentListings = recentListingsResult.listings.map((listing) => ({ id: listing.id }))
-
-    // Get recent approved PC listings
-    const recentPcListings = await prisma.pcListing.findMany({
-      where: { status: ApprovalStatus.APPROVED },
-      select: { id: true },
-      orderBy: { createdAt: 'desc' },
-      take: limit,
-    })
-
-    // Warm cache for listings separately
-    const listingTasks = recentListings.map((listing) => ({
-      key: `seo:listing:${listing.id}`,
-      fetchFn: () => getListingForSEO(listing.id),
-    }))
-
-    const pcListingTasks = recentPcListings.map((listing) => ({
-      key: `seo:pclisting:${listing.id}`,
-      fetchFn: () => getPcListingForSEO(listing.id),
-    }))
-
-    await warmCache(listingTasks, {
-      ttl: ms.minutes(30),
-      staleWhileRevalidate: ms.hours(12),
-    })
-
-    await warmCache(pcListingTasks, {
-      ttl: ms.minutes(30),
-      staleWhileRevalidate: ms.hours(12),
-    })
-
-    warmedItems += listingTasks.length + pcListingTasks.length
-  } catch (error) {
-    errors.push(`Failed to warm recent listings: ${error}`)
-  }
-
-  return {
-    warmedItems,
-    duration: Date.now() - startTime,
-    errors,
-  }
-}
-
-export async function warmSitemapData(): Promise<WarmingResult> {
-  const startTime = Date.now()
-  const errors: string[] = []
-  let warmedItems = 0
-
-  try {
-    // Warm sitemap data
-    const sitemapTasks = [
-      {
-        key: 'seo:sitemap:games:1000',
-        fetchFn: () => getApprovedGamesForSitemap(1000),
-      },
-      {
-        key: 'seo:sitemap:listings:500',
-        fetchFn: () => getApprovedListingsForSitemap(500),
-      },
-      {
-        key: 'seo:sitemap:pclistings:500',
-        fetchFn: () => getApprovedPcListingsForSitemap(500),
-      },
-    ]
-
-    await warmCache(sitemapTasks, {
-      ttl: ms.hours(6),
-      staleWhileRevalidate: ms.days(2),
-    })
-
-    warmedItems += sitemapTasks.length
-  } catch (error) {
-    errors.push(`Failed to warm sitemap data: ${error}`)
-  }
-
-  return {
-    warmedItems,
-    duration: Date.now() - startTime,
-    errors,
-  }
-}
-
-export async function warmAllCaches(): Promise<WarmingResult> {
-  console.log('[Cache Warming] Starting comprehensive cache warming...')
-
-  const results = await Promise.all([
-    warmPopularGames(50),
-    warmRecentListings(100),
-    warmSitemapData(),
-  ])
-
-  const totalWarmed = results.reduce((sum, r) => sum + r.warmedItems, 0)
-  const totalDuration = Math.max(...results.map((r) => r.duration))
-  const allErrors = results.flatMap((r) => r.errors)
-
-  console.info(`[Cache Warming] Completed: ${totalWarmed} items warmed in ${totalDuration}ms`)
-
-  if (allErrors.length > 0) {
-    console.error('[Cache Warming] Errors encountered:', allErrors)
-  }
-
-  return {
-    warmedItems: totalWarmed,
-    duration: totalDuration,
-    errors: allErrors,
-  }
-}
-
-/**
- * Schedule cache warming
- * This should be called from a cron job or similar scheduler
- */
-export function scheduleCacheWarming() {
-  // Initial warming on startup
-  setTimeout(() => {
-    warmAllCaches().catch(console.error)
-  }, ms.seconds(5))
-
-  // Warm popular content every hour
-  setInterval(() => {
-    warmPopularGames(30).catch(console.error)
-  }, ms.hours(1))
-
-  // Warm recent listings every 30 minutes
-  setInterval(() => {
-    warmRecentListings(50).catch(console.error)
-  }, ms.minutes(30))
-
-  // Warm sitemap data every 6 hours
-  setInterval(() => {
-    warmSitemapData().catch(console.error)
-  }, ms.hours(6))
-}
diff --git a/src/server/db/seo-queries.test.ts b/src/server/db/seo-queries.test.ts
new file mode 100644
index 000000000..5fbbdb59b
--- /dev/null
+++ b/src/server/db/seo-queries.test.ts
@@ -0,0 +1,122 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+const cacheMocks = vi.hoisted(() => ({
+  cacheLife: vi.fn(),
+  cacheTag: vi.fn(),
+}))
+
+const prismaMocks = vi.hoisted(() => ({
+  game: {
+    findUnique: vi.fn(),
+    findMany: vi.fn(),
+  },
+  listing: {
+    findUnique: vi.fn(),
+    findMany: vi.fn(),
+  },
+  pcListing: {
+    findUnique: vi.fn(),
+    findMany: vi.fn(),
+  },
+  user: {
+    findUnique: vi.fn(),
+  },
+}))
+
+vi.mock('next/cache', () => cacheMocks)
+vi.mock('@/server/db', () => ({ prisma: prismaMocks }))
+
+const { getApprovedGamesForSitemap, getGameForSEO, getListingForSEO, getPcListingForSEO } =
+  await import('./seo-queries')
+
+describe('SEO query cache metadata', () => {
+  beforeEach(() => {
+    vi.clearAllMocks()
+  })
+
+  it('uses a short cache profile for missing SEO records', async () => {
+    prismaMocks.game.findUnique.mockResolvedValueOnce(null)
+
+    await getGameForSEO('game-1')
+
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('games', 'game-game-1')
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-miss')
+  })
+
+  it('does not convert SEO query failures into cacheable misses', async () => {
+    const error = new Error('database unavailable')
+    prismaMocks.game.findUnique.mockRejectedValueOnce(error)
+
+    await expect(getGameForSEO('game-1')).rejects.toThrow(error)
+
+    expect(cacheMocks.cacheLife).not.toHaveBeenCalled()
+  })
+
+  it('tags handheld report metadata by listing, game, device, and emulator', async () => {
+    const listing = {
+      id: 'listing-1',
+      gameId: 'game-1',
+      deviceId: 'device-1',
+      emulatorId: 'emulator-1',
+      notes: null,
+      createdAt: new Date('2026-01-01T00:00:00.000Z'),
+      game: { title: 'Game', imageUrl: null },
+      device: { modelName: 'Pocket', brand: { name: 'Brand' } },
+      emulator: { name: 'Emulator' },
+      performance: { label: 'Perfect', rank: 5 },
+      author: { name: 'Author' },
+    }
+    prismaMocks.listing.findUnique.mockResolvedValueOnce(listing)
+
+    await getListingForSEO('listing-1')
+
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('listings', 'listing-listing-1')
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith(
+      'game-game-1',
+      'device-device-1',
+      'emulator-emulator-1',
+    )
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-report')
+  })
+
+  it('tags PC report metadata by listing, game, CPU, and GPU', async () => {
+    const listing = {
+      id: 'pc-listing-1',
+      gameId: 'game-1',
+      cpuId: 'cpu-1',
+      gpuId: 'gpu-1',
+      notes: null,
+      createdAt: new Date('2026-01-01T00:00:00.000Z'),
+      game: { title: 'Game', imageUrl: null },
+      cpu: { modelName: 'CPU', brand: { name: 'Brand' } },
+      gpu: { modelName: 'GPU', brand: { name: 'Brand' } },
+      performance: { label: 'Perfect', rank: 5 },
+      author: { name: 'Author' },
+    }
+    prismaMocks.pcListing.findUnique.mockResolvedValueOnce(listing)
+
+    await getPcListingForSEO('pc-listing-1')
+
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('pc-listings', 'pc-listing-pc-listing-1')
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('game-game-1', 'cpu-cpu-1', 'gpu-gpu-1')
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-report')
+  })
+
+  it('uses the sitemap cache profile and tags sitemap data', async () => {
+    prismaMocks.game.findMany.mockResolvedValueOnce([])
+
+    await getApprovedGamesForSitemap(1000)
+
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-sitemap')
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('sitemap', 'games')
+  })
+
+  it('does not cache an empty sitemap when the sitemap query fails', async () => {
+    const error = new Error('database unavailable')
+    prismaMocks.game.findMany.mockRejectedValueOnce(error)
+
+    await expect(getApprovedGamesForSitemap(1000)).rejects.toThrow(error)
+
+    expect(cacheMocks.cacheLife).not.toHaveBeenCalled()
+  })
+})
diff --git a/src/server/db/seo-queries.ts b/src/server/db/seo-queries.ts
index a2d6c5fcc..9631237a6 100644
--- a/src/server/db/seo-queries.ts
+++ b/src/server/db/seo-queries.ts
@@ -1,246 +1,165 @@
-import { getFromCache } from '@/lib/cache/seo-cache'
-import { withMetrics } from '@/lib/monitoring/seo-metrics'
+import { cacheLife, cacheTag } from 'next/cache'
 import { prisma } from '@/server/db'
-import { ms } from '@/utils/time'
 import { ApprovalStatus } from '@orm/client'
 
-/**
- * Database queries optimized for SEO metadata generation.
- * These queries are designed to be fast and fetch only the data needed for metadata.
- * All queries use caching to minimize database load.
- */
-
-// Cache TTLs based on content type
-const CACHE_TTL = {
-  GAME: ms.hours(1),
-  LISTING: ms.minutes(30),
-  USER: ms.hours(1),
-  SITEMAP: ms.hours(6),
-}
-
-const CACHE_STALE = {
-  GAME: ms.days(1),
-  LISTING: ms.hours(12),
-  USER: ms.days(1),
-  SITEMAP: ms.days(2),
-}
+const SEO_CACHE_PROFILE = {
+  RECORD: 'seo-record',
+  REPORT: 'seo-report',
+  SITEMAP: 'seo-sitemap',
+  MISS: 'seo-miss',
+} as const
 
 export async function getGameForSEO(id: string) {
-  return withMetrics(
-    'getGameForSEO',
-    () =>
-      getFromCache(
-        `seo:game:${id}`,
-        async () => {
-          try {
-            return await prisma.game.findUnique({
-              where: { id },
-              select: {
-                id: true,
-                title: true,
-                imageUrl: true,
-                system: { select: { name: true } },
-              },
-            })
-          } catch {
-            return null
-          }
-        },
-        {
-          ttl: CACHE_TTL.GAME,
-          staleWhileRevalidate: CACHE_STALE.GAME,
-        },
-      ),
-    { cacheKey: `seo:game:${id}` },
-  )
+  'use cache'
+
+  const game = await prisma.game.findUnique({
+    where: { id },
+    select: {
+      id: true,
+      title: true,
+      imageUrl: true,
+      system: { select: { name: true } },
+    },
+  })
+
+  cacheTag('games', `game-${id}`)
+  cacheLife(game ? SEO_CACHE_PROFILE.RECORD : SEO_CACHE_PROFILE.MISS)
+  return game
 }
 
 export async function getListingForSEO(id: string) {
-  return withMetrics(
-    'getListingForSEO',
-    () =>
-      getFromCache(
-        `seo:listing:${id}`,
-        async () => {
-          try {
-            return await prisma.listing.findUnique({
-              where: { id },
-              select: {
-                id: true,
-                notes: true,
-                createdAt: true,
-                game: { select: { title: true, imageUrl: true } },
-                device: {
-                  select: {
-                    modelName: true,
-                    brand: { select: { name: true } },
-                  },
-                },
-                emulator: { select: { name: true } },
-                performance: { select: { label: true, rank: true } },
-                author: { select: { name: true } },
-              },
-            })
-          } catch {
-            return null
-          }
-        },
-        {
-          ttl: CACHE_TTL.LISTING,
-          staleWhileRevalidate: CACHE_STALE.LISTING,
+  'use cache'
+
+  const listing = await prisma.listing.findUnique({
+    where: { id },
+    select: {
+      id: true,
+      gameId: true,
+      deviceId: true,
+      emulatorId: true,
+      notes: true,
+      createdAt: true,
+      game: { select: { title: true, imageUrl: true } },
+      device: {
+        select: {
+          modelName: true,
+          brand: { select: { name: true } },
         },
-      ),
-    { cacheKey: `seo:listing:${id}` },
-  )
+      },
+      emulator: { select: { name: true } },
+      performance: { select: { label: true, rank: true } },
+      author: { select: { name: true } },
+    },
+  })
+
+  cacheTag('listings', `listing-${id}`)
+  if (listing) {
+    cacheTag(
+      `game-${listing.gameId}`,
+      `device-${listing.deviceId}`,
+      `emulator-${listing.emulatorId}`,
+    )
+  }
+
+  cacheLife(listing ? SEO_CACHE_PROFILE.REPORT : SEO_CACHE_PROFILE.MISS)
+  return listing
 }
 
 export async function getPcListingForSEO(id: string) {
-  return withMetrics(
-    'getPcListingForSEO',
-    () =>
-      getFromCache(
-        `seo:pclisting:${id}`,
-        async () => {
-          try {
-            return await prisma.pcListing.findUnique({
-              where: { id },
-              select: {
-                id: true,
-                notes: true,
-                createdAt: true,
-                game: { select: { title: true, imageUrl: true } },
-                cpu: {
-                  select: {
-                    modelName: true,
-                    brand: { select: { name: true } },
-                  },
-                },
-                gpu: {
-                  select: {
-                    modelName: true,
-                    brand: { select: { name: true } },
-                  },
-                },
-                performance: { select: { label: true, rank: true } },
-                author: { select: { name: true } },
-              },
-            })
-          } catch {
-            return null
-          }
+  'use cache'
+
+  const listing = await prisma.pcListing.findUnique({
+    where: { id },
+    select: {
+      id: true,
+      gameId: true,
+      cpuId: true,
+      gpuId: true,
+      notes: true,
+      createdAt: true,
+      game: { select: { title: true, imageUrl: true } },
+      cpu: {
+        select: {
+          modelName: true,
+          brand: { select: { name: true } },
         },
-        {
-          ttl: CACHE_TTL.LISTING,
-          staleWhileRevalidate: CACHE_STALE.LISTING,
+      },
+      gpu: {
+        select: {
+          modelName: true,
+          brand: { select: { name: true } },
         },
-      ),
-    { cacheKey: `seo:pclisting:${id}` },
-  )
+      },
+      performance: { select: { label: true, rank: true } },
+      author: { select: { name: true } },
+    },
+  })
+
+  cacheTag('pc-listings', `pc-listing-${id}`)
+  if (listing) {
+    const tags = [`game-${listing.gameId}`, `cpu-${listing.cpuId}`]
+    if (listing.gpuId) tags.push(`gpu-${listing.gpuId}`)
+    cacheTag(...tags)
+  }
+
+  cacheLife(listing ? SEO_CACHE_PROFILE.REPORT : SEO_CACHE_PROFILE.MISS)
+  return listing
 }
 
 export async function getUserForSEO(id: string) {
-  return withMetrics(
-    'getUserForSEO',
-    () =>
-      getFromCache(
-        `seo:user:${id}`,
-        async () => {
-          try {
-            return await prisma.user.findUnique({
-              where: { id },
-              select: { id: true, name: true, profileImage: true },
-            })
-          } catch {
-            return null
-          }
-        },
-        {
-          ttl: CACHE_TTL.USER,
-          staleWhileRevalidate: CACHE_STALE.USER,
-        },
-      ),
-    { cacheKey: `seo:user:${id}` },
-  )
+  'use cache'
+
+  const user = await prisma.user.findUnique({
+    where: { id },
+    select: { id: true, name: true, profileImage: true },
+  })
+
+  cacheTag(`user-${id}`)
+  cacheLife(user ? SEO_CACHE_PROFILE.RECORD : SEO_CACHE_PROFILE.MISS)
+  return user
 }
 
-// For sitemap generation - cached longer since it's expensive
 export async function getApprovedGamesForSitemap(limit = 1000) {
-  return withMetrics(
-    'getApprovedGamesForSitemap',
-    () =>
-      getFromCache(
-        `seo:sitemap:games:${limit}`,
-        async () => {
-          try {
-            return await prisma.game.findMany({
-              where: { status: ApprovalStatus.APPROVED },
-              select: { id: true, createdAt: true },
-              orderBy: { createdAt: 'desc' },
-              take: limit,
-            })
-          } catch {
-            return []
-          }
-        },
-        {
-          ttl: CACHE_TTL.SITEMAP,
-          staleWhileRevalidate: CACHE_STALE.SITEMAP,
-        },
-      ),
-    { cacheKey: `seo:sitemap:games:${limit}` },
-  )
+  'use cache'
+
+  const games = await prisma.game.findMany({
+    where: { status: ApprovalStatus.APPROVED },
+    select: { id: true, createdAt: true },
+    orderBy: { createdAt: 'desc' },
+    take: limit,
+  })
+
+  cacheLife(SEO_CACHE_PROFILE.SITEMAP)
+  cacheTag('sitemap', 'games')
+  return games
 }
 
 export async function getApprovedListingsForSitemap(limit = 500) {
-  return withMetrics(
-    'getApprovedListingsForSitemap',
-    () =>
-      getFromCache(
-        `seo:sitemap:listings:${limit}`,
-        async () => {
-          try {
-            return await prisma.listing.findMany({
-              where: { status: ApprovalStatus.APPROVED },
-              select: { id: true, createdAt: true },
-              orderBy: { createdAt: 'desc' },
-              take: limit,
-            })
-          } catch {
-            return []
-          }
-        },
-        {
-          ttl: CACHE_TTL.SITEMAP,
-          staleWhileRevalidate: CACHE_STALE.SITEMAP,
-        },
-      ),
-    { cacheKey: `seo:sitemap:listings:${limit}` },
-  )
+  'use cache'
+
+  const listings = await prisma.listing.findMany({
+    where: { status: ApprovalStatus.APPROVED },
+    select: { id: true, createdAt: true },
+    orderBy: { createdAt: 'desc' },
+    take: limit,
+  })
+
+  cacheLife(SEO_CACHE_PROFILE.SITEMAP)
+  cacheTag('sitemap', 'listings')
+  return listings
 }
 
 export async function getApprovedPcListingsForSitemap(limit = 500) {
-  return withMetrics(
-    'getApprovedPcListingsForSitemap',
-    () =>
-      getFromCache(
-        `seo:sitemap:pclistings:${limit}`,
-        async () => {
-          try {
-            return await prisma.pcListing.findMany({
-              where: { status: ApprovalStatus.APPROVED },
-              select: { id: true, createdAt: true },
-              orderBy: { createdAt: 'desc' },
-              take: limit,
-            })
-          } catch {
-            return []
-          }
-        },
-        {
-          ttl: CACHE_TTL.SITEMAP,
-          staleWhileRevalidate: CACHE_STALE.SITEMAP,
-        },
-      ),
-    { cacheKey: `seo:sitemap:pclistings:${limit}` },
-  )
+  'use cache'
+
+  const listings = await prisma.pcListing.findMany({
+    where: { status: ApprovalStatus.APPROVED },
+    select: { id: true, createdAt: true },
+    orderBy: { createdAt: 'desc' },
+    take: limit,
+  })
+
+  cacheLife(SEO_CACHE_PROFILE.SITEMAP)
+  cacheTag('sitemap', 'pc-listings')
+  return listings
 }
diff --git a/src/server/notifications/analyticsService.ts b/src/server/notifications/analyticsService.ts
index 032324a2b..40c168678 100644
--- a/src/server/notifications/analyticsService.ts
+++ b/src/server/notifications/analyticsService.ts
@@ -489,13 +489,6 @@ export class NotificationAnalyticsService {
     notificationAnalyticsCache.clear()
   }
 
-  /**
-   * Get cache statistics for monitoring
-   */
-  getCacheStats() {
-    return notificationAnalyticsCache.getStats()
-  }
-
   private buildDateFilter(startDate?: Date, endDate?: Date): Record<string, unknown> {
     const filter: Record<string, unknown> = {}
 
diff --git a/src/server/services/catalog.service.test.ts b/src/server/services/catalog.service.test.ts
new file mode 100644
index 000000000..f2404f9d0
--- /dev/null
+++ b/src/server/services/catalog.service.test.ts
@@ -0,0 +1,88 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { Role, type PrismaClient } from '@orm/client'
+
+const mockDeviceById = vi.hoisted(() => vi.fn())
+const mockFindByModelAndBrandName = vi.hoisted(() => vi.fn())
+const mockGetDeviceCompatibilityData = vi.hoisted(() => vi.fn())
+const mockGetSocCompatibilityData = vi.hoisted(() => vi.fn())
+
+const cacheMocks = vi.hoisted(() => ({
+  get: vi.fn(),
+  set: vi.fn(),
+}))
+
+vi.mock('@/server/repositories/devices.repository', () => ({
+  DevicesRepository: vi.fn().mockImplementation(function MockDevicesRepository() {
+    return {
+      byId: mockDeviceById,
+      findByModelAndBrandName: mockFindByModelAndBrandName,
+    }
+  }),
+}))
+
+vi.mock('@/server/repositories/listings.repository', () => ({
+  ListingsRepository: vi.fn().mockImplementation(function MockListingsRepository() {
+    return {
+      getDeviceCompatibilityData: mockGetDeviceCompatibilityData,
+      getSocCompatibilityData: mockGetSocCompatibilityData,
+    }
+  }),
+}))
+
+vi.mock('@/server/utils/cache/instances', () => ({
+  catalogCompatibilityCache: cacheMocks,
+}))
+
+const { getDeviceCompatibility } = await import('./catalog.service')
+
+const device = {
+  id: 'device-1',
+  modelName: 'Pocket',
+  socId: 'soc-1',
+  brand: { name: 'Brand' },
+  soc: { id: 'soc-1', name: 'SoC', manufacturer: 'Maker' },
+}
+
+const cachedResponse = {
+  device: {
+    id: 'device-1',
+    modelName: 'Pocket',
+    brandName: 'Brand',
+    socId: 'soc-1',
+    socName: 'SoC',
+    socManufacturer: 'Maker',
+  },
+  systems: [],
+  generatedAt: new Date('2026-01-01T00:00:00.000Z'),
+  cacheExpiresIn: 600,
+}
+
+describe('catalog compatibility cache', () => {
+  beforeEach(() => {
+    vi.clearAllMocks()
+    mockDeviceById.mockResolvedValue(device)
+    cacheMocks.get.mockReturnValue(cachedResponse)
+  })
+
+  it('uses a public visibility cache key for regular users', async () => {
+    await getDeviceCompatibility(
+      { deviceId: 'device-1', systemIds: ['system-b', 'system-a'] },
+      { prisma: {} as PrismaClient, userRole: Role.USER, userId: 'user-1' },
+    )
+
+    expect(cacheMocks.get).toHaveBeenCalledWith(
+      'device:device-1:systems:system-a,system-b:breakdown:true:min:1:soc:soc-1:visibility:public',
+    )
+  })
+
+  it('uses a moderator visibility cache key for moderators', async () => {
+    await getDeviceCompatibility(
+      { deviceId: 'device-1', includeEmulatorBreakdown: false, minListingCount: 3 },
+      { prisma: {} as PrismaClient, userRole: Role.MODERATOR, userId: 'mod-1' },
+    )
+
+    expect(cacheMocks.get).toHaveBeenCalledWith(
+      'device:device-1:systems:all:breakdown:false:min:3:soc:soc-1:visibility:moderator',
+    )
+  })
+})
diff --git a/src/server/services/catalog.service.ts b/src/server/services/catalog.service.ts
index 2ac4b4c26..2f5cdcea8 100644
--- a/src/server/services/catalog.service.ts
+++ b/src/server/services/catalog.service.ts
@@ -8,12 +8,13 @@ import {
   MINIMUM_DEVICE_LISTINGS,
   type ScoringListingWithMetadata,
 } from '@/server/utils/compatibility-scoring'
+import { hasRolePermission } from '@/utils/permissions'
+import { Role, type PrismaClient } from '@orm/client'
 import type {
   DeviceCompatibilityResponse,
   EmulatorCompatibility,
   SystemCompatibility,
 } from '@/schemas/mobile'
-import type { Role, PrismaClient } from '@orm/client'
 
 export interface GetDeviceCompatibilityInput {
   deviceId?: string
@@ -53,7 +54,6 @@ export async function getDeviceCompatibility(
   const devicesRepo = new DevicesRepository(ctx.prisma)
   const listingsRepo = new ListingsRepository(ctx.prisma)
 
-  // Resolve device by ID or by name + brand
   let device
   if (input.deviceId) {
     device = await devicesRepo.byId(input.deviceId)
@@ -63,14 +63,14 @@ export async function getDeviceCompatibility(
 
   if (!device) throw ResourceError.device.notFound()
 
-  // Create cache key from device ID and input parameters (include SoC in key)
-  const cacheKey = `device:${device.id}:systems:${input.systemIds?.sort().join(',') ?? 'all'}:breakdown:${input.includeEmulatorBreakdown ?? true}:min:${input.minListingCount ?? 1}:soc:${device.socId ?? 'none'}`
+  const systemKey = input.systemIds ? [...input.systemIds].sort().join(',') : 'all'
+  const visibilityBucket =
+    ctx.userRole && hasRolePermission(ctx.userRole, Role.MODERATOR) ? 'moderator' : 'public'
+  const cacheKey = `device:${device.id}:systems:${systemKey}:breakdown:${input.includeEmulatorBreakdown ?? true}:min:${input.minListingCount ?? 1}:soc:${device.socId ?? 'none'}:visibility:${visibilityBucket}`
 
-  // Check cache first
   const cached = catalogCompatibilityCache.get(cacheKey)
   if (cached) return cached
 
-  // Fetch device-specific listings
   const deviceListings = await listingsRepo.getDeviceCompatibilityData(device.id, {
     systemIds: input.systemIds,
     userRole: ctx.userRole,
@@ -88,11 +88,10 @@ export async function getDeviceCompatibility(
       },
       systems: [],
       generatedAt: new Date(),
-      cacheExpiresIn: 600, // 10 minutes
+      cacheExpiresIn: 600,
     }
   }
 
-  // Get all verified developers for emulators in these listings
   const allListings = [...deviceListings]
   const emulatorIds = [...new Set(allListings.map((l) => l.emulatorId))]
   const verifiedDevs = await ctx.prisma.verifiedDeveloper.findMany({
@@ -100,7 +99,6 @@ export async function getDeviceCompatibility(
     select: { userId: true, emulatorId: true },
   })
 
-  // Create a map of verified developers: userId_emulatorId -> true
   const verifiedDevMap = new Set<string>()
   for (const vd of verifiedDevs) {
     verifiedDevMap.add(`${vd.userId}_${vd.emulatorId}`)
@@ -113,13 +111,11 @@ export async function getDeviceCompatibility(
 
   const deviceSystemAggregations = aggregateBySystem(listingsWithMetadata)
 
-  // Track data source info per system
   const dataSourceMap = new Map<
     string,
     { dataSource: 'device' | 'soc'; deviceCount: number; socCount: number; deviceIds: Set<string> }
   >()
 
-  // Initialize all device systems as 'device' source
   for (const sysAgg of deviceSystemAggregations) {
     dataSourceMap.set(sysAgg.system.id, {
       dataSource: 'device',
@@ -129,12 +125,10 @@ export async function getDeviceCompatibility(
     })
   }
 
-  // Identify systems needing SoC fallback
   const systemsNeedingFallback = deviceSystemAggregations.filter(
     (s) => s.listings.length < MINIMUM_DEVICE_LISTINGS,
   )
 
-  // If device has SoC AND some systems need fallback, fetch SoC data
   if (device.socId && systemsNeedingFallback.length > 0) {
     const socListings = await listingsRepo.getSocCompatibilityData(device.socId, device.id, {
       systemIds: systemsNeedingFallback.map((s) => s.system.id),
@@ -142,7 +136,6 @@ export async function getDeviceCompatibility(
     })
 
     if (socListings.length > 0) {
-      // Track unique devices contributing SoC data per system
       const socDevicesBySystem = new Map<string, Set<string>>()
 
       for (const listing of socListings) {
@@ -153,16 +146,13 @@ export async function getDeviceCompatibility(
         socDevicesBySystem.get(systemId)!.add(listing.deviceId)
       }
 
-      // Enhance SoC listings with metadata
       const socListingsWithMetadata: ScoringListingWithMetadata[] = socListings.map((listing) => ({
         ...listing,
         isVerifiedDeveloper: verifiedDevMap.has(`${listing.authorId}_${listing.emulatorId}`),
       }))
 
-      // Combine device + SoC listings
       listingsWithMetadata = [...listingsWithMetadata, ...socListingsWithMetadata]
 
-      // Update data source info for systems that used SoC fallback
       for (const sysAgg of systemsNeedingFallback) {
         const systemId = sysAgg.system.id
         const socDevices = socDevicesBySystem.get(systemId)
@@ -178,15 +168,12 @@ export async function getDeviceCompatibility(
     }
   }
 
-  // Re-aggregate with combined listings
   const systemAggregations = aggregateBySystem(listingsWithMetadata)
 
-  // Filter by minimum listing count
   const filteredSystems = systemAggregations.filter(
     (s) => s.listings.length >= (input.minListingCount ?? 1),
   )
 
-  // Convert to response format
   const systems: SystemCompatibility[] = filteredSystems.map((systemAgg) => {
     const confidence = calculateConfidenceLevel(systemAgg.listings.length, systemAgg.totalVotes)
     const sourceInfo = dataSourceMap.get(systemAgg.system.id)
@@ -255,7 +242,7 @@ export async function getDeviceCompatibility(
     cacheExpiresIn: 600, // 10 minutes
   }
 
-  catalogCompatibilityCache.set(cacheKey, response) // Cache the response
+  catalogCompatibilityCache.set(cacheKey, response)
 
   return response
 }
diff --git a/src/server/tgdb.ts b/src/server/tgdb.ts
index 5f449e587..c7262eb11 100644
--- a/src/server/tgdb.ts
+++ b/src/server/tgdb.ts
@@ -77,15 +77,12 @@ export async function searchGames(
     throw new TGDBError('Search query cannot be empty')
   }
 
-  // Get TGDB platform ID from system key
   const tgdbPlatformId = systemKey
     ? (PLATFORM_MAPPINGS.tgdb[systemKey as PlatformKey] ?? null)
     : null
 
-  // Create cache key for this search
   const cacheKey = createCacheKey('tgdb:searchGames', query.trim(), systemKey ?? 'none', page)
 
-  // Check cache first
   const cached = tgdbGamesCache.get(cacheKey)
   if (cached) return cached
 
@@ -95,12 +92,10 @@ export async function searchGames(
     include: 'boxart',
   }
 
-  // Add platform filter if platform ID is provided
   if (tgdbPlatformId) params['filter[platform]'] = tgdbPlatformId.toString()
 
   const response = await makeRequest<TGDBGamesByNameResponse>('/v1.1/Games/ByGameName', params)
 
-  // Score and sort the results if we have games
   if (response.data.games.length > 0) {
     const scoredResults = response.data.games.map((game, originalIndex) => ({
       game,
@@ -108,7 +103,6 @@ export async function searchGames(
       originalIndex,
     }))
 
-    // Sort by score (descending), then by original index (ascending) for ties
     scoredResults.sort((a, b) =>
       b.score !== a.score ? b.score - a.score : a.originalIndex - b.originalIndex,
     )
@@ -116,13 +110,11 @@ export async function searchGames(
     response.data.games = scoredResults.map((item) => item.game)
   }
 
-  // Cache the response
   tgdbGamesCache.set(cacheKey, response)
 
   return response
 }
 
-// Helper function to get boxart URL from search response
 export function getBoxartUrlFromGame(
   gameId: number,
   searchResponse: TGDBGamesByNameResponse,
@@ -130,7 +122,6 @@ export function getBoxartUrlFromGame(
   const gameIdStr = gameId.toString()
   const boxartData = searchResponse.include?.boxart?.data[gameIdStr]
 
-  // Early returns to avoid deep nesting
   if (!boxartData?.length) return undefined
   if (!searchResponse.include?.boxart?.base_url) return undefined
 
@@ -146,11 +137,9 @@ export async function getGameImages(gameIds: number[]): Promise<TGDBGamesImagesR
     throw new TGDBError('At least one game ID is required')
   }
 
-  // Create cache key for this images request
-  const sortedIds = [...gameIds].sort((a, b) => a - b) // Sort for consistent caching
+  const sortedIds = [...gameIds].sort((a, b) => a - b)
   const cacheKey = createCacheKey('tgdb:getGameImages', sortedIds.join(','))
 
-  // Check cache first
   const cached = tgdbImagesCache.get(cacheKey)
   if (cached) return cached
 
@@ -159,17 +148,14 @@ export async function getGameImages(gameIds: number[]): Promise<TGDBGamesImagesR
     'filter[type]': 'boxart,fanart,banner,screenshot,clearlogo,titlescreen',
   })
 
-  // Cache the response
   tgdbImagesCache.set(cacheKey, response)
 
   return response
 }
 
 export async function getPlatforms(): Promise<TGDBPlatformsResponse> {
-  // Create cache key for platforms (this data rarely changes)
   const cacheKey = createCacheKey('tgdb:getPlatforms')
 
-  // Check cache first
   const cached = tgdbPlatformsCache.get(cacheKey)
   if (cached) {
     return cached
@@ -177,8 +163,7 @@ export async function getPlatforms(): Promise<TGDBPlatformsResponse> {
 
   const response = await makeRequest<TGDBPlatformsResponse>('/v1/Platforms')
 
-  // Cache the response with longer TTL since platforms don't change often
-  tgdbPlatformsCache.set(cacheKey, response, 60 * 60 * 1000) // 1 hour
+  tgdbPlatformsCache.set(cacheKey, response, { ttl: 60 * 60 * 1000 })
 
   return response
 }
@@ -186,10 +171,8 @@ export async function getPlatforms(): Promise<TGDBPlatformsResponse> {
 export async function getGameImageUrls(
   gameId: number,
 ): Promise<{ boxartUrl?: string; bannerUrl?: string }> {
-  // Create cache key for this specific game's image URLs
   const cacheKey = createCacheKey('tgdb:getGameImageUrls', gameId)
 
-  // Check cache first
   const cached = tgdbImageUrlsCache.get(cacheKey)
   if (cached) return cached
 
@@ -199,7 +182,6 @@ export async function getGameImageUrls(
     const gameIdStr = gameId.toString()
     const gameImagesData = imagesResponse.data.images[gameIdStr] ?? []
 
-    // Use functional approach to find images
     const validImages = gameImagesData
       .filter((image) => image.filename && imagesResponse.data.base_url)
       .map((image) => ({
@@ -211,15 +193,12 @@ export async function getGameImageUrls(
     const boxartUrl = validImages.find((img) => img.type === 'boxart')?.fullUrl
     let bannerUrl = validImages.find((img) => img.type === 'banner')?.fullUrl
 
-    // Try alternative image types if banner is not found
     bannerUrl ??= validImages.find(
       (img) => img.type === 'fanart' || img.type === 'clearlogo',
     )?.fullUrl
 
-    // fallback to boxart if no banner found
     const result = { boxartUrl, bannerUrl: bannerUrl ? bannerUrl : boxartUrl }
 
-    // Cache the result
     tgdbImageUrlsCache.set(cacheKey, result)
 
     return result
@@ -233,13 +212,10 @@ export async function searchGameImages(
   query: string,
   systemKey?: string | null,
 ): Promise<Map<number, GameImageOption[]>> {
-  // Create cache key for this search
   const cacheKey = createCacheKey('tgdb:searchGameImages', query.trim(), systemKey ?? 'none')
 
-  // Check cache first - need to handle Map serialization
   const cached = tgdbGameImagesCache.get(cacheKey)
   if (cached) {
-    // Convert cached object back to Map
     const resultMap = new Map<number, GameImageOption[]>()
     Object.entries(cached).forEach(([gameId, images]) => {
       resultMap.set(parseInt(gameId), images)
@@ -251,22 +227,18 @@ export async function searchGameImages(
   const gameImageMap = new Map<number, GameImageOption[]>()
 
   if (gamesResponse.data.games.length === 0) {
-    // Cache empty result too
     tgdbGameImagesCache.set(cacheKey, {})
     return gameImageMap
   }
 
   try {
-    // Get images for all found games
     const gameIds = gamesResponse.data.games.map((game) => game.id)
     const imagesResponse = await getGameImages(gameIds)
 
-    // Process each game's images using functional approach
     gamesResponse.data.games.forEach((game) => {
       const boxartImages = createBoxartImages(game, gamesResponse)
       const otherImages = createOtherImages(game, imagesResponse)
 
-      // Combine and deduplicate images based on URL
       const allImages = [...boxartImages, ...otherImages]
       const uniqueImages = allImages.reduce((acc, image) => {
         const existingImage = acc.find((existing) => existing.url === image.url)
@@ -276,7 +248,6 @@ export async function searchGameImages(
       if (uniqueImages.length > 0) gameImageMap.set(game.id, uniqueImages)
     })
 
-    // Cache the result - convert Map to plain object for JSON serialization
     const cacheObject: Record<string, GameImageOption[]> = {}
     gameImageMap.forEach((images, gameId) => {
       cacheObject[gameId.toString()] = images
@@ -284,13 +255,11 @@ export async function searchGameImages(
     tgdbGameImagesCache.set(cacheKey, cacheObject)
   } catch (error) {
     console.error('Error fetching game images from TGDB:', error)
-    // Continue without images rather than failing completely
   }
 
   return gameImageMap
 }
 
-// Helper function to create boxart images from games response
 function createBoxartImages(
   game: TGDBGamesByNameResponse['data']['games'][0],
   gamesResponse: TGDBGamesByNameResponse,
@@ -309,7 +278,7 @@ function createBoxartImages(
         url,
         resolution: boxart.resolution,
         id: boxart.id,
-        index, // Add index to ensure uniqueness
+        index,
       }
     })
     .filter((boxart) => isValidImageUrl(boxart.url))
@@ -325,11 +294,6 @@ function createBoxartImages(
     }))
 }
 
-/**
- * Creates other image types (fanart, banner, etc.) from TGDB images response.
- * @param game - The game object from TGDB search response.
- * @param imagesResponse - The full images response from TGDB.
- */
 function createOtherImages(
   game: TGDBGamesByNameResponse['data']['games'][0],
   imagesResponse: TGDBGamesImagesResponse,
diff --git a/src/server/utils/cache/MemoryCache.test.ts b/src/server/utils/cache/MemoryCache.test.ts
deleted file mode 100644
index 18d8c1ec1..000000000
--- a/src/server/utils/cache/MemoryCache.test.ts
+++ /dev/null
@@ -1,263 +0,0 @@
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
-import MemoryCache from './MemoryCache'
-
-describe('MemoryCache', () => {
-  let cache: MemoryCache<string>
-  let consoleDebugSpy: ReturnType<typeof vi.spyOn>
-
-  beforeEach(() => {
-    // Mock console.debug to suppress cache cleanup messages
-    consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {})
-
-    cache = new MemoryCache<string>({
-      ttl: 50, // 50ms - much faster for tests
-      maxSize: 3,
-      cleanupInterval: 25, // 25ms - faster cleanup
-    })
-  })
-
-  afterEach(() => {
-    cache.destroy()
-    consoleDebugSpy.mockRestore()
-  })
-
-  describe('basic operations', () => {
-    it('should store and retrieve values', () => {
-      cache.set('key1', 'value1')
-      expect(cache.get('key1')).toBe('value1')
-    })
-
-    it('should return undefined for non-existent keys', () => {
-      expect(cache.get('nonexistent')).toBeUndefined()
-    })
-
-    it('should delete values', () => {
-      cache.set('key1', 'value1')
-      expect(cache.delete('key1')).toBe(true)
-      expect(cache.get('key1')).toBeUndefined()
-    })
-
-    it('should return false when deleting non-existent keys', () => {
-      expect(cache.delete('nonexistent')).toBe(false)
-    })
-
-    it('should clear all values', () => {
-      cache.set('key1', 'value1')
-      cache.set('key2', 'value2')
-      cache.clear()
-      expect(cache.get('key1')).toBeUndefined()
-      expect(cache.get('key2')).toBeUndefined()
-      expect(cache.getSize()).toBe(0)
-    })
-  })
-
-  describe('TTL (Time To Live)', () => {
-    it('should expire entries after TTL', async () => {
-      cache.set('key1', 'value1')
-      expect(cache.get('key1')).toBe('value1')
-
-      // Wait for expiration (now much faster - 60ms)
-      await new Promise((resolve) => setTimeout(resolve, 60))
-
-      expect(cache.get('key1')).toBeUndefined()
-    })
-
-    it('should support custom TTL per entry', async () => {
-      cache.set('short', 'value1', 10) // 10ms TTL
-      cache.set('long', 'value2', 100) // 100ms TTL
-
-      await new Promise((resolve) => setTimeout(resolve, 15))
-
-      expect(cache.get('short')).toBeUndefined()
-      expect(cache.get('long')).toBe('value2')
-    })
-
-    it('should update lastAccessed on get', () => {
-      cache.set('key1', 'value1')
-
-      // Access the key multiple times
-      cache.get('key1')
-      cache.get('key1')
-
-      expect(cache.get('key1')).toBe('value1')
-    })
-  })
-
-  describe('LRU eviction', () => {
-    it('should evict least recently used items when at capacity', () => {
-      // Fill cache to capacity (maxSize: 3)
-      cache.set('key1', 'value1')
-      cache.set('key2', 'value2')
-      cache.set('key3', 'value3')
-
-      // Access key1 to make it recently used
-      cache.get('key1')
-
-      // Add fourth item, should evict key2 (least recently used)
-      cache.set('key4', 'value4')
-
-      expect(cache.get('key1')).toBe('value1') // Still present (recently accessed)
-      expect(cache.get('key2')).toBeUndefined() // Evicted
-      expect(cache.get('key3')).toBe('value3') // Still present
-      expect(cache.get('key4')).toBe('value4') // Newly added
-    })
-
-    it('should not evict when updating existing keys', () => {
-      cache.set('key1', 'value1')
-      cache.set('key2', 'value2')
-      cache.set('key3', 'value3')
-
-      // Update existing key (should not trigger eviction)
-      cache.set('key1', 'updated_value1')
-
-      expect(cache.getSize()).toBe(3)
-      expect(cache.get('key1')).toBe('updated_value1')
-      expect(cache.get('key2')).toBe('value2')
-      expect(cache.get('key3')).toBe('value3')
-    })
-  })
-
-  describe('pattern invalidation', () => {
-    it('should invalidate entries matching pattern', () => {
-      // Clear cache to ensure we start fresh for this test
-      cache.clear()
-
-      cache.set('user:1:profile', 'profile1')
-      cache.set('user:1:settings', 'settings1')
-      cache.set('user:2:profile', 'profile2')
-      // Don't add the 4th item to avoid LRU eviction (maxSize: 3)
-
-      const deletedCount = cache.invalidatePattern('user:1:*')
-
-      expect(deletedCount).toBe(2)
-      expect(cache.get('user:1:profile')).toBeUndefined()
-      expect(cache.get('user:1:settings')).toBeUndefined()
-      expect(cache.get('user:2:profile')).toBe('profile2')
-
-      // Now add the product item separately to test it wasn't affected
-      cache.set('product:1:info', 'product1')
-      expect(cache.get('product:1:info')).toBe('product1')
-    })
-
-    it('should handle complex patterns', () => {
-      cache.set('api:v1:users', 'users')
-      cache.set('api:v1:products', 'products')
-      cache.set('api:v2:users', 'users_v2')
-
-      const deletedCount = cache.invalidatePattern('api:v1:*')
-
-      expect(deletedCount).toBe(2)
-      expect(cache.get('api:v2:users')).toBe('users_v2')
-    })
-  })
-
-  describe('statistics', () => {
-    it('should track cache statistics', () => {
-      const initialStats = cache.getStats()
-      expect(initialStats.hits).toBe(0)
-      expect(initialStats.misses).toBe(0)
-      expect(initialStats.size).toBe(0)
-
-      cache.set('key1', 'value1')
-      cache.get('key1') // Hit
-      cache.get('nonexistent') // Miss
-
-      const stats = cache.getStats()
-      expect(stats.hits).toBe(1)
-      expect(stats.misses).toBe(1)
-      expect(stats.size).toBe(1)
-      expect(stats.maxSize).toBe(3)
-    })
-
-    it('should track evictions', () => {
-      // Fill cache beyond capacity to trigger evictions
-      cache.set('key1', 'value1')
-      cache.set('key2', 'value2')
-      cache.set('key3', 'value3')
-      cache.set('key4', 'value4') // Should trigger eviction
-
-      const stats = cache.getStats()
-      expect(stats.evictions).toBe(1)
-    })
-  })
-
-  describe('cleanup and memory management', () => {
-    it('should automatically cleanup expired entries', async () => {
-      cache.set('temp1', 'value1', 10) // 10ms TTL
-      cache.set('temp2', 'value2', 10) // 10ms TTL
-      cache.set('permanent', 'value3', 200) // 200ms TTL
-
-      expect(cache.getSize()).toBe(3)
-
-      // Wait for cleanup to run (cleanupInterval: 25ms, entries expire after 10ms)
-      await new Promise((resolve) => setTimeout(resolve, 35))
-
-      expect(cache.getSize()).toBe(1)
-      expect(cache.get('permanent')).toBe('value3')
-    })
-
-    it('should destroy properly and clear timer', () => {
-      const destroySpy = vi.spyOn(cache, 'destroy')
-
-      cache.destroy()
-
-      expect(destroySpy).toHaveBeenCalled()
-      expect(cache.getSize()).toBe(0)
-    })
-  })
-
-  describe('type safety', () => {
-    it('should work with different value types', () => {
-      const numberCache = new MemoryCache<number>({
-        ttl: 1000,
-        maxSize: 10,
-      })
-
-      const objectCache = new MemoryCache<{ id: number; name: string }>({
-        ttl: 1000,
-        maxSize: 10,
-      })
-
-      numberCache.set('count', 42)
-      objectCache.set('user', { id: 1, name: 'John' })
-
-      expect(numberCache.get('count')).toBe(42)
-      expect(objectCache.get('user')).toEqual({ id: 1, name: 'John' })
-
-      numberCache.destroy()
-      objectCache.destroy()
-    })
-  })
-
-  describe('edge cases', () => {
-    it('should handle rapid successive operations', () => {
-      // Rapidly set and get many values
-      for (let i = 0; i < 100; i++) {
-        cache.set(`key${i}`, `value${i}`)
-      }
-
-      // Should only keep the last 3 due to maxSize: 3
-      expect(cache.getSize()).toBe(3)
-      expect(cache.get('key99')).toBe('value99')
-      expect(cache.get('key98')).toBe('value98')
-      expect(cache.get('key97')).toBe('value97')
-    })
-
-    it('should handle setting the same key multiple times', () => {
-      cache.set('key1', 'value1')
-      cache.set('key1', 'value2')
-      cache.set('key1', 'value3')
-
-      expect(cache.get('key1')).toBe('value3')
-      expect(cache.getSize()).toBe(1)
-    })
-
-    it('should handle empty string keys and values', () => {
-      cache.set('', 'empty_key')
-      cache.set('empty_value', '')
-
-      expect(cache.get('')).toBe('empty_key')
-      expect(cache.get('empty_value')).toBe('')
-    })
-  })
-})
diff --git a/src/server/utils/cache/MemoryCache.ts b/src/server/utils/cache/MemoryCache.ts
deleted file mode 100644
index 0b99c3c06..000000000
--- a/src/server/utils/cache/MemoryCache.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-/**
- * High-performance in-memory cache with TTL, LRU eviction, and memory management
- * Features: Time-to-live, Least Recently Used eviction, automatic cleanup, cache statistics
- */
-
-interface CacheEntry<T> {
-  value: T
-  expires: number
-  lastAccessed: number
-  createdAt: number
-}
-
-interface CacheStats {
-  hits: number
-  misses: number
-  size: number
-  maxSize: number
-  evictions: number
-}
-
-interface CacheOptions {
-  ttl: number // Time to live in milliseconds
-  maxSize: number // Maximum number of entries
-  cleanupInterval?: number // Cleanup interval in milliseconds (default: 5 minutes)
-}
-
-class MemoryCache<T> {
-  private cache = new Map<string, CacheEntry<T>>()
-  private readonly options: Required<CacheOptions>
-  private cleanupTimer: NodeJS.Timeout | null = null
-  private entryCounter = 0 // Add counter for unique timestamps
-  private stats: CacheStats = {
-    hits: 0,
-    misses: 0,
-    size: 0,
-    maxSize: 0,
-    evictions: 0,
-  }
-
-  constructor(options: CacheOptions) {
-    this.options = {
-      cleanupInterval: 5 * 60 * 1000, // 5 minutes default
-      ...options,
-    }
-    this.stats.maxSize = options.maxSize
-    this.startCleanupTimer()
-  }
-
-  get(key: string): T | undefined {
-    const entry = this.cache.get(key)
-
-    if (!entry) {
-      this.stats.misses++
-      return undefined
-    }
-
-    const now = Date.now()
-
-    // Check if expired
-    if (entry.expires < now) {
-      this.cache.delete(key)
-      this.stats.misses++
-      this.stats.size--
-      return undefined
-    }
-
-    // Update last accessed time for LRU with unique ordering
-    entry.lastAccessed = now + this.entryCounter++ * 0.001
-    this.stats.hits++
-    return entry.value
-  }
-
-  set(key: string, value: T, customTtl?: number): void {
-    const now = Date.now()
-    const ttl = customTtl ?? this.options.ttl
-
-    const entry: CacheEntry<T> = {
-      value,
-      expires: now + ttl,
-      lastAccessed: now + this.entryCounter++ * 0.001, // Add microsecond precision
-      createdAt: now,
-    }
-
-    const isExistingKey = this.cache.has(key)
-
-    // If we're at capacity and this is a new key, evict LRU
-    if (!isExistingKey && this.cache.size >= this.options.maxSize) {
-      this.evictLRU()
-    }
-
-    this.cache.set(key, entry)
-
-    // Update stats only for new keys
-    if (!isExistingKey) {
-      this.stats.size++
-    }
-  }
-
-  delete(key: string): boolean {
-    const deleted = this.cache.delete(key)
-    if (deleted) {
-      this.stats.size--
-    }
-    return deleted
-  }
-
-  clear(): void {
-    this.cache.clear()
-    this.stats.size = 0
-  }
-
-  invalidatePattern(pattern: string): number {
-    // Escape special regex characters except for our wildcard *
-    const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*')
-    const regex = new RegExp(`^${escapedPattern}$`)
-    let deletedCount = 0
-
-    // Collect keys to delete first to avoid modifying during iteration
-    const keysToDelete: string[] = []
-    for (const key of this.cache.keys()) {
-      if (regex.test(key)) {
-        keysToDelete.push(key)
-      }
-    }
-
-    // Delete the collected keys
-    for (const key of keysToDelete) {
-      this.cache.delete(key)
-      deletedCount++
-    }
-
-    // Update stats.size to match actual cache size
-    this.stats.size = this.cache.size
-
-    return deletedCount
-  }
-
-  getStats(): Readonly<CacheStats> {
-    return { ...this.stats }
-  }
-
-  getSize(): number {
-    return this.cache.size
-  }
-
-  getCreatedAt(key: string): Date | null {
-    const entry = this.cache.get(key)
-    if (!entry) return null
-
-    // Update last accessed time
-    entry.lastAccessed = Date.now()
-
-    return new Date(entry.createdAt)
-  }
-
-  destroy(): void {
-    if (this.cleanupTimer) {
-      clearInterval(this.cleanupTimer)
-      this.cleanupTimer = null
-    }
-    this.clear()
-  }
-
-  private evictLRU(): void {
-    let oldestKey: string | null = null
-    let oldestTime = Infinity
-
-    for (const [key, entry] of this.cache.entries()) {
-      if (entry.lastAccessed < oldestTime) {
-        oldestTime = entry.lastAccessed
-        oldestKey = key
-      }
-    }
-
-    if (oldestKey) {
-      this.cache.delete(oldestKey)
-      this.stats.evictions++
-      this.stats.size--
-    }
-  }
-
-  private startCleanupTimer(): void {
-    this.cleanupTimer = setInterval(() => {
-      this.cleanup()
-    }, this.options.cleanupInterval)
-    // Allow process to exit if this is the only active timer
-    this.cleanupTimer.unref?.()
-  }
-
-  private cleanup(): void {
-    const now = Date.now()
-    let cleanedCount = 0
-
-    for (const [key, entry] of this.cache.entries()) {
-      if (entry.expires < now) {
-        this.cache.delete(key)
-        cleanedCount++
-      }
-    }
-
-    // Update stats.size to match actual cache size
-    this.stats.size = this.cache.size
-
-    if (cleanedCount > 0) {
-      console.debug(`Cache cleanup: removed ${cleanedCount} expired entries`)
-    }
-  }
-}
-
-export default MemoryCache
diff --git a/src/server/utils/cache/index.ts b/src/server/utils/cache/index.ts
index 93bc865f6..32d7510c1 100644
--- a/src/server/utils/cache/index.ts
+++ b/src/server/utils/cache/index.ts
@@ -1,8 +1,2 @@
-/**
- * Cache module exports
- * in-memory caching with TTL, LRU eviction, and type safety
- */
-
-export { default as MemoryCache } from './MemoryCache'
 export * from './instances'
 export * from './utils'
diff --git a/src/server/utils/cache/instances.ts b/src/server/utils/cache/instances.ts
index 8c8338487..eefdc6bba 100644
--- a/src/server/utils/cache/instances.ts
+++ b/src/server/utils/cache/instances.ts
@@ -1,10 +1,5 @@
-/**
- * Cache instances for different parts of the application
- * Each cache is typed for its specific use case
- */
-
+import { LRUCache } from 'lru-cache'
 import { TIME_CONSTANTS } from '@/utils/time'
-import MemoryCache from './MemoryCache'
 import type { DeviceCompatibilityResponse } from '@/schemas/mobile'
 import type { BatchBySteamAppIdsResponse } from '@/server/api/routers/mobile/games'
 import type {
@@ -23,30 +18,34 @@ import type {
 } from '@/types/tgdb'
 import type { NotificationType } from '@orm/client'
 
-// Game statistics cache
-export const gameStatsCache = new MemoryCache<{
-  pending: number
-  approved: number
-  rejected: number
-  total: number
-}>({
+export const gameStatsCache = new LRUCache<
+  string,
+  {
+    pending: number
+    approved: number
+    rejected: number
+    total: number
+  }
+>({
   ttl: TIME_CONSTANTS.FIVE_MINUTES,
-  maxSize: 100, // Small cache for stats
+  max: 100,
 })
 
-// Listing statistics cache
-export const listingStatsCache = new MemoryCache<{
-  pending: number
-  approved: number
-  rejected: number
-  total: number
-}>({
+export const listingStatsCache = new LRUCache<
+  string,
+  {
+    pending: number
+    approved: number
+    rejected: number
+    total: number
+  }
+>({
   ttl: TIME_CONSTANTS.FIVE_MINUTES,
-  maxSize: 100, // Small cache for stats
+  max: 100,
 })
 
-// Notification analytics cache - for expensive analytics queries
-export const notificationAnalyticsCache = new MemoryCache<
+export const notificationAnalyticsCache = new LRUCache<
+  string,
   | NotificationMetrics
   | ChannelMetrics
   | TypeMetrics
@@ -59,53 +58,64 @@ export const notificationAnalyticsCache = new MemoryCache<
       clickRate: number
     }[]
 >({
-  ttl: TIME_CONSTANTS.TEN_MINUTES, // analytics can be slightly stale
-  maxSize: 200, // Analytics queries with different date ranges and params
+  ttl: TIME_CONSTANTS.TEN_MINUTES,
+  max: 200,
 })
 
-// TGDB-specific caches with proper typing
-export const tgdbGamesCache = new MemoryCache<TGDBGamesByNameResponse>({
+export const tgdbGamesCache = new LRUCache<string, TGDBGamesByNameResponse>({
   ttl: TIME_CONSTANTS.TEN_MINUTES,
-  maxSize: 200,
+  max: 200,
 })
 
-export const tgdbImagesCache = new MemoryCache<TGDBGamesImagesResponse>({
+export const tgdbImagesCache = new LRUCache<string, TGDBGamesImagesResponse>({
   ttl: TIME_CONSTANTS.TEN_MINUTES,
-  maxSize: 200,
+  max: 200,
 })
 
-export const tgdbPlatformsCache = new MemoryCache<TGDBPlatformsResponse>({
-  ttl: TIME_CONSTANTS.TEN_MINUTES, // rarely change
-  maxSize: 10,
+export const tgdbPlatformsCache = new LRUCache<string, TGDBPlatformsResponse>({
+  ttl: TIME_CONSTANTS.TEN_MINUTES,
+  max: 10,
 })
 
-export const tgdbImageUrlsCache = new MemoryCache<{
-  boxartUrl?: string
-  bannerUrl?: string
-}>({
+export const tgdbImageUrlsCache = new LRUCache<
+  string,
+  {
+    boxartUrl?: string
+    bannerUrl?: string
+  }
+>({
   ttl: TIME_CONSTANTS.TEN_MINUTES,
-  maxSize: 500,
+  max: 500,
 })
 
-export const tgdbGameImagesCache = new MemoryCache<Record<string, GameImageOption[]>>({
+export const tgdbGameImagesCache = new LRUCache<string, Record<string, GameImageOption[]>>({
   ttl: TIME_CONSTANTS.TEN_MINUTES,
-  maxSize: 100,
+  max: 100,
 })
 
-// Driver version cache to avoid hitting GitHub rate limits
-export const driverVersionsCache = new MemoryCache<DriverVersionsResponse>({
+export const driverVersionsCache = new LRUCache<string, DriverVersionsResponse>({
   ttl: TIME_CONSTANTS.THIRTY_MINUTES,
-  maxSize: 1,
+  max: 1,
 })
 
-// Batch Steam App ID lookup cache - for GameHub Lite integration
-export const steamBatchQueryCache = new MemoryCache<BatchBySteamAppIdsResponse>({
-  ttl: TIME_CONSTANTS.TEN_MINUTES, // listings data changes frequently
-  maxSize: 100, // Cache up to 100 different batch queries
+export const steamBatchQueryCache = new LRUCache<string, BatchBySteamAppIdsResponse>({
+  ttl: TIME_CONSTANTS.TEN_MINUTES,
+  max: 100,
 })
 
-// Catalog compatibility cache - for RetroCatalog integration
-export const catalogCompatibilityCache = new MemoryCache<DeviceCompatibilityResponse>({
-  ttl: TIME_CONSTANTS.TEN_MINUTES, // balance freshness with server load
-  maxSize: 500, // cache popular device queries
+export const catalogCompatibilityCache = new LRUCache<string, DeviceCompatibilityResponse>({
+  ttl: TIME_CONSTANTS.TEN_MINUTES,
+  max: 500,
 })
+
+export function invalidateCatalogCompatibilityCacheForDevice(deviceId: string): number {
+  const prefix = `device:${deviceId}:`
+  let deleted = 0
+
+  for (const key of catalogCompatibilityCache.keys()) {
+    if (!key.startsWith(prefix)) continue
+    if (catalogCompatibilityCache.delete(key)) deleted++
+  }
+
+  return deleted
+}
diff --git a/src/server/utils/driver-versions.ts b/src/server/utils/driver-versions.ts
index 9d4508bd0..4b2abb80d 100644
--- a/src/server/utils/driver-versions.ts
+++ b/src/server/utils/driver-versions.ts
@@ -144,7 +144,7 @@ export async function getDriverVersions(): Promise<DriverVersionsResponse> {
       releases,
       rateLimited: false,
     }
-    driverVersionsCache.set(CACHE_KEY, payload, ms.minutes(30))
+    driverVersionsCache.set(CACHE_KEY, payload, { ttl: ms.minutes(30) })
     return payload
   } catch (error) {
     if (isRateLimitError(error)) {
@@ -154,7 +154,7 @@ export async function getDriverVersions(): Promise<DriverVersionsResponse> {
         rateLimited: true,
         errorMessage: 'GitHub rate limit exceeded. Try again in a few minutes.',
       }
-      driverVersionsCache.set(CACHE_KEY, payload, ms.minutes(5))
+      driverVersionsCache.set(CACHE_KEY, payload, { ttl: ms.minutes(5) })
       return payload
     }
 
@@ -164,7 +164,7 @@ export async function getDriverVersions(): Promise<DriverVersionsResponse> {
       rateLimited: false,
       errorMessage: 'Failed to fetch driver versions. Please try again later.',
     }
-    driverVersionsCache.set(CACHE_KEY, payload, ms.minutes(2))
+    driverVersionsCache.set(CACHE_KEY, payload, { ttl: ms.minutes(2) })
     return payload
   }
 }
diff --git a/src/server/utils/steamGameBatcher.ts b/src/server/utils/steamGameBatcher.ts
index cd76b17d4..0b64197bc 100644
--- a/src/server/utils/steamGameBatcher.ts
+++ b/src/server/utils/steamGameBatcher.ts
@@ -1,14 +1,7 @@
-/**
- * Steam Game Batch Matcher
- * Efficiently matches Steam App IDs to games in the database
- * Optimized for large batches (up to 1000 Steam App IDs)
- */
-
+import { LRUCache } from 'lru-cache'
 import { ms } from '@/utils/time'
-import { MemoryCache } from './cache'
 import { getSteamGamesData } from './steamGameSearch'
 
-// Validation constants
 const MAX_STEAM_APP_ID = 10000000
 const MAX_BATCH_SIZE = 1000
 
@@ -23,55 +16,41 @@ interface GameMatchResult {
   matchStrategy: 'metadata' | 'exact' | 'normalized' | 'not_found'
 }
 
-// Cache for Steam App ID → Game Name mappings (1 hour TTL)
-const steamAppNameCache = new MemoryCache<string>({
+const steamAppNameCache = new LRUCache<string, string>({
   ttl: ms.hours(1),
-  maxSize: 10000, // Cache up to 10k mappings
+  max: 10000,
 })
 
-/**
- * Normalize game titles for better matching
- * Removes special characters, extra spaces, and converts to lowercase
- */
 export function normalizeGameTitle(title: string): string {
   return title
     .toLowerCase()
-    .replace(/[™®©]/g, '') // Remove trademark symbols
-    .replace(/[:\-–—]/g, ' ') // Convert separators to spaces
-    .replace(/\s+/g, ' ') // Collapse multiple spaces
-    .replace(/[^\w\s]/g, '') // Remove special characters
+    .replace(/[™®©]/g, '')
+    .replace(/[:\-–—]/g, ' ')
+    .replace(/\s+/g, ' ')
+    .replace(/[^\w\s]/g, '')
     .trim()
 }
 
-/**
- * Get Steam app information from our cached Steam app list
- * This reuses the cache from our existing Steam App ID search feature
- */
 async function getSteamAppInfo(appIds: string[]): Promise<Map<string, SteamAppInfo>> {
   const result = new Map<string, SteamAppInfo>()
 
   try {
-    // Get all Steam apps from cache (already implemented in steamGameSearch.ts)
     const allSteamApps = await getSteamGamesData()
 
-    // Create a lookup map for O(1) access
     const steamAppMap = new Map<string, SteamAppInfo>()
     for (const app of allSteamApps) {
       steamAppMap.set(String(app.appid), app)
     }
 
-    // Look up each requested app ID
     for (const appId of appIds) {
       const cacheKey = `steam-app-${appId}`
 
-      // Try cache first
       const cachedName = steamAppNameCache.get(cacheKey)
       if (cachedName) {
         result.set(appId, { appid: Number(appId), name: cachedName })
         continue
       }
 
-      // Lookup in Steam app list
       const appInfo = steamAppMap.get(appId)
       if (appInfo) {
         result.set(appId, appInfo)
@@ -85,14 +64,9 @@ async function getSteamAppInfo(appIds: string[]): Promise<Map<string, SteamAppIn
   return result
 }
 
-/**
- * Match Steam App IDs to game names
- * Returns a map of steamAppId → game name (or null if not found)
- */
 export async function matchSteamAppIdsToNames(steamAppIds: string[]): Promise<GameMatchResult[]> {
   const results: GameMatchResult[] = []
 
-  // Get Steam app information for all requested IDs
   const steamApps = await getSteamAppInfo(steamAppIds)
 
   for (const appId of steamAppIds) {
@@ -102,7 +76,7 @@ export async function matchSteamAppIdsToNames(steamAppIds: string[]): Promise<Ga
       results.push({
         steamAppId: appId,
         gameName: appInfo.name,
-        matchStrategy: 'exact', // Will be determined by the database query
+        matchStrategy: 'exact',
       })
     } else {
       results.push({
@@ -116,9 +90,6 @@ export async function matchSteamAppIdsToNames(steamAppIds: string[]): Promise<Ga
   return results
 }
 
-/**
- * Create normalized title mappings for fallback matching
- */
 export function createNormalizedTitleMap(gameMatches: GameMatchResult[]): Map<string, string> {
   const normalizedMap = new Map<string, string>()
 
@@ -132,17 +103,12 @@ export function createNormalizedTitleMap(gameMatches: GameMatchResult[]): Map<st
   return normalizedMap
 }
 
-/**
- * Batch validation for Steam App IDs
- * Ensures all IDs are valid and within reasonable limits
- */
 export function validateSteamAppIds(steamAppIds: string[]): {
   valid: boolean
   errors: string[]
 } {
   const errors: string[] = []
 
-  // Check batch size
   if (steamAppIds.length === 0) {
     errors.push('Batch cannot be empty')
   }
@@ -151,26 +117,22 @@ export function validateSteamAppIds(steamAppIds: string[]): {
     errors.push(`Batch size exceeds maximum limit of ${MAX_BATCH_SIZE}`)
   }
 
-  // Check for duplicates
   const uniqueIds = new Set(steamAppIds)
   if (uniqueIds.size !== steamAppIds.length) {
     errors.push('Batch contains duplicate Steam App IDs')
   }
 
-  // Validate each ID format
   for (const appId of steamAppIds) {
     if (!appId || typeof appId !== 'string') {
       errors.push(`Invalid Steam App ID format: ${appId}`)
       break
     }
 
-    // Steam App IDs are numeric strings
     if (!/^\d+$/.test(appId)) {
       errors.push(`Invalid Steam App ID format: ${appId}`)
       break
     }
 
-    // Reasonable range check (Steam App IDs are typically < 10 million)
     const numericId = Number(appId)
     if (numericId < 0 || numericId > MAX_STEAM_APP_ID) {
       errors.push(`Steam App ID out of valid range: ${appId}`)
diff --git a/src/server/utils/steamGameSearch.ts b/src/server/utils/steamGameSearch.ts
index f1b4ba0c7..b4ce70a1b 100644
--- a/src/server/utils/steamGameSearch.ts
+++ b/src/server/utils/steamGameSearch.ts
@@ -1,13 +1,7 @@
-/**
- * Steam game data management with fuzzy search capabilities
- * Fetches and caches Steam game data from Steam Web API with periodic updates
- */
-
 import Fuse from 'fuse.js'
+import { LRUCache } from 'lru-cache'
 import { ms } from '@/utils/time'
-import { MemoryCache } from './cache'
 
-// Types for Steam game data
 interface SteamAppEntry {
   appid: number
   name: string
@@ -28,19 +22,21 @@ interface SteamGameSearchResult {
   score: number
 }
 
-// Cache for Steam games data - longer TTL since the list is large and updates periodically
-const steamGamesDataCache = new MemoryCache<SteamAppEntry[]>({
+interface CachedData<T> {
+  data: T
+  createdAt: Date
+}
+
+const steamGamesDataCache = new LRUCache<string, CachedData<SteamAppEntry[]>>({
   ttl: ms.days(1),
-  maxSize: 1,
+  max: 1,
 })
 
-// Cache for Fuse.js search instance - rebuild when data updates
-const steamGamesFuseCache = new MemoryCache<Fuse<SteamAppEntry>>({
+const steamGamesFuseCache = new LRUCache<string, Fuse<SteamAppEntry>>({
   ttl: ms.days(1),
-  maxSize: 1,
+  max: 1,
 })
 
-// Configuration for fuzzy search
 const FUSE_OPTIONS = {
   keys: [{ name: 'name', weight: 1.0 }],
   threshold: 0.4,
@@ -54,16 +50,11 @@ const FUSE_OPTIONS = {
 
 // ISteamApps/GetAppList/v2 was removed by Valve. The replacement requires a key.
 const STEAM_STORE_API_URL = 'https://api.steampowered.com/IStoreService/GetAppList/v1/'
-const FETCH_TIMEOUT_MS = 30_000 // 30s — avoids hanging on serverless cold starts
-const PAGE_SIZE = 50_000 // max allowed by the API
+const FETCH_TIMEOUT_MS = 30_000
+const PAGE_SIZE = 50_000
 
-// In-flight deduplication: prevents concurrent cold starts from each firing a fetch
 let inflightFetch: Promise<SteamAppEntry[]> | null = null
 
-/**
- * Fetches Steam games data from IStoreService/GetAppList/v1 with pagination.
- * Requires STEAM_API_KEY env var.
- */
 async function fetchSteamGamesData(): Promise<SteamAppEntry[]> {
   const apiKey = process.env.STEAM_API_KEY
   if (!apiKey) {
@@ -119,16 +110,12 @@ async function fetchSteamGamesData(): Promise<SteamAppEntry[]> {
   return allApps
 }
 
-/**
- * Gets cached Steam games data or fetches fresh data
- */
 export async function getSteamGamesData(): Promise<SteamAppEntry[]> {
   const cacheKey = 'steam-games-data'
 
   const cachedData = steamGamesDataCache.get(cacheKey)
-  if (cachedData) return cachedData
+  if (cachedData) return cachedData.data
 
-  // Deduplicate concurrent fetches so only one in-flight request runs at a time
   if (!inflightFetch) {
     inflightFetch = fetchSteamGamesData().finally(() => {
       inflightFetch = null
@@ -137,7 +124,7 @@ export async function getSteamGamesData(): Promise<SteamAppEntry[]> {
 
   try {
     const freshData = await inflightFetch
-    steamGamesDataCache.set(cacheKey, freshData)
+    steamGamesDataCache.set(cacheKey, { data: freshData, createdAt: new Date() })
     return freshData
   } catch (error) {
     const message = error instanceof Error ? error.message : String(error)
@@ -149,17 +136,12 @@ export async function getSteamGamesData(): Promise<SteamAppEntry[]> {
   }
 }
 
-/**
- * Gets or creates Fuse.js search instance
- */
 async function getFuseInstance(): Promise<Fuse<SteamAppEntry>> {
   const cacheKey = 'steam-games-fuse'
 
-  // Try to get from cache first
   const cachedFuse = steamGamesFuseCache.get(cacheKey)
   if (cachedFuse) return cachedFuse
 
-  // Create new Fuse instance with fresh data
   const gamesData = await getSteamGamesData()
   const fuse = new Fuse(gamesData, FUSE_OPTIONS)
 
@@ -167,9 +149,6 @@ async function getFuseInstance(): Promise<Fuse<SteamAppEntry>> {
   return fuse
 }
 
-/**
- * Normalizes a title for better matching
- */
 function normalizeTitle(title: string): string {
   return title
     .toLowerCase()
@@ -178,12 +157,6 @@ function normalizeTitle(title: string): string {
     .trim()
 }
 
-/**
- * Searches for Steam App ID based on game name using fuzzy matching
- * @param gameName - The name of the game to search for
- * @param maxResults - Maximum number of results to return (default: 5)
- * @returns Array of search results with app IDs and match scores
- */
 export async function findSteamAppIdForGameName(
   gameName: string,
   maxResults: number = 5,
@@ -195,17 +168,14 @@ export async function findSteamAppIdForGameName(
     const searchTerm = gameName.trim().toLowerCase()
     const searchResults = fuse.search(searchTerm, { limit: maxResults * 2 })
 
-    // Filter and enhance results with better scoring
     const enhancedResults = searchResults
       .map((result) => {
         const item = result.item
         const fuseScore = result.score || 0
 
-        // Calculate enhanced score based on multiple factors
         const nameMatch = item.name.toLowerCase()
         const normalizedMatch = normalizeTitle(item.name)
 
-        // Exact match bonus
         let bonusScore = 0
         if (nameMatch === searchTerm || normalizedMatch === normalizeTitle(searchTerm)) {
           bonusScore = 40
@@ -213,7 +183,6 @@ export async function findSteamAppIdForGameName(
           bonusScore = 20
         }
 
-        // Word match bonus
         const searchWords = searchTerm.split(' ').filter((w) => w.length > 2)
         const nameWords = nameMatch.split(' ')
 
@@ -224,7 +193,6 @@ export async function findSteamAppIdForGameName(
           }
         })
 
-        // Penalize soundtrack/dlc/demo entries unless specifically searched for
         let contentBonus = 0
         const lowerName = nameMatch
         const isDlc =
@@ -243,7 +211,6 @@ export async function findSteamAppIdForGameName(
         if (isSoundtrack && !searchesForSoundtrack) contentBonus = -15
         if (isDemo && !searchesForDemo) contentBonus = -15
 
-        // Final score calculation
         const baseScore = Math.round((1 - fuseScore) * 100)
         const finalScore = Math.min(
           100,
@@ -262,7 +229,6 @@ export async function findSteamAppIdForGameName(
       })
       .filter((result) => result.score >= 30)
       .sort((a, b) => {
-        // Prioritize main game versions if scores are close
         if (Math.abs(a.score - b.score) <= 10) {
           const aExtra = a.isDlc || a.isSoundtrack || a.isDemo
           const bExtra = b.isDlc || b.isSoundtrack || b.isDemo
@@ -273,7 +239,6 @@ export async function findSteamAppIdForGameName(
       })
       .slice(0, maxResults)
 
-    // Remove the extra properties from final results
     return enhancedResults.map(({ isDlc, isSoundtrack, isDemo, ...result }) => result)
   } catch (error) {
     console.error('Error searching for Steam App ID:', error)
@@ -281,19 +246,11 @@ export async function findSteamAppIdForGameName(
   }
 }
 
-/**
- * Gets the best matching App ID for a game name (highest score)
- * @param gameName - The name of the game to search for
- * @returns The best matching App ID or null if no good match found
- */
 export async function getBestSteamAppIdMatch(gameName: string): Promise<string | null> {
   const results = await findSteamAppIdForGameName(gameName, 1)
   return results.length > 0 && results[0].score >= 50 ? results[0].appId : null
 }
 
-/**
- * Forces a refresh of the Steam games data cache
- */
 export async function refreshSteamGamesData(): Promise<void> {
   try {
     steamGamesDataCache.clear()
@@ -306,9 +263,6 @@ export async function refreshSteamGamesData(): Promise<void> {
   }
 }
 
-/**
- * Gets statistics about the cached Steam games data
- */
 export async function getSteamGamesStats(): Promise<{
   totalGames: number
   cacheStatus: 'hit' | 'miss' | 'empty'
@@ -318,21 +272,20 @@ export async function getSteamGamesStats(): Promise<{
   const cachedData = steamGamesDataCache.get(cacheKey)
 
   if (cachedData) {
-    const lastUpdated = steamGamesDataCache.getCreatedAt(cacheKey)
     return {
-      totalGames: cachedData.length,
+      totalGames: cachedData.data.length,
       cacheStatus: 'hit',
-      lastUpdated: lastUpdated || undefined,
+      lastUpdated: cachedData.createdAt,
     }
   }
 
   try {
     const freshData = await getSteamGamesData()
-    const lastUpdated = steamGamesDataCache.getCreatedAt(cacheKey)
+    const cached = steamGamesDataCache.get(cacheKey)
     return {
       totalGames: freshData.length,
       cacheStatus: 'miss',
-      lastUpdated: lastUpdated || undefined,
+      lastUpdated: cached?.createdAt,
     }
   } catch (error) {
     console.error('[steamGameSearch] getSteamGamesStats failed to load data:', error)
diff --git a/src/server/utils/switchGameSearch.ts b/src/server/utils/switchGameSearch.ts
index adf23b504..06915ee19 100644
--- a/src/server/utils/switchGameSearch.ts
+++ b/src/server/utils/switchGameSearch.ts
@@ -1,13 +1,7 @@
-/**
- * Nintendo Switch game data management with fuzzy search capabilities
- * Fetches and caches Switch game data from external source with periodic updates
- */
-
 import Fuse from 'fuse.js'
+import { LRUCache } from 'lru-cache'
 import { ms } from '@/utils/time'
-import { MemoryCache } from './cache'
 
-// Types for Switch game data
 interface SwitchGameEntry {
   program_id: string
   name: string
@@ -21,38 +15,37 @@ interface SwitchGameSearchResult {
   score: number
 }
 
-// Cache for Switch games data - longer TTL since data updates weekly
-const switchGamesDataCache = new MemoryCache<SwitchGameEntry[]>({
+interface CachedData<T> {
+  data: T
+  createdAt: Date
+}
+
+const switchGamesDataCache = new LRUCache<string, CachedData<SwitchGameEntry[]>>({
   ttl: ms.days(1),
-  maxSize: 1,
+  max: 1,
 })
 
-// Cache for Fuse.js search instance - rebuild when data updates
-const switchGamesFuseCache = new MemoryCache<Fuse<SwitchGameEntry>>({
+const switchGamesFuseCache = new LRUCache<string, Fuse<SwitchGameEntry>>({
   ttl: ms.days(1),
-  maxSize: 1,
+  max: 1,
 })
 
-// Configuration for fuzzy search
 const FUSE_OPTIONS = {
   keys: [
     { name: 'name', weight: 0.7 },
     { name: 'title_normalized', weight: 0.3 },
   ],
-  threshold: 0.4, // More strict threshold for better matches
-  distance: 50, // Reduced distance for better precision
-  minMatchCharLength: 3, // Require at least 3 characters to match
+  threshold: 0.4,
+  distance: 50,
+  minMatchCharLength: 3,
   includeScore: true,
-  includeMatches: true, // Include match details for better filtering
-  ignoreLocation: true, // Don't penalize matches at different positions
-  findAllMatches: true, // Find all matching patterns
+  includeMatches: true,
+  ignoreLocation: true,
+  findAllMatches: true,
 }
 
 const SWITCH_GAMES_URL = 'https://producdevity.github.io/switch-games-json/switchbrew_id_names.json'
 
-/**
- * Fetches Switch games data from external source
- */
 async function fetchSwitchGamesData(): Promise<SwitchGameEntry[]> {
   const response = await fetch(SWITCH_GAMES_URL, {
     headers: { 'User-Agent': 'EmuReady-GameSearch/1.0' },
@@ -68,9 +61,7 @@ async function fetchSwitchGamesData(): Promise<SwitchGameEntry[]> {
     throw new Error('Invalid Switch games data format: expected array')
   }
 
-  // Validate data structure
   for (const entry of data.slice(0, 5)) {
-    // Check first 5 entries
     if (!entry.program_id || !entry.name || !entry.title_normalized) {
       throw new Error('Invalid Switch game entry structure')
     }
@@ -79,23 +70,17 @@ async function fetchSwitchGamesData(): Promise<SwitchGameEntry[]> {
   return data
 }
 
-/**
- * Gets cached Switch games data or fetches fresh data
- */
 async function getSwitchGamesData(): Promise<SwitchGameEntry[]> {
   const cacheKey = 'switch-games-data'
 
-  // Try to get from cache first
   const cachedData = switchGamesDataCache.get(cacheKey)
-  if (cachedData) return cachedData
+  if (cachedData) return cachedData.data
 
-  // Fetch fresh data and cache it
   try {
     const freshData = await fetchSwitchGamesData()
-    switchGamesDataCache.set(cacheKey, freshData)
+    switchGamesDataCache.set(cacheKey, { data: freshData, createdAt: new Date() })
     return freshData
   } catch (error) {
-    // Add context to the error without losing the original stack trace
     if (error instanceof Error) {
       error.message = `Switch games data fetch failed: ${error.message}`
     }
@@ -103,17 +88,12 @@ async function getSwitchGamesData(): Promise<SwitchGameEntry[]> {
   }
 }
 
-/**
- * Gets or creates Fuse.js search instance
- */
 async function getFuseInstance(): Promise<Fuse<SwitchGameEntry>> {
   const cacheKey = 'switch-games-fuse'
 
-  // Try to get from cache first
   const cachedFuse = switchGamesFuseCache.get(cacheKey)
   if (cachedFuse) return cachedFuse
 
-  // Create new Fuse instance with fresh data
   const gamesData = await getSwitchGamesData()
   const fuse = new Fuse(gamesData, FUSE_OPTIONS)
 
@@ -121,12 +101,6 @@ async function getFuseInstance(): Promise<Fuse<SwitchGameEntry>> {
   return fuse
 }
 
-/**
- * Searches for Nintendo Switch title ID based on game name using fuzzy matching
- * @param gameName - The name of the game to search for
- * @param maxResults - Maximum number of results to return (default: 5)
- * @returns Array of search results with title IDs and match scores
- */
 export async function findTitleIdForGameName(
   gameName: string,
   maxResults: number = 5,
@@ -136,19 +110,16 @@ export async function findTitleIdForGameName(
   try {
     const fuse = await getFuseInstance()
     const searchTerm = gameName.trim().toLowerCase()
-    const searchResults = fuse.search(searchTerm, { limit: maxResults * 2 }) // Get more results for filtering
+    const searchResults = fuse.search(searchTerm, { limit: maxResults * 2 })
 
-    // Filter and enhance results with better scoring
     const enhancedResults = searchResults
       .map((result) => {
         const item = result.item
         const fuseScore = result.score || 0
 
-        // Calculate enhanced score based on multiple factors
         const nameMatch = item.name.toLowerCase()
         const normalizedMatch = item.title_normalized.toLowerCase()
 
-        // Exact match bonus
         let bonusScore = 0
         if (nameMatch === searchTerm || normalizedMatch === searchTerm) {
           bonusScore = 40
@@ -156,7 +127,6 @@ export async function findTitleIdForGameName(
           bonusScore = 20
         }
 
-        // Word match bonus
         const searchWords = searchTerm.split(' ').filter((w) => w.length > 2)
         const nameWords = nameMatch.split(' ')
         const normalizedWords = normalizedMatch.split(' ')
@@ -171,16 +141,14 @@ export async function findTitleIdForGameName(
           }
         })
 
-        // Avoid demo/kiosk versions unless specifically searched for
         let demoBonus = 0
         const isDemo = nameMatch.includes('demo') || nameMatch.includes('kiosk')
         const searchesForDemo = searchTerm.includes('demo') || searchTerm.includes('kiosk')
 
         if (isDemo && !searchesForDemo) {
-          demoBonus = -15 // Penalize demos if not specifically searched
+          demoBonus = -15
         }
 
-        // Final score calculation
         const baseScore = Math.round((1 - fuseScore) * 100)
         const finalScore = Math.min(
           100,
@@ -195,9 +163,8 @@ export async function findTitleIdForGameName(
           isDemo,
         }
       })
-      .filter((result) => result.score >= 30) // Only return results with decent scores
+      .filter((result) => result.score >= 30)
       .sort((a, b) => {
-        // Prioritize non-demo versions if scores are close
         if (Math.abs(a.score - b.score) <= 10) {
           if (a.isDemo && !b.isDemo) return 1
           if (!a.isDemo && b.isDemo) return -1
@@ -206,7 +173,6 @@ export async function findTitleIdForGameName(
       })
       .slice(0, maxResults)
 
-    // Remove the isDemo property from final results
     return enhancedResults.map(({ isDemo, ...result }) => result)
   } catch (error) {
     console.error('Error searching for Switch title ID:', error)
@@ -214,29 +180,17 @@ export async function findTitleIdForGameName(
   }
 }
 
-/**
- * Gets the best matching title ID for a game name (highest score)
- * @param gameName - The name of the game to search for
- * @returns The best matching title ID or null if no good match found
- */
 export async function getBestTitleIdMatch(gameName: string): Promise<string | null> {
   const results = await findTitleIdForGameName(gameName, 1)
 
-  // Return the best match if score is good enough (>= 50% for more flexibility)
   return results.length > 0 && results[0].score >= 50 ? results[0].titleId : null
 }
 
-/**
- * Forces a refresh of the Switch games data cache
- * Useful for periodic updates or manual refresh
- */
 export async function refreshSwitchGamesData(): Promise<void> {
   try {
-    // Clear caches to force fresh fetch
     switchGamesDataCache.clear()
     switchGamesFuseCache.clear()
 
-    // Pre-populate cache with fresh data
     await getSwitchGamesData()
     await getFuseInstance()
   } catch (error) {
@@ -245,9 +199,6 @@ export async function refreshSwitchGamesData(): Promise<void> {
   }
 }
 
-/**
- * Gets statistics about the cached Switch games data
- */
 export async function getSwitchGamesStats(): Promise<{
   totalGames: number
   cacheStatus: 'hit' | 'miss' | 'empty'
@@ -257,21 +208,20 @@ export async function getSwitchGamesStats(): Promise<{
   const cachedData = switchGamesDataCache.get(cacheKey)
 
   if (cachedData) {
-    const lastUpdated = switchGamesDataCache.getCreatedAt(cacheKey)
     return {
-      totalGames: cachedData.length,
+      totalGames: cachedData.data.length,
       cacheStatus: 'hit',
-      lastUpdated: lastUpdated || undefined,
+      lastUpdated: cachedData.createdAt,
     }
   }
 
   try {
     const freshData = await getSwitchGamesData()
-    const lastUpdated = switchGamesDataCache.getCreatedAt(cacheKey)
+    const cached = switchGamesDataCache.get(cacheKey)
     return {
       totalGames: freshData.length,
       cacheStatus: 'miss',
-      lastUpdated: lastUpdated || undefined,
+      lastUpdated: cached?.createdAt,
     }
   } catch {
     return {
diff --git a/src/server/utils/threeDsGameSearch.ts b/src/server/utils/threeDsGameSearch.ts
index f94163ca0..c4c9f1464 100644
--- a/src/server/utils/threeDsGameSearch.ts
+++ b/src/server/utils/threeDsGameSearch.ts
@@ -1,11 +1,6 @@
-/**
- * Nintendo 3DS title ID lookup with fuzzy search and caching
- * Mirrors the Nintendo Switch lookup flow but sources data from the 3DS title manifest
- */
-
 import Fuse from 'fuse.js'
+import { LRUCache } from 'lru-cache'
 import { ms } from '@/utils/time'
-import { MemoryCache } from './cache'
 import type { IFuseOptions } from 'fuse.js'
 
 interface RawThreeDsTitleEntry {
@@ -37,6 +32,11 @@ export interface ThreeDsGameSearchResult {
   productCode: string | null
 }
 
+interface CachedData<T> {
+  data: T
+  createdAt: Date
+}
+
 const REGION_PRIORITY = [
   'US',
   'GB',
@@ -78,14 +78,14 @@ const FUSE_OPTIONS: IFuseOptions<ThreeDsGameEntry> = {
 const THREEDS_TITLES_URL = 'https://dantheman827.github.io/nus-info/titles.json'
 const THREEDS_TITLE_NAMES_URL = 'https://dantheman827.github.io/nus-info/title-names.json'
 
-const threeDsGamesDataCache = new MemoryCache<ThreeDsGameEntry[]>({
+const threeDsGamesDataCache = new LRUCache<string, CachedData<ThreeDsGameEntry[]>>({
   ttl: ms.days(1),
-  maxSize: 1,
+  max: 1,
 })
 
-const threeDsGamesFuseCache = new MemoryCache<Fuse<ThreeDsGameEntry>>({
+const threeDsGamesFuseCache = new LRUCache<string, Fuse<ThreeDsGameEntry>>({
   ttl: ms.days(1),
-  maxSize: 1,
+  max: 1,
 })
 
 function normalizeTitle(title: string): string {
@@ -205,11 +205,11 @@ async function fetchThreeDsGamesData(): Promise<ThreeDsGameEntry[]> {
 async function getThreeDsGamesData(): Promise<ThreeDsGameEntry[]> {
   const cacheKey = '3ds-games-data'
   const cached = threeDsGamesDataCache.get(cacheKey)
-  if (cached) return cached
+  if (cached) return cached.data
 
   try {
     const fresh = await fetchThreeDsGamesData()
-    threeDsGamesDataCache.set(cacheKey, fresh)
+    threeDsGamesDataCache.set(cacheKey, { data: fresh, createdAt: new Date() })
     return fresh
   } catch (error) {
     if (error instanceof Error) {
@@ -358,21 +358,20 @@ export async function getThreeDsGamesStats(): Promise<{
   const cached = threeDsGamesDataCache.get(cacheKey)
 
   if (cached) {
-    const lastUpdated = threeDsGamesDataCache.getCreatedAt(cacheKey)
     return {
-      totalGames: cached.length,
+      totalGames: cached.data.length,
       cacheStatus: 'hit',
-      lastUpdated: lastUpdated ?? undefined,
+      lastUpdated: cached.createdAt,
     }
   }
 
   try {
     const fresh = await getThreeDsGamesData()
-    const lastUpdated = threeDsGamesDataCache.getCreatedAt(cacheKey)
+    const cachedAfterRefresh = threeDsGamesDataCache.get(cacheKey)
     return {
       totalGames: fresh.length,
       cacheStatus: 'miss',
-      lastUpdated: lastUpdated ?? undefined,
+      lastUpdated: cachedAfterRefresh?.createdAt,
     }
   } catch {
     return {
diff --git a/tsconfig.json b/tsconfig.json
index 5a1351ef1..d40a002d3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,7 +11,7 @@
     "moduleResolution": "bundler",
     "resolveJsonModule": true,
     "isolatedModules": true,
-    "jsx": "preserve",
+    "jsx": "react-jsx",
     "incremental": true,
     "plugins": [{ "name": "next" }],
     "paths": {

From 2813fb4c483a82f81609c8ab0266c79bc2ba7463 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Fri, 22 May 2026 17:26:35 +0200
Subject: [PATCH 02/22] feat: implement cache invalidation for device catalogs
 and SEO updates, add dynamic copyright year handling in Footer component

---
 src/components/footer/Footer.tsx         |  9 ++++++++-
 src/server/api/routers/listings/admin.ts | 17 +++++++++++++++++
 src/server/api/routers/pcListings.ts     | 17 +++++++++++++++++
 3 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx
index 320e30728..ac29fe935 100644
--- a/src/components/footer/Footer.tsx
+++ b/src/components/footer/Footer.tsx
@@ -1,6 +1,7 @@
 'use client'
 
 import Link from 'next/link'
+import { useEffect, useState } from 'react'
 import { FooterAppLinks } from '@/components/footer/components/FooterAppLinks'
 import { FooterBetaBadge } from '@/components/footer/components/FooterBetaBadge'
 import { FooterKofiButton } from '@/components/footer/components/FooterKofiButton'
@@ -12,6 +13,12 @@ import analytics from '@/lib/analytics'
 import { env } from '@/lib/env'
 
 function Footer() {
+  const [copyrightYear, setCopyrightYear] = useState<number | null>(null)
+
+  useEffect(() => {
+    setCopyrightYear(new Date().getUTCFullYear())
+  }, [])
+
   return (
     <footer className="relative overflow-hidden">
       <div className="absolute inset-0 -z-10">
@@ -177,7 +184,7 @@ function Footer() {
           <div className="relative pt-8 border-t border-gray-200/50 dark:border-gray-700/50">
             <div className="text-center">
               <p className="text-gray-500 dark:text-gray-400 text-sm mb-4">
-                &copy; {new Date().getFullYear()} EmuReady. All rights reserved.
+                &copy; {copyrightYear ? `${copyrightYear} ` : ''}EmuReady. All rights reserved.
               </p>
               <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/60 dark:bg-gray-800/60 backdrop-blur-sm border border-gray-200/50 dark:border-gray-700/50">
                 <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
diff --git a/src/server/api/routers/listings/admin.ts b/src/server/api/routers/listings/admin.ts
index 81fc7fb02..aa8ce0644 100644
--- a/src/server/api/routers/listings/admin.ts
+++ b/src/server/api/routers/listings/admin.ts
@@ -676,6 +676,16 @@ export const adminRouter = createTRPCRouter({
       listingStatsCache.delete(LISTING_STATS_CACHE_KEY)
 
       const { validListings, bannedUserListings, notFoundOrNotPendingIds } = transactionResult
+      const { invalidateCatalogCompatibilityCacheForDevice } = await import(
+        '@/server/utils/cache/instances'
+      )
+      const deviceIdsToInvalidate = new Set([
+        ...validListings.map((listing) => listing.deviceId),
+        ...bannedUserListings.map((listing) => listing.deviceId),
+      ])
+      for (const deviceId of deviceIdsToInvalidate) {
+        invalidateCatalogCompatibilityCacheForDevice(deviceId)
+      }
 
       let message = `Successfully approved ${validListings.length} listing(s).`
 
@@ -830,6 +840,13 @@ export const adminRouter = createTRPCRouter({
       listingStatsCache.delete(LISTING_STATS_CACHE_KEY)
 
       const { listingsToReject, notFoundOrNotPendingIds } = transactionResult
+      const { invalidateCatalogCompatibilityCacheForDevice } = await import(
+        '@/server/utils/cache/instances'
+      )
+      const deviceIdsToInvalidate = new Set(listingsToReject.map((listing) => listing.deviceId))
+      for (const deviceId of deviceIdsToInvalidate) {
+        invalidateCatalogCompatibilityCacheForDevice(deviceId)
+      }
 
       const message =
         notFoundOrNotPendingIds.length > 0
diff --git a/src/server/api/routers/pcListings.ts b/src/server/api/routers/pcListings.ts
index 863829813..9d2804bb9 100644
--- a/src/server/api/routers/pcListings.ts
+++ b/src/server/api/routers/pcListings.ts
@@ -992,6 +992,23 @@ export const pcListingsRouter = createTRPCRouter({
         }
       }
 
+      if (pcListing.status === ApprovalStatus.APPROVED) {
+        await invalidatePcListingSeoForUpdate(
+          {
+            id,
+            gameId: pcListing.gameId,
+            cpuId: pcListing.cpuId,
+            gpuId: pcListing.gpuId,
+          },
+          {
+            id,
+            gameId: updatedPcListing.gameId,
+            cpuId: updatedPcListing.cpuId,
+            gpuId: updatedPcListing.gpuId,
+          },
+        )
+      }
+
       return updatedPcListing
     }),
 

From 3e78a677d8c93759aa95ad24387e2c5c3eb85d93 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Fri, 22 May 2026 18:40:42 +0200
Subject: [PATCH 03/22] feat: SEO cache invalidation with new listing SEO
 functions and batch processing

---
 src/server/api/routers/listings/admin.test.ts |  7 +--
 src/server/api/routers/listings/admin.ts      | 63 +++++--------------
 src/server/cache/invalidation.test.ts         | 27 +++++++-
 src/server/cache/invalidation.ts              | 42 +++++++++++++
 src/server/utils/cache/instances.ts           | 10 +++
 5 files changed, 95 insertions(+), 54 deletions(-)

diff --git a/src/server/api/routers/listings/admin.test.ts b/src/server/api/routers/listings/admin.test.ts
index cf7043700..27b944789 100644
--- a/src/server/api/routers/listings/admin.test.ts
+++ b/src/server/api/routers/listings/admin.test.ts
@@ -43,15 +43,14 @@ vi.mock('@/server/notifications/eventEmitter', () => ({
 }))
 
 vi.mock('@/server/cache/invalidation', () => ({
-  invalidateListing: vi.fn().mockResolvedValue(undefined),
-  invalidateListPages: vi.fn().mockResolvedValue(undefined),
-  invalidateSitemap: vi.fn().mockResolvedValue(undefined),
-  revalidateByTag: vi.fn().mockResolvedValue(undefined),
+  invalidateListingSeo: vi.fn().mockResolvedValue(undefined),
+  invalidateListingsSeo: vi.fn().mockResolvedValue(undefined),
 }))
 
 vi.mock('@/server/utils/cache/instances', () => ({
   listingStatsCache: { delete: vi.fn(), get: vi.fn(), set: vi.fn() },
   invalidateCatalogCompatibilityCacheForDevice: vi.fn(),
+  invalidateCatalogCompatibilityCacheForDevices: vi.fn(),
 }))
 
 vi.mock('@/server/utils/emulator-config/emulator-detector', () => ({
diff --git a/src/server/api/routers/listings/admin.ts b/src/server/api/routers/listings/admin.ts
index aa8ce0644..2d401b675 100644
--- a/src/server/api/routers/listings/admin.ts
+++ b/src/server/api/routers/listings/admin.ts
@@ -27,12 +27,7 @@ import {
   protectedProcedure,
 } from '@/server/api/trpc'
 import { buildProcessedOrderBy } from '@/server/api/utils/listingHelpers'
-import {
-  invalidateListing,
-  invalidateListPages,
-  invalidateSitemap,
-  revalidateByTag,
-} from '@/server/cache/invalidation'
+import { invalidateListingSeo, invalidateListingsSeo } from '@/server/cache/invalidation'
 import { notificationEventEmitter, NOTIFICATION_EVENTS } from '@/server/notifications/eventEmitter'
 import { ListingsRepository } from '@/server/repositories/listings.repository'
 import { PcListingsRepository } from '@/server/repositories/pc-listings.repository'
@@ -41,7 +36,11 @@ import {
   computeReviewRiskProfiles,
   getRiskOnlyReviewPage,
 } from '@/server/services/review-risk.service'
-import { listingStatsCache } from '@/server/utils/cache/instances'
+import {
+  invalidateCatalogCompatibilityCacheForDevice,
+  invalidateCatalogCompatibilityCacheForDevices,
+  listingStatsCache,
+} from '@/server/utils/cache/instances'
 import { generateEmulatorConfig } from '@/server/utils/emulator-config/emulator-detector'
 import { paginate } from '@/server/utils/pagination'
 import { hasRolePermission } from '@/utils/permissions'
@@ -212,19 +211,12 @@ export const adminRouter = createTRPCRouter({
       },
     })
 
-    // Invalidate SEO cache
-    await invalidateListing(listingId)
-    await invalidateListPages()
-    await invalidateSitemap()
-    await revalidateByTag('listings')
-    await revalidateByTag(`listing-${listingId}`)
-    await revalidateByTag(`game-${listingToApprove.gameId}`)
-    await revalidateByTag(`device-${listingToApprove.deviceId}`)
-    await revalidateByTag(`emulator-${listingToApprove.emulatorId}`)
-
-    const { invalidateCatalogCompatibilityCacheForDevice } = await import(
-      '@/server/utils/cache/instances'
-    )
+    await invalidateListingSeo({
+      id: listingId,
+      gameId: listingToApprove.gameId,
+      deviceId: listingToApprove.deviceId,
+      emulatorId: listingToApprove.emulatorId,
+    })
     invalidateCatalogCompatibilityCacheForDevice(listingToApprove.deviceId)
 
     // Apply trust action for listing approval to the author
@@ -343,9 +335,6 @@ export const adminRouter = createTRPCRouter({
     // Invalidate listing stats cache
     listingStatsCache.delete(LISTING_STATS_CACHE_KEY)
 
-    const { invalidateCatalogCompatibilityCacheForDevice } = await import(
-      '@/server/utils/cache/instances'
-    )
     invalidateCatalogCompatibilityCacheForDevice(listingToReject.deviceId)
 
     return updatedListing
@@ -632,17 +621,7 @@ export const adminRouter = createTRPCRouter({
         const { validListings } = transactionResult
         const approvedAt = new Date()
 
-        // Invalidate SEO cache for all approved listings
-        await Promise.all([
-          ...validListings.map((listing) => invalidateListing(listing.id)),
-          ...validListings.map((listing) => revalidateByTag(`listing-${listing.id}`)),
-          ...validListings.map((listing) => revalidateByTag(`game-${listing.gameId}`)),
-          ...validListings.map((listing) => revalidateByTag(`device-${listing.deviceId}`)),
-          ...validListings.map((listing) => revalidateByTag(`emulator-${listing.emulatorId}`)),
-        ])
-        await invalidateListPages()
-        await invalidateSitemap()
-        await revalidateByTag('listings')
+        await invalidateListingsSeo(validListings)
 
         // Emit notification events for each approved listing
         for (const listing of validListings) {
@@ -676,16 +655,10 @@ export const adminRouter = createTRPCRouter({
       listingStatsCache.delete(LISTING_STATS_CACHE_KEY)
 
       const { validListings, bannedUserListings, notFoundOrNotPendingIds } = transactionResult
-      const { invalidateCatalogCompatibilityCacheForDevice } = await import(
-        '@/server/utils/cache/instances'
-      )
-      const deviceIdsToInvalidate = new Set([
+      invalidateCatalogCompatibilityCacheForDevices([
         ...validListings.map((listing) => listing.deviceId),
         ...bannedUserListings.map((listing) => listing.deviceId),
       ])
-      for (const deviceId of deviceIdsToInvalidate) {
-        invalidateCatalogCompatibilityCacheForDevice(deviceId)
-      }
 
       let message = `Successfully approved ${validListings.length} listing(s).`
 
@@ -840,13 +813,9 @@ export const adminRouter = createTRPCRouter({
       listingStatsCache.delete(LISTING_STATS_CACHE_KEY)
 
       const { listingsToReject, notFoundOrNotPendingIds } = transactionResult
-      const { invalidateCatalogCompatibilityCacheForDevice } = await import(
-        '@/server/utils/cache/instances'
+      invalidateCatalogCompatibilityCacheForDevices(
+        listingsToReject.map((listing) => listing.deviceId),
       )
-      const deviceIdsToInvalidate = new Set(listingsToReject.map((listing) => listing.deviceId))
-      for (const deviceId of deviceIdsToInvalidate) {
-        invalidateCatalogCompatibilityCacheForDevice(deviceId)
-      }
 
       const message =
         notFoundOrNotPendingIds.length > 0
diff --git a/src/server/cache/invalidation.test.ts b/src/server/cache/invalidation.test.ts
index 0adf383c8..5d7870ba6 100644
--- a/src/server/cache/invalidation.test.ts
+++ b/src/server/cache/invalidation.test.ts
@@ -7,9 +7,12 @@ const nextCacheMocks = vi.hoisted(() => ({
 
 vi.mock('next/cache', () => nextCacheMocks)
 
-const { invalidatePcListing, invalidatePcListingSeoForUpdate, revalidateByTag } = await import(
-  './invalidation'
-)
+const {
+  invalidateListingSeo,
+  invalidatePcListing,
+  invalidatePcListingSeoForUpdate,
+  revalidateByTag,
+} = await import('./invalidation')
 
 describe('cache invalidation', () => {
   beforeEach(() => {
@@ -46,4 +49,22 @@ describe('cache invalidation', () => {
     expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('gpu-gpu-1', 'max')
     expect(nextCacheMocks.revalidateTag).toHaveBeenCalledTimes(8)
   })
+
+  it('invalidates handheld listing SEO paths and tags consistently', async () => {
+    await invalidateListingSeo({
+      id: 'listing-1',
+      gameId: 'game-1',
+      deviceId: 'device-1',
+      emulatorId: 'emulator-1',
+    })
+
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/listings/listing-1')
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/listings')
+    expect(nextCacheMocks.revalidatePath).toHaveBeenCalledWith('/sitemap.xml')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('listing-listing-1', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('listings', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('game-game-1', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('device-device-1', 'max')
+    expect(nextCacheMocks.revalidateTag).toHaveBeenCalledWith('emulator-emulator-1', 'max')
+  })
 })
diff --git a/src/server/cache/invalidation.ts b/src/server/cache/invalidation.ts
index e4b0d5122..a897da932 100644
--- a/src/server/cache/invalidation.ts
+++ b/src/server/cache/invalidation.ts
@@ -7,6 +7,13 @@ interface PcListingSeoTarget {
   gpuId: string | null
 }
 
+interface ListingSeoTarget {
+  id: string
+  gameId: string
+  deviceId: string
+  emulatorId: string
+}
+
 export async function invalidateGame(gameId: string) {
   const startTime = performance.now()
 
@@ -83,6 +90,10 @@ export async function invalidatePcListingSeo(listing: PcListingSeoTarget) {
   await invalidatePcListingSeoTargets([listing.id], [listing])
 }
 
+export async function invalidateListingSeo(listing: ListingSeoTarget) {
+  await invalidateListingSeoTargets([listing.id], [listing])
+}
+
 export async function invalidatePcListingSeoForUpdate(
   previous: PcListingSeoTarget,
   next: PcListingSeoTarget,
@@ -97,6 +108,13 @@ export async function invalidatePcListingsSeo(listings: PcListingSeoTarget[]) {
   )
 }
 
+export async function invalidateListingsSeo(listings: ListingSeoTarget[]) {
+  await invalidateListingSeoTargets(
+    listings.map((listing) => listing.id),
+    listings,
+  )
+}
+
 export async function revalidateByTag(tag: string) {
   try {
     revalidateTag(tag, 'max')
@@ -105,6 +123,18 @@ export async function revalidateByTag(tag: string) {
   }
 }
 
+async function invalidateListingSeoTargets(listingIds: string[], tagTargets: ListingSeoTarget[]) {
+  if (listingIds.length === 0 && tagTargets.length === 0) return
+
+  const uniqueListingIds = [...new Set(listingIds)]
+  const tags = collectListingSeoTags(tagTargets)
+
+  await Promise.all(uniqueListingIds.map((listingId) => invalidateListing(listingId)))
+  await invalidateListPages()
+  await invalidateSitemap()
+  await Promise.all([...tags].map((tag) => revalidateByTag(tag)))
+}
+
 async function invalidatePcListingSeoTargets(
   listingIds: string[],
   tagTargets: PcListingSeoTarget[],
@@ -120,6 +150,18 @@ async function invalidatePcListingSeoTargets(
   await Promise.all([...tags].map((tag) => revalidateByTag(tag)))
 }
 
+function collectListingSeoTags(listings: ListingSeoTarget[]): Set<string> {
+  const tags = new Set<string>(['listings'])
+
+  for (const listing of listings) {
+    tags.add(`game-${listing.gameId}`)
+    tags.add(`device-${listing.deviceId}`)
+    tags.add(`emulator-${listing.emulatorId}`)
+  }
+
+  return tags
+}
+
 function collectPcListingSeoTags(listings: PcListingSeoTarget[]): Set<string> {
   const tags = new Set<string>(['pc-listings'])
 
diff --git a/src/server/utils/cache/instances.ts b/src/server/utils/cache/instances.ts
index eefdc6bba..fa3fed181 100644
--- a/src/server/utils/cache/instances.ts
+++ b/src/server/utils/cache/instances.ts
@@ -119,3 +119,13 @@ export function invalidateCatalogCompatibilityCacheForDevice(deviceId: string):
 
   return deleted
 }
+
+export function invalidateCatalogCompatibilityCacheForDevices(deviceIds: Iterable<string>): number {
+  let deleted = 0
+
+  for (const deviceId of new Set(deviceIds)) {
+    deleted += invalidateCatalogCompatibilityCacheForDevice(deviceId)
+  }
+
+  return deleted
+}

From e2fc784a5821182d50eeef7effb86cdd3974397b Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Fri, 22 May 2026 18:41:03 +0200
Subject: [PATCH 04/22] feat: improve session tracking with null safety checks
 and initialization

---
 src/components/SessionTracker.tsx | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/src/components/SessionTracker.tsx b/src/components/SessionTracker.tsx
index 318be9bb2..45266c9f8 100644
--- a/src/components/SessionTracker.tsx
+++ b/src/components/SessionTracker.tsx
@@ -21,13 +21,22 @@ function SessionTracker() {
   const { user } = useUser()
   const pathname = usePathname()
   const { analyticsAllowed } = useCookieConsent()
-  const sessionStartRef = useRef<number>(Date.now())
-  const pageLoadTimeRef = useRef<number>(Date.now())
-  const sessionIdRef = useRef<string>(generateUUID())
+  const sessionStartRef = useRef<number | null>(null)
+  const pageLoadTimeRef = useRef<number | null>(null)
+  const sessionIdRef = useRef<string | null>(null)
   const hasTrackedSessionStart = useRef<boolean>(false)
   const discoveredFeatures = useRef<Set<string>>(new Set())
   const previousUserIdRef = useRef<string | undefined>(undefined)
 
+  useEffect(() => {
+    if (sessionStartRef.current !== null) return
+
+    const now = Date.now()
+    sessionStartRef.current = now
+    pageLoadTimeRef.current = now
+    sessionIdRef.current = generateUUID()
+  }, [])
+
   // Track user sign-in when a user transitions from null/undefined to having a user
   useEffect(() => {
     if (!analyticsAllowed) return
@@ -49,7 +58,7 @@ function SessionTracker() {
 
   // Track session start on the first load
   useEffect(() => {
-    if (!analyticsAllowed || hasTrackedSessionStart.current) return
+    if (!analyticsAllowed || hasTrackedSessionStart.current || !sessionIdRef.current) return
 
     hasTrackedSessionStart.current = true
 
@@ -63,7 +72,7 @@ function SessionTracker() {
 
   // Track page views when pathname changes
   useEffect(() => {
-    if (!analyticsAllowed) return
+    if (!analyticsAllowed || pageLoadTimeRef.current === null) return
 
     const loadTime = Date.now() - pageLoadTimeRef.current
 
@@ -79,10 +88,12 @@ function SessionTracker() {
 
     // Track feature discovery based on page visits
     const featureMap: Record<string, string> = {
+      '/pc-listings/new': 'pc-listing_creation',
       '/listings/new': 'listing_creation',
       '/profile': 'profile_management',
       '/admin': 'admin_panel',
       '/listings': 'listing_browser',
+      '/pc-listings': 'pc-listing_browser',
       '/games': 'game_browser',
     }
 
@@ -102,12 +113,13 @@ function SessionTracker() {
 
   // Track session duration on page unloading
   useEffect(() => {
-    if (!analyticsAllowed) return
+    if (!analyticsAllowed || sessionStartRef.current === null || !sessionIdRef.current) return
 
     const handleBeforeUnload = () => {
+      if (sessionStartRef.current === null || !sessionIdRef.current) return
+
       const sessionDuration = Date.now() - sessionStartRef.current
 
-      // Track session end with analytics
       analytics.session.sessionEnded({
         userId: user?.id,
         sessionId: sessionIdRef.current,

From 511820f471ffbd8d2c79cdbb01c364601bc75b9f Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:21:25 +0200
Subject: [PATCH 05/22] feat: update ESLint configuration and react-hooks
 plugin version for improved linting

---
 eslint.config.mjs | 19 ++++++++++---------
 package.json      |  2 +-
 pnpm-lock.yaml    |  4 ++--
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/eslint.config.mjs b/eslint.config.mjs
index 4198461f0..6470b0b6e 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -2,6 +2,7 @@ import typescriptEslint from '@typescript-eslint/eslint-plugin'
 import typescriptParser from '@typescript-eslint/parser'
 import eslintConfigPrettier from 'eslint-config-prettier'
 import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'
+import nextTypeScript from 'eslint-config-next/typescript'
 
 const eslintConfig = [
   {
@@ -29,6 +30,7 @@ const eslintConfig = [
     ],
   },
   ...nextCoreWebVitals,
+  ...nextTypeScript,
   eslintConfigPrettier,
   {
     files: ['**/*.{js,jsx,ts,tsx}'],
@@ -49,15 +51,14 @@ const eslintConfig = [
       'no-case-declarations': 'off',
       'no-prototype-builtins': 'off',
       'no-redeclare': 'off',
-      // Next 16's flat config enables React Compiler rollout checks. Keep this upgrade
-      // focused on framework/caching behavior; enable these after fixing existing violations.
-      'react-hooks/purity': 'off',
-      'react-hooks/static-components': 'off',
-      'react-hooks/immutability': 'off',
-      'react-hooks/incompatible-library': 'off',
-      'react-hooks/preserve-manual-memoization': 'off',
-      'react-hooks/refs': 'off',
-      'react-hooks/set-state-in-effect': 'off',
+      // TODO: set to error in followup PR
+      'react-hooks/purity': 'warn',
+      'react-hooks/static-components': 'warn',
+      'react-hooks/immutability': 'warn',
+      'react-hooks/incompatible-library': 'warn',
+      'react-hooks/preserve-manual-memoization': 'warn',
+      'react-hooks/refs': 'warn',
+      'react-hooks/set-state-in-effect': 'warn',
     },
   },
   {
diff --git a/package.json b/package.json
index ac82ecd07..26de38473 100644
--- a/package.json
+++ b/package.json
@@ -149,7 +149,7 @@
     "eslint-config-prettier": "^10.1.5",
     "eslint-import-resolver-typescript": "^4.4.3",
     "eslint-plugin-import": "^2.31.0",
-    "eslint-plugin-react-hooks": "5.2.0",
+    "eslint-plugin-react-hooks": "^7.1.1",
     "husky": "^9.1.7",
     "lightningcss": "^1.30.1",
     "lint-staged": "^16.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 461bd0c37..4f77ee367 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -295,8 +295,8 @@ importers:
         specifier: ^2.31.0
         version: 2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.29.0(jiti@2.7.0))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.7.0))
       eslint-plugin-react-hooks:
-        specifier: 5.2.0
-        version: 5.2.0(eslint@9.29.0(jiti@2.7.0))
+        specifier: ^7.1.1
+        version: 7.1.1(eslint@9.29.0(jiti@2.7.0))
       husky:
         specifier: ^9.1.7
         version: 9.1.7

From bbd8f31e8a8db8fddc266b496518aaa5ebf14d53 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:23:11 +0200
Subject: [PATCH 06/22] feat: add SEO functions for approved listings and user
 metadata tagging in tests

---
 src/server/db/seo-queries.test.ts | 49 +++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 2 deletions(-)

diff --git a/src/server/db/seo-queries.test.ts b/src/server/db/seo-queries.test.ts
index 5fbbdb59b..4b7de5a1d 100644
--- a/src/server/db/seo-queries.test.ts
+++ b/src/server/db/seo-queries.test.ts
@@ -26,8 +26,15 @@ const prismaMocks = vi.hoisted(() => ({
 vi.mock('next/cache', () => cacheMocks)
 vi.mock('@/server/db', () => ({ prisma: prismaMocks }))
 
-const { getApprovedGamesForSitemap, getGameForSEO, getListingForSEO, getPcListingForSEO } =
-  await import('./seo-queries')
+const {
+  getApprovedGamesForSitemap,
+  getApprovedListingsForSitemap,
+  getApprovedPcListingsForSitemap,
+  getGameForSEO,
+  getListingForSEO,
+  getPcListingForSEO,
+  getUserForSEO,
+} = await import('./seo-queries')
 
 describe('SEO query cache metadata', () => {
   beforeEach(() => {
@@ -111,6 +118,44 @@ describe('SEO query cache metadata', () => {
     expect(cacheMocks.cacheTag).toHaveBeenCalledWith('sitemap', 'games')
   })
 
+  it('tags user metadata by user id', async () => {
+    const user = {
+      id: 'user-1',
+      name: 'Author',
+      profileImage: null,
+    }
+    prismaMocks.user.findUnique.mockResolvedValueOnce(user)
+
+    await expect(getUserForSEO('user-1')).resolves.toEqual(user)
+
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('user-user-1')
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-record')
+  })
+
+  it('tags handheld report sitemap data', async () => {
+    const listings = [{ id: 'listing-1', createdAt: new Date('2026-01-01T00:00:00.000Z') }]
+    prismaMocks.listing.findMany.mockResolvedValueOnce(listings)
+
+    await expect(getApprovedListingsForSitemap(10)).resolves.toEqual(listings)
+
+    expect(prismaMocks.listing.findMany).toHaveBeenCalledWith(expect.objectContaining({ take: 10 }))
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-sitemap')
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('sitemap', 'listings')
+  })
+
+  it('tags PC report sitemap data', async () => {
+    const listings = [{ id: 'pc-listing-1', createdAt: new Date('2026-01-01T00:00:00.000Z') }]
+    prismaMocks.pcListing.findMany.mockResolvedValueOnce(listings)
+
+    await expect(getApprovedPcListingsForSitemap(10)).resolves.toEqual(listings)
+
+    expect(prismaMocks.pcListing.findMany).toHaveBeenCalledWith(
+      expect.objectContaining({ take: 10 }),
+    )
+    expect(cacheMocks.cacheLife).toHaveBeenCalledWith('seo-sitemap')
+    expect(cacheMocks.cacheTag).toHaveBeenCalledWith('sitemap', 'pc-listings')
+  })
+
   it('does not cache an empty sitemap when the sitemap query fails', async () => {
     const error = new Error('database unavailable')
     prismaMocks.game.findMany.mockRejectedValueOnce(error)

From 7e3ce023ddb28ef3dea5a4302fb7a6066ba8bf50 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:23:50 +0200
Subject: [PATCH 07/22] feat: add SEO invalidation tests for admin updates on
 PC listings

---
 src/server/api/routers/pcListings.test.ts | 60 ++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 2 deletions(-)

diff --git a/src/server/api/routers/pcListings.test.ts b/src/server/api/routers/pcListings.test.ts
index 7e26f8532..63b441acf 100644
--- a/src/server/api/routers/pcListings.test.ts
+++ b/src/server/api/routers/pcListings.test.ts
@@ -1,7 +1,11 @@
-import { describe, expect, it, beforeEach, vi } from 'vitest'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
 import { RISK_SIGNAL_TYPES } from '@/schemas/authorRisk'
 import { SUBMISSION_RISK_SIGNAL_TYPES } from '@/schemas/submissionRisk'
-import { invalidatePcListingsSeo } from '@/server/cache/invalidation'
+import {
+  invalidatePcListingSeo,
+  invalidatePcListingSeoForUpdate,
+  invalidatePcListingsSeo,
+} from '@/server/cache/invalidation'
 import { PERMISSIONS } from '@/utils/permission-system'
 import { ApprovalStatus, PcOs, Role, TrustAction } from '@orm/client'
 import type * as AuthorRiskService from '@/server/services/author-risk.service'
@@ -170,6 +174,7 @@ function createMockPrisma() {
     pcListing: {
       findUnique: vi.fn(),
       findMany: vi.fn().mockResolvedValue([]),
+      update: vi.fn(),
       updateMany: vi.fn().mockResolvedValue({ count: 0 }),
     },
     userBan: {
@@ -807,6 +812,57 @@ describe('pcListings trust integration', () => {
     })
   })
 
+  describe('updateAdmin', () => {
+    it('invalidates public SEO when an admin update approves a PC report', async () => {
+      const gameId = '00000000-0000-4000-a000-000000000040'
+      const cpuId = '00000000-0000-4000-a000-000000000070'
+      const emulatorId = '00000000-0000-4000-a000-000000000060'
+      const updatedListing = {
+        id: LISTING_ID,
+        gameId,
+        cpuId,
+        gpuId: null,
+        status: ApprovalStatus.APPROVED,
+      }
+
+      const { caller, prisma } = createCaller({
+        userId: ADMIN_ID,
+        role: Role.MODERATOR,
+        permissions: [PERMISSIONS.APPROVE_LISTINGS],
+      })
+      prisma.pcListing.findUnique.mockResolvedValue({
+        id: LISTING_ID,
+        gameId,
+        cpuId,
+        gpuId: null,
+        status: ApprovalStatus.PENDING,
+        customFieldValues: [],
+      })
+      prisma.pcListing.update.mockResolvedValue(updatedListing)
+
+      await caller.updateAdmin({
+        id: LISTING_ID,
+        gameId,
+        cpuId,
+        emulatorId,
+        performanceId: 1,
+        memorySize: 16,
+        os: PcOs.WINDOWS,
+        osVersion: '11',
+        notes: 'Updated report',
+        status: ApprovalStatus.APPROVED,
+      })
+
+      expect(invalidatePcListingSeo).toHaveBeenCalledWith({
+        id: LISTING_ID,
+        gameId,
+        cpuId,
+        gpuId: null,
+      })
+      expect(invalidatePcListingSeoForUpdate).not.toHaveBeenCalled()
+    })
+  })
+
   describe('bulkApprove', () => {
     it('calls applyTrustAction with LISTING_APPROVED for each listing author', async () => {
       const listing1 = {

From 8cd3dd1fb9ff50f81d96bf73b2713900a58d26f2 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:24:50 +0200
Subject: [PATCH 08/22] feat: add authentication handling for mobile API
 requests

---
 src/proxy.ts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/proxy.ts b/src/proxy.ts
index 0bf3eb786..abe405e8e 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -183,6 +183,12 @@ const handleClerkAuth = clerkMiddleware(async (auth, req) => {
 export async function proxy(req: NextRequest, evt: NextFetchEvent) {
   const pathname = req.nextUrl.pathname
 
+  if (pathname === '/api/mobile/auth') {
+    const response = await handleClerkAuth(req, evt)
+    if (!response) return applyDevNoStoreHeader(NextResponse.next(), req)
+    return applyDevNoStoreHeader(response, req)
+  }
+
   if (pathname.startsWith('/api/mobile/')) {
     const apiProtectionResponse = protectTRPCAPI(req)
     if (apiProtectionResponse) return apiProtectionResponse

From 4b4e5e1af69d378ef293100a40a92ab5c31cce5f Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:26:10 +0200
Subject: [PATCH 09/22] feat: SEO invalidation logic for PC listings updates

---
 src/server/api/routers/pcListings.ts | 40 +++++++++++++++-------------
 1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/src/server/api/routers/pcListings.ts b/src/server/api/routers/pcListings.ts
index 9d2804bb9..95ccc36d9 100644
--- a/src/server/api/routers/pcListings.ts
+++ b/src/server/api/routers/pcListings.ts
@@ -966,21 +966,17 @@ export const pcListingsRouter = createTRPCRouter({
 
       if (!pcListing) return ResourceError.pcListing.notFound()
 
-      // Update PC listing and handle custom field values
       const updatedPcListing = await ctx.prisma.pcListing.update({
         where: { id },
         data: { ...data, updatedAt: new Date() },
         include: pcListingDetailInclude,
       })
 
-      // Handle custom field values if provided
       if (customFieldValues) {
-        // Delete existing custom field values
         await ctx.prisma.pcListingCustomFieldValue.deleteMany({
           where: { pcListingId: id },
         })
 
-        // Create new custom field values
         if (customFieldValues.length > 0) {
           await ctx.prisma.pcListingCustomFieldValue.createMany({
             data: customFieldValues.map((cfv) => ({
@@ -992,21 +988,27 @@ export const pcListingsRouter = createTRPCRouter({
         }
       }
 
-      if (pcListing.status === ApprovalStatus.APPROVED) {
-        await invalidatePcListingSeoForUpdate(
-          {
-            id,
-            gameId: pcListing.gameId,
-            cpuId: pcListing.cpuId,
-            gpuId: pcListing.gpuId,
-          },
-          {
-            id,
-            gameId: updatedPcListing.gameId,
-            cpuId: updatedPcListing.cpuId,
-            gpuId: updatedPcListing.gpuId,
-          },
-        )
+      const previousSeoTarget = {
+        id,
+        gameId: pcListing.gameId,
+        cpuId: pcListing.cpuId,
+        gpuId: pcListing.gpuId,
+      }
+      const nextSeoTarget = {
+        id,
+        gameId: updatedPcListing.gameId,
+        cpuId: updatedPcListing.cpuId,
+        gpuId: updatedPcListing.gpuId,
+      }
+      const wasApproved = pcListing.status === ApprovalStatus.APPROVED
+      const isApproved = updatedPcListing.status === ApprovalStatus.APPROVED
+
+      if (wasApproved && isApproved) {
+        await invalidatePcListingSeoForUpdate(previousSeoTarget, nextSeoTarget)
+      } else if (wasApproved) {
+        await invalidatePcListingSeo(previousSeoTarget)
+      } else if (isApproved) {
+        await invalidatePcListingSeo(nextSeoTarget)
       }
 
       return updatedPcListing

From 9ce21f8c35a1f4c5d09d7e172b520ef91134b181 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:26:54 +0200
Subject: [PATCH 10/22] feat: enhance GET request handling with connection
 setup

---
 src/app/api/trpc/[trpc]/route.ts | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts
index 63d0cbf2f..d89c3841f 100644
--- a/src/app/api/trpc/[trpc]/route.ts
+++ b/src/app/api/trpc/[trpc]/route.ts
@@ -1,5 +1,5 @@
 import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
-import { type NextRequest } from 'next/server'
+import { connection, type NextRequest } from 'next/server'
 import { appRouter } from '@/server/api/root'
 import { createAppRouterTRPCContext } from '@/server/api/trpc'
 
@@ -18,4 +18,9 @@ const handler = async (req: NextRequest) => {
   })
 }
 
-export { handler as GET, handler as POST }
+async function GET(req: NextRequest) {
+  await connection()
+  return handler(req)
+}
+
+export { GET, handler as POST }

From 9a7177d681bba53f4cc0455275d4a9d6c7c4ba86 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:27:47 +0200
Subject: [PATCH 11/22] feat: improve caching and SEO by adding connection
 setup in GET requests

---
 src/app/api/account/export/route.ts                | 4 +++-
 src/app/api/admin/listings/[id]/config/route.ts    | 4 +++-
 src/app/api/admin/notifications/analytics/route.ts | 6 ++++--
 src/app/api/catalog/compatibility/route.ts         | 4 +++-
 src/app/api/health/route.ts                        | 4 +++-
 src/app/api/mobile/auth/route.ts                   | 4 +++-
 src/app/api/mobile/trpc/[trpc]/route.ts            | 9 +++++++--
 src/app/api/notifications/stream/route.ts          | 4 +++-
 src/app/api/proxy-image/route.ts                   | 4 +++-
 9 files changed, 32 insertions(+), 11 deletions(-)

diff --git a/src/app/api/account/export/route.ts b/src/app/api/account/export/route.ts
index 50a9f3c73..d6500f5b3 100644
--- a/src/app/api/account/export/route.ts
+++ b/src/app/api/account/export/route.ts
@@ -1,8 +1,10 @@
 import { auth } from '@clerk/nextjs/server'
-import { NextResponse } from 'next/server'
+import { connection, NextResponse } from 'next/server'
 import { prisma } from '@/server/db'
 
 export async function GET() {
+  await connection()
+
   try {
     // Get authenticated user
     const { userId } = await auth()
diff --git a/src/app/api/admin/listings/[id]/config/route.ts b/src/app/api/admin/listings/[id]/config/route.ts
index c6d306f45..14b79283a 100644
--- a/src/app/api/admin/listings/[id]/config/route.ts
+++ b/src/app/api/admin/listings/[id]/config/route.ts
@@ -1,11 +1,13 @@
 import { auth } from '@clerk/nextjs/server'
-import { type NextRequest, NextResponse } from 'next/server'
+import { connection, type NextRequest, NextResponse } from 'next/server'
 import { prisma } from '@/server/db'
 import { generateEmulatorConfig } from '@/server/utils/emulator-config/emulator-detector'
 import { roleIncludesRole } from '@/utils/permission-system'
 import { Role } from '@orm'
 
 export async function GET(_: NextRequest, props: { params: Promise<{ id: string }> }) {
+  await connection()
+
   const { userId } = await auth()
   const params = await props.params
 
diff --git a/src/app/api/admin/notifications/analytics/route.ts b/src/app/api/admin/notifications/analytics/route.ts
index 23500b02c..17482b8ba 100644
--- a/src/app/api/admin/notifications/analytics/route.ts
+++ b/src/app/api/admin/notifications/analytics/route.ts
@@ -1,11 +1,13 @@
 import { auth } from '@clerk/nextjs/server'
-import { type NextRequest } from 'next/server'
+import { connection, type NextRequest } from 'next/server'
 import { prisma } from '@/server/db'
 import { notificationAnalyticsService } from '@/server/notifications/analyticsService'
 import { hasRolePermission } from '@/utils/permissions'
 import { Role } from '@orm'
 
 export async function GET(request: NextRequest) {
+  await connection()
+
   try {
     // Check if user is admin
     const { userId } = await auth()
@@ -14,7 +16,7 @@ export async function GET(request: NextRequest) {
     }
 
     const user = await prisma.user.findUnique({
-      where: { id: userId },
+      where: { clerkId: userId },
       select: { role: true },
     })
 
diff --git a/src/app/api/catalog/compatibility/route.ts b/src/app/api/catalog/compatibility/route.ts
index 77c748a96..5ce84cb5c 100644
--- a/src/app/api/catalog/compatibility/route.ts
+++ b/src/app/api/catalog/compatibility/route.ts
@@ -1,5 +1,5 @@
 import { TRPCError } from '@trpc/server'
-import { type NextRequest, NextResponse } from 'next/server'
+import { connection, type NextRequest, NextResponse } from 'next/server'
 import { ZodError } from 'zod'
 import { validateAndConsumeApiKey } from '@/lib/api/validateApiKey'
 import { GetDeviceCompatibilitySchema } from '@/schemas/mobile'
@@ -23,6 +23,8 @@ import { getDeviceCompatibility } from '@/server/services/catalog.service'
  * Either deviceId OR both deviceModelName and deviceBrandName must be provided.
  */
 export async function GET(request: NextRequest) {
+  await connection()
+
   try {
     const apiKey = await validateAndConsumeApiKey(request)
 
diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts
index d36e5b28c..32b647bdb 100644
--- a/src/app/api/health/route.ts
+++ b/src/app/api/health/route.ts
@@ -1,4 +1,4 @@
-import { NextResponse } from 'next/server'
+import { connection, NextResponse } from 'next/server'
 import { prisma } from '@/server/db'
 import type { NextRequest } from 'next/server'
 
@@ -109,6 +109,8 @@ interface HealthResponse {
  *                   description: Error message
  */
 export async function GET(_request: NextRequest) {
+  await connection()
+
   try {
     const dbStart = Date.now()
     await prisma.$queryRaw`SELECT 1`
diff --git a/src/app/api/mobile/auth/route.ts b/src/app/api/mobile/auth/route.ts
index 758137c2c..9c7f47645 100644
--- a/src/app/api/mobile/auth/route.ts
+++ b/src/app/api/mobile/auth/route.ts
@@ -1,9 +1,11 @@
 import { auth } from '@clerk/nextjs/server'
-import { type NextRequest, NextResponse } from 'next/server'
+import { connection, type NextRequest, NextResponse } from 'next/server'
 import { getCORSHeaders } from '@/lib/cors'
 import { prisma } from '@/server/db'
 
 export async function GET(request: NextRequest) {
+  await connection()
+
   try {
     const { userId } = await auth()
 
diff --git a/src/app/api/mobile/trpc/[trpc]/route.ts b/src/app/api/mobile/trpc/[trpc]/route.ts
index ccedbb63b..5d9b36963 100644
--- a/src/app/api/mobile/trpc/[trpc]/route.ts
+++ b/src/app/api/mobile/trpc/[trpc]/route.ts
@@ -1,5 +1,5 @@
 import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
-import { type NextRequest, NextResponse } from 'next/server'
+import { connection, type NextRequest, NextResponse } from 'next/server'
 import { getCORSHeaders } from '@/lib/cors'
 import { createMobileTRPCFetchContext } from '@/server/api/mobileContext'
 import { mobileRouter } from '@/server/api/routers/mobile'
@@ -89,4 +89,9 @@ const handler = async (req: NextRequest) => {
   }
 }
 
-export { handler as GET, handler as POST }
+async function GET(req: NextRequest) {
+  await connection()
+  return handler(req)
+}
+
+export { GET, handler as POST }
diff --git a/src/app/api/notifications/stream/route.ts b/src/app/api/notifications/stream/route.ts
index 20041c0d3..ba7877bdc 100644
--- a/src/app/api/notifications/stream/route.ts
+++ b/src/app/api/notifications/stream/route.ts
@@ -1,11 +1,13 @@
 import { auth } from '@clerk/nextjs/server'
-import { type NextRequest } from 'next/server'
+import { connection, type NextRequest } from 'next/server'
 import {
   realtimeNotificationService,
   createSSEResponse,
 } from '@/server/notifications/realtimeService'
 
 export async function GET(request: NextRequest) {
+  await connection()
+
   try {
     // Get authenticated user
     const { userId } = await auth()
diff --git a/src/app/api/proxy-image/route.ts b/src/app/api/proxy-image/route.ts
index 9d9872d9c..557a805b8 100644
--- a/src/app/api/proxy-image/route.ts
+++ b/src/app/api/proxy-image/route.ts
@@ -1,4 +1,4 @@
-import { type NextRequest } from 'next/server'
+import { connection, type NextRequest } from 'next/server'
 import { logger } from '@/lib/logger'
 
 /**
@@ -16,6 +16,8 @@ function isAllowedUrl(raw?: string | null): URL | null {
 }
 
 export async function GET(req: NextRequest) {
+  await connection()
+
   const src = req.nextUrl.searchParams.get('url')
   const url = isAllowedUrl(src)
   if (!url) return new Response('Invalid or missing url parameter', { status: 400 })

From df5afd2a5610d328d7f3f4de0d854ccc2b19acdd Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:28:39 +0200
Subject: [PATCH 12/22] feat: refactor admin layout to use Suspense and
 separate client logic

---
 src/app/admin/AdminLayoutClient.tsx | 236 +++++++++++++++++++++++++
 src/app/admin/layout.tsx            | 258 +---------------------------
 2 files changed, 242 insertions(+), 252 deletions(-)
 create mode 100644 src/app/admin/AdminLayoutClient.tsx

diff --git a/src/app/admin/AdminLayoutClient.tsx b/src/app/admin/AdminLayoutClient.tsx
new file mode 100644
index 000000000..e3ea4effb
--- /dev/null
+++ b/src/app/admin/AdminLayoutClient.tsx
@@ -0,0 +1,236 @@
+'use client'
+
+import { RedirectToSignIn, SignedIn, SignedOut, useUser } from '@clerk/nextjs'
+import { ChevronLeft, ChevronRight, Home } from 'lucide-react'
+import Link from 'next/link'
+import { usePathname, useRouter } from 'next/navigation'
+import { useEffect, useState, type PropsWithChildren } from 'react'
+import { isNumber } from 'remeda'
+import { ADMIN_ROUTES } from '@/app/admin/config/routes'
+import { LoadingSpinner } from '@/components/ui/LoadingSpinner'
+import { api } from '@/lib/api'
+import { cn } from '@/lib/utils'
+import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
+import { hasRolePermission as hasRolePermission } from '@/utils/permissions'
+import { Role } from '@orm'
+import AdminNavbar from './components/AdminNavbar'
+import {
+  adminNavItems,
+  superAdminNavItems,
+  moderatorNavItems,
+  getDeveloperNavItems,
+  type AdminNavItem,
+} from './data'
+
+export default function AdminLayoutClient(props: PropsWithChildren) {
+  const pathname = usePathname()
+  const router = useRouter()
+  const [isCollapsed, setIsCollapsed] = useState(false)
+  const { user, isLoaded } = useUser()
+
+  const userQuery = api.users.me.useQuery(undefined, {
+    enabled: !!user,
+  })
+
+  const verifiedEmulatorsQuery = api.verifiedDevelopers.getMyVerifiedEmulators.useQuery(undefined, {
+    enabled: !!userQuery.data && userQuery.data.role === Role.DEVELOPER,
+  })
+
+  const gameStatsQuery = api.games.stats.useQuery(undefined, {
+    enabled:
+      !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS),
+    refetchInterval: 30000,
+    staleTime: 10000,
+    refetchOnMount: true,
+    refetchOnWindowFocus: true,
+  })
+
+  const listingStatsQuery = api.listings.stats.useQuery(undefined, {
+    enabled:
+      !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS),
+    refetchInterval: 30000,
+    staleTime: 10000,
+    refetchOnMount: true,
+    refetchOnWindowFocus: true,
+  })
+
+  const pcListingStatsQuery = api.pcListings.stats.useQuery(undefined, {
+    enabled:
+      !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS),
+    refetchInterval: 30000,
+    staleTime: 10000,
+    refetchOnMount: true,
+    refetchOnWindowFocus: true,
+  })
+
+  const isSuperAdmin = hasRolePermission(userQuery.data?.role, Role.SUPER_ADMIN)
+
+  const reportsStatsQuery = api.listingReports.stats.useQuery(undefined, {
+    enabled: !!userQuery.data && isSuperAdmin,
+    refetchInterval: 30000,
+    staleTime: 10000,
+    refetchOnMount: true,
+    refetchOnWindowFocus: true,
+  })
+  const isAdmin = hasRolePermission(userQuery.data?.role, Role.ADMIN)
+  const isModerator = hasRolePermission(userQuery.data?.role, Role.MODERATOR)
+  const isDeveloper = userQuery.data?.role === Role.DEVELOPER
+
+  const devPendingListingsQuery = api.listings.getPending.useQuery(
+    { page: 1, limit: 1 },
+    { enabled: isDeveloper },
+  )
+
+  const devPendingPcListingsQuery = api.pcListings.pending.useQuery(
+    { page: 1, limit: 1 },
+    { enabled: isDeveloper },
+  )
+
+  const hasAdminPanelAccess = hasPermission(
+    userQuery.data?.permissions,
+    PERMISSIONS.ACCESS_ADMIN_PANEL,
+  )
+
+  useEffect(() => {
+    if (!isLoaded || userQuery.isPending || !user) return
+
+    if (userQuery.data && !hasAdminPanelAccess) router.replace('/')
+  }, [isLoaded, user, userQuery.data, hasAdminPanelAccess, router, userQuery.isPending])
+
+  if (!isLoaded) return <LoadingSpinner size="lg" />
+
+  if (!user) {
+    return (
+      <SignedOut>
+        <RedirectToSignIn />
+      </SignedOut>
+    )
+  }
+
+  if (userQuery.isPending) return <LoadingSpinner size="lg" />
+
+  if (!userQuery.data) return null
+
+  if (!hasAdminPanelAccess) return null
+
+  const adminNavItemsWithCounts = adminNavItems.map((item) => {
+    if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) {
+      return { ...item, count: gameStatsQuery.data.pending }
+    }
+    if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) {
+      return { ...item, count: listingStatsQuery.data.pending }
+    }
+    if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) {
+      return { ...item, count: pcListingStatsQuery.data.pending }
+    }
+    return item
+  })
+
+  const superAdminNavItemsWithCounts = superAdminNavItems.map((item) => {
+    return item.href === ADMIN_ROUTES.REPORTS && reportsStatsQuery.data
+      ? { ...item, count: reportsStatsQuery.data.pending }
+      : item
+  })
+
+  let navItems: AdminNavItem[] = []
+  let superAdminItems: AdminNavItem[] = []
+
+  if (isSuperAdmin) {
+    navItems = adminNavItemsWithCounts
+    superAdminItems = superAdminNavItemsWithCounts
+  } else if (isAdmin) {
+    navItems = adminNavItemsWithCounts
+  } else if (isModerator) {
+    navItems = moderatorNavItems.map((item) => {
+      if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) {
+        return { ...item, count: gameStatsQuery.data.pending }
+      }
+      if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) {
+        return { ...item, count: listingStatsQuery.data.pending }
+      }
+      if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) {
+        return { ...item, count: pcListingStatsQuery.data.pending }
+      }
+      return item
+    })
+  } else if (isDeveloper && verifiedEmulatorsQuery.data) {
+    const emulatorIds = verifiedEmulatorsQuery.data.map((e) => e.id)
+    navItems = getDeveloperNavItems(emulatorIds)
+
+    const pendingHandheldCount = devPendingListingsQuery.data?.pagination.total
+    const pendingPcCount = devPendingPcListingsQuery.data?.pagination.total
+
+    navItems = navItems.map((item) => {
+      if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && isNumber(pendingHandheldCount)) {
+        return { ...item, count: pendingHandheldCount }
+      }
+      if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && isNumber(pendingPcCount)) {
+        return { ...item, count: pendingPcCount }
+      }
+      return item
+    })
+  }
+
+  return (
+    <>
+      <SignedIn>
+        <div className="min-h-screen flex bg-gray-50 dark:bg-gray-900">
+          <aside
+            className={cn(
+              'bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex-shrink-0 hidden md:flex flex-col transition-all duration-300 ease-in-out overflow-hidden',
+              isCollapsed ? 'w-20' : 'w-64',
+            )}
+          >
+            <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
+              {!isCollapsed && (
+                <Link href="/admin" className="font-bold text-gray-900 dark:text-white text-xl">
+                  Admin
+                </Link>
+              )}
+              <button
+                onClick={() => setIsCollapsed(!isCollapsed)}
+                className="p-1 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none transition-colors"
+              >
+                {isCollapsed ? (
+                  <ChevronRight className="w-5 h-5" />
+                ) : (
+                  <ChevronLeft className="w-5 h-5" />
+                )}
+              </button>
+            </div>
+
+            <AdminNavbar
+              pathname={pathname}
+              isCollapsed={isCollapsed}
+              isSuperAdmin={isSuperAdmin}
+              adminNavItems={navItems}
+              superAdminNavItems={superAdminItems}
+            />
+
+            <div className="p-3 border-t border-gray-200 dark:border-gray-700">
+              <Link
+                href="/"
+                className={cn(
+                  'flex items-center p-2 text-gray-600 dark:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
+                  isCollapsed ? 'justify-center' : 'justify-start',
+                )}
+              >
+                <Home className="w-5 h-5 min-w-5" />
+                {!isCollapsed && <span className="ml-3">Back to Site</span>}
+              </Link>
+            </div>
+          </aside>
+
+          <div className="flex-1 flex flex-col min-w-0">
+            <main className="flex-1 p-2 sm:px-4 md:px-6 overflow-auto">
+              <div className="max-w-full">{props.children}</div>
+            </main>
+          </div>
+        </div>
+      </SignedIn>
+      <SignedOut>
+        <RedirectToSignIn />
+      </SignedOut>
+    </>
+  )
+}
diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx
index b875748ce..46ea7bb9d 100644
--- a/src/app/admin/layout.tsx
+++ b/src/app/admin/layout.tsx
@@ -1,256 +1,10 @@
-'use client'
-
-import { RedirectToSignIn, SignedIn, SignedOut, useUser } from '@clerk/nextjs'
-import { ChevronLeft, ChevronRight, Home } from 'lucide-react'
-import Link from 'next/link'
-import { usePathname, useRouter } from 'next/navigation'
-import { useEffect, useState, type PropsWithChildren } from 'react'
-import { isNumber } from 'remeda'
-import { ADMIN_ROUTES } from '@/app/admin/config/routes'
-import { LoadingSpinner } from '@/components/ui/LoadingSpinner'
-import { api } from '@/lib/api'
-import { cn } from '@/lib/utils'
-import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
-import { hasRolePermission as hasRolePermission } from '@/utils/permissions'
-import { Role } from '@orm'
-import AdminNavbar from './components/AdminNavbar'
-import {
-  adminNavItems,
-  superAdminNavItems,
-  moderatorNavItems,
-  getDeveloperNavItems,
-  type AdminNavItem,
-} from './data'
-
-function AdminLayout(props: PropsWithChildren) {
-  const pathname = usePathname()
-  const router = useRouter()
-  const [isCollapsed, setIsCollapsed] = useState(false)
-  const { user, isLoaded } = useUser()
-
-  const userQuery = api.users.me.useQuery(undefined, {
-    enabled: !!user,
-  })
-
-  // For developers, fetch their verified emulators
-  const verifiedEmulatorsQuery = api.verifiedDevelopers.getMyVerifiedEmulators.useQuery(undefined, {
-    enabled: !!userQuery.data && userQuery.data.role === Role.DEVELOPER,
-  })
-
-  // Get pending games count for admin navigation
-  const gameStatsQuery = api.games.stats.useQuery(undefined, {
-    enabled:
-      !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS),
-    refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated)
-    staleTime: 10000, // Consider data stale after 10 seconds
-    refetchOnMount: true,
-    refetchOnWindowFocus: true,
-  })
-
-  // Get pending listings count for admin navigation
-  const listingStatsQuery = api.listings.stats.useQuery(undefined, {
-    enabled:
-      !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS),
-    refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated)
-    staleTime: 10000, // Consider data stale after 10 seconds
-    refetchOnMount: true,
-    refetchOnWindowFocus: true,
-  })
-
-  // Get pending PC listings count for admin navigation
-  const pcListingStatsQuery = api.pcListings.stats.useQuery(undefined, {
-    enabled:
-      !!userQuery.data && hasPermission(userQuery.data.permissions, PERMISSIONS.VIEW_STATISTICS),
-    refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated)
-    staleTime: 10000, // Consider data stale after 10 seconds
-    refetchOnMount: true,
-    refetchOnWindowFocus: true,
-  })
-
-  const isSuperAdmin = hasRolePermission(userQuery.data?.role, Role.SUPER_ADMIN)
-
-  // Get pending reports count for admin navigation
-  const reportsStatsQuery = api.listingReports.stats.useQuery(undefined, {
-    enabled: !!userQuery.data && isSuperAdmin,
-    refetchInterval: 30000, // Refetch every 30 seconds (to keep counts updated)
-    staleTime: 10000, // Consider data stale after 10 seconds
-    refetchOnMount: true,
-    refetchOnWindowFocus: true,
-  })
-  const isAdmin = hasRolePermission(userQuery.data?.role, Role.ADMIN)
-  const isModerator = hasRolePermission(userQuery.data?.role, Role.MODERATOR)
-  const isDeveloper = userQuery.data?.role === Role.DEVELOPER // Exact match, not hierarchical
-
-  const devPendingListingsQuery = api.listings.getPending.useQuery(
-    { page: 1, limit: 1 },
-    { enabled: isDeveloper },
-  )
-
-  const devPendingPcListingsQuery = api.pcListings.pending.useQuery(
-    { page: 1, limit: 1 },
-    { enabled: isDeveloper },
-  )
-
-  // New permission-based checks
-  const hasAdminPanelAccess = hasPermission(
-    userQuery.data?.permissions,
-    PERMISSIONS.ACCESS_ADMIN_PANEL,
-  )
-
-  useEffect(() => {
-    if (!isLoaded || userQuery.isPending || !user) return
-
-    // Check if a user has the right permissions once data is loaded
-    // Use permission-based access control
-    if (userQuery.data && !hasAdminPanelAccess) router.replace('/')
-  }, [isLoaded, user, userQuery.data, hasAdminPanelAccess, router, userQuery.isPending])
-
-  if (!isLoaded) return <LoadingSpinner size="lg" />
-
-  if (!user) {
-    return (
-      <SignedOut>
-        <RedirectToSignIn />
-      </SignedOut>
-    )
-  }
-
-  if (userQuery.isPending) return <LoadingSpinner size="lg" />
-
-  if (!userQuery.data) return null
-
-  // Don't render admin content if user doesn't have admin panel access
-  if (!hasAdminPanelAccess) return null
-
-  // Create navigation items with counts
-  const adminNavItemsWithCounts = adminNavItems.map((item) => {
-    if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) {
-      return { ...item, count: gameStatsQuery.data.pending }
-    }
-    if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) {
-      return { ...item, count: listingStatsQuery.data.pending }
-    }
-    if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) {
-      return { ...item, count: pcListingStatsQuery.data.pending }
-    }
-    return item
-  })
-
-  // Create super admin navigation items with counts
-  const superAdminNavItemsWithCounts = superAdminNavItems.map((item) => {
-    return item.href === ADMIN_ROUTES.REPORTS && reportsStatsQuery.data
-      ? { ...item, count: reportsStatsQuery.data.pending }
-      : item
-  })
-
-  // Select appropriate nav items based on a user role
-  let navItems: AdminNavItem[] = []
-  let superAdminItems: AdminNavItem[] = []
-
-  if (isSuperAdmin) {
-    navItems = adminNavItemsWithCounts
-    superAdminItems = superAdminNavItemsWithCounts
-  } else if (isAdmin) {
-    navItems = adminNavItemsWithCounts
-  } else if (isModerator) {
-    navItems = moderatorNavItems.map((item) => {
-      if (item.href === ADMIN_ROUTES.GAME_APPROVALS && gameStatsQuery.data) {
-        return { ...item, count: gameStatsQuery.data.pending }
-      }
-      if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && listingStatsQuery.data) {
-        return { ...item, count: listingStatsQuery.data.pending }
-      }
-      if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && pcListingStatsQuery.data) {
-        return { ...item, count: pcListingStatsQuery.data.pending }
-      }
-      return item
-    })
-  } else if (isDeveloper && verifiedEmulatorsQuery.data) {
-    const emulatorIds = verifiedEmulatorsQuery.data.map((e) => e.id)
-    navItems = getDeveloperNavItems(emulatorIds)
-
-    // Add counts to developer approvals links (handheld + PC)
-    const pendingHandheldCount = devPendingListingsQuery.data?.pagination.total
-    const pendingPcCount = devPendingPcListingsQuery.data?.pagination.total
-
-    navItems = navItems.map((item) => {
-      if (item.href === ADMIN_ROUTES.LISTING_APPROVALS && isNumber(pendingHandheldCount)) {
-        return { ...item, count: pendingHandheldCount }
-      }
-      if (item.href === ADMIN_ROUTES.PC_LISTING_APPROVALS && isNumber(pendingPcCount)) {
-        return { ...item, count: pendingPcCount }
-      }
-      return item
-    })
-  }
+import { Suspense, type PropsWithChildren } from 'react'
+import AdminLayoutClient from './AdminLayoutClient'
 
+export default function AdminLayout(props: PropsWithChildren) {
   return (
-    <>
-      <SignedIn>
-        <div className="min-h-screen flex bg-gray-50 dark:bg-gray-900">
-          {/* Sidebar */}
-          <aside
-            className={cn(
-              'bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex-shrink-0 hidden md:flex flex-col transition-all duration-300 ease-in-out overflow-hidden',
-              isCollapsed ? 'w-20' : 'w-64',
-            )}
-          >
-            {/* Sidebar Header */}
-            <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
-              {!isCollapsed && (
-                <Link href="/admin" className="font-bold text-gray-900 dark:text-white text-xl">
-                  Admin
-                </Link>
-              )}
-              <button
-                onClick={() => setIsCollapsed(!isCollapsed)}
-                className="p-1 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none transition-colors"
-              >
-                {isCollapsed ? (
-                  <ChevronRight className="w-5 h-5" />
-                ) : (
-                  <ChevronLeft className="w-5 h-5" />
-                )}
-              </button>
-            </div>
-
-            {/* Navigation */}
-            <AdminNavbar
-              pathname={pathname}
-              isCollapsed={isCollapsed}
-              isSuperAdmin={isSuperAdmin}
-              adminNavItems={navItems}
-              superAdminNavItems={superAdminItems}
-            />
-
-            {/* Back to Site Link */}
-            <div className="p-3 border-t border-gray-200 dark:border-gray-700">
-              <Link
-                href="/"
-                className={cn(
-                  'flex items-center p-2 text-gray-600 dark:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
-                  isCollapsed ? 'justify-center' : 'justify-start',
-                )}
-              >
-                <Home className="w-5 h-5 min-w-5" />
-                {!isCollapsed && <span className="ml-3">Back to Site</span>}
-              </Link>
-            </div>
-          </aside>
-
-          {/* Main Content Area */}
-          <div className="flex-1 flex flex-col min-w-0">
-            <main className="flex-1 p-2 sm:px-4 md:px-6 overflow-auto">
-              <div className="max-w-full">{props.children}</div>
-            </main>
-          </div>
-        </div>
-      </SignedIn>
-      <SignedOut>
-        <RedirectToSignIn />
-      </SignedOut>
-    </>
+    <Suspense fallback={null}>
+      <AdminLayoutClient>{props.children}</AdminLayoutClient>
+    </Suspense>
   )
 }
-
-export default AdminLayout

From 83037a604432ad787549504a934a4bc7f7e158d0 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:29:13 +0200
Subject: [PATCH 13/22] feat: wrap components in Suspense for improved loading
 handling

---
 src/app/layout.tsx | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3639b1033..5d2a1c30d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -60,21 +60,27 @@ export default function RootLayout(props: PropsWithChildren) {
             <Toaster richColors closeButton />
             {env.IS_PROD && !env.DISABLE_COOKIE_BANNER && <CookieConsent />}
             <div className="flex flex-col min-h-screen bg-background text-foreground">
-              <Navbar />
+              <Suspense fallback={null}>
+                <Navbar />
+              </Suspense>
               <Main>{props.children}</Main>
               <Footer />
             </div>
           </Providers>
           {env.IS_PROD && (
-            <>
+            <Suspense fallback={null}>
               <SessionTracker />
               <PageViewTracker />
               <SpeedInsights />
               <KofiWidget />
               <GoogleAnalytics gaId={env.GA_ID} />
-            </>
+            </Suspense>
+          )}
+          {env.VERCEL_ANALYTICS_ENABLED && (
+            <Suspense fallback={null}>
+              <Analytics />
+            </Suspense>
           )}
-          {env.VERCEL_ANALYTICS_ENABLED && <Analytics />}
         </ClerkBoundary>
       </body>
     </html>
@@ -84,7 +90,9 @@ export default function RootLayout(props: PropsWithChildren) {
 function ClerkBoundary(props: PropsWithChildren) {
   if (process.env.NODE_ENV !== 'development') {
     return (
-      <ClerkProvider appearance={{ baseTheme: shadesOfPurple }}>{props.children}</ClerkProvider>
+      <Suspense fallback={null}>
+        <ClerkProvider appearance={{ baseTheme: shadesOfPurple }}>{props.children}</ClerkProvider>
+      </Suspense>
     )
   }
 

From 67a2b916412fcca673a5b923d3422d0955972893 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 00:30:56 +0200
Subject: [PATCH 14/22] feat: wrap components in Suspense for improved loading
 handling

---
 .../[emulatorId]/EditEmulatorClientPage.tsx   | 127 ++++++++++
 .../EmulatorCustomFieldsClientPage.tsx        | 215 +++++++++++++++++
 .../[emulatorId]/custom-fields/page.tsx       | 221 ++----------------
 src/app/admin/emulators/[emulatorId]/page.tsx | 134 ++---------
 .../games/[id]/AdminGameEditClientPage.tsx    |  92 ++++++++
 src/app/admin/games/[id]/page.tsx             | 101 ++------
 .../[id]/edit/EditListingClientPage.tsx       |  59 +++++
 src/app/admin/listings/[id]/edit/page.tsx     |  66 ++----
 src/app/games/[id]/page.tsx                   |  16 +-
 src/app/listings/[id]/page.tsx                |  16 +-
 src/app/pc-listings/[id]/page.tsx             |  16 +-
 src/app/terms/page.tsx                        |   3 +-
 src/app/users/[id]/page.tsx                   |  18 +-
 13 files changed, 608 insertions(+), 476 deletions(-)
 create mode 100644 src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx
 create mode 100644 src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
 create mode 100644 src/app/admin/games/[id]/AdminGameEditClientPage.tsx
 create mode 100644 src/app/admin/listings/[id]/edit/EditListingClientPage.tsx

diff --git a/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx b/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx
new file mode 100644
index 000000000..fdbac088b
--- /dev/null
+++ b/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx
@@ -0,0 +1,127 @@
+'use client'
+
+import { ArrowLeft } from 'lucide-react'
+import { notFound, useRouter } from 'next/navigation'
+import { Button, PageSkeletonLoading } from '@/components/ui'
+import { api } from '@/lib/api'
+import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
+import { Role } from '@orm'
+import EmulatorEditForm from './components/EmulatorEditForm'
+import ManageSupportedSystems from './components/ManageSupportedSystems'
+
+interface Props {
+  emulatorId: string
+}
+
+export default function EditEmulatorClientPage(props: Props) {
+  const router = useRouter()
+
+  const emulatorsQuery = api.emulators.byId.useQuery(
+    { id: props.emulatorId },
+    { enabled: !!props.emulatorId },
+  )
+  const currentUser = api.users.me.useQuery()
+  const verifiedDeveloperQuery = api.emulators.getVerifiedDeveloper.useQuery(
+    { emulatorId: props.emulatorId },
+    {
+      enabled: !!props.emulatorId && currentUser.data?.role === Role.DEVELOPER,
+    },
+  )
+
+  const user = currentUser.data
+  const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS)
+  const isDeveloper = user?.role === Role.DEVELOPER
+  const isVerifiedDeveloper = Boolean(verifiedDeveloperQuery.data)
+  const hasAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper))
+
+  const systemsQuery = api.systems.get.useQuery({}, { enabled: !!user && hasAccess })
+
+  const isLoading =
+    currentUser.isPending || emulatorsQuery.isLoading || verifiedDeveloperQuery.isLoading
+
+  if (isLoading) return <PageSkeletonLoading />
+
+  if (!user || !hasAccess) {
+    return (
+      <div className="container mx-auto p-8">
+        <Button
+          variant="outline"
+          size="sm"
+          icon={ArrowLeft}
+          onClick={() => router.back()}
+          className="mb-6"
+        >
+          Back
+        </Button>
+        <p className="text-lg text-gray-700 dark:text-gray-200">
+          You do not have permission to manage this emulator.
+        </p>
+      </div>
+    )
+  }
+
+  if (systemsQuery.isPending) return <PageSkeletonLoading />
+
+  if (emulatorsQuery.error || systemsQuery.error) {
+    return (
+      <div className="container mx-auto p-4">
+        <p className="text-red-500">
+          Error loading data:{' '}
+          {emulatorsQuery.error?.message ??
+            systemsQuery.error?.message ??
+            'An unknown error occurred'}
+        </p>
+      </div>
+    )
+  }
+
+  if (!emulatorsQuery.data || !systemsQuery.data) return notFound()
+
+  return (
+    <div className="container mx-auto p-4 md:p-8">
+      <Button
+        variant="outline"
+        size="sm"
+        icon={ArrowLeft}
+        onClick={() => router.back()}
+        className="mb-6"
+      >
+        Back
+      </Button>
+
+      <h1 className="text-3xl font-bold mb-8 text-gray-800 dark:text-white">
+        Edit Emulator:{' '}
+        <span className="text-blue-600 dark:text-blue-400">{emulatorsQuery.data.name}</span>
+      </h1>
+
+      <div className="space-y-10">
+        <section className="bg-white dark:bg-gray-800 shadow-xl rounded-lg p-6">
+          <h2 className="text-xl font-semibold mb-6 text-gray-700 dark:text-gray-200 border-b pb-3 dark:border-gray-700">
+            Emulator Details
+          </h2>
+          <EmulatorEditForm emulator={emulatorsQuery.data} />
+        </section>
+
+        <section className="bg-white dark:bg-gray-800 shadow-xl rounded-lg p-6">
+          <h2 className="text-xl font-semibold mb-6 text-gray-700 dark:text-gray-200 border-b pb-3 dark:border-gray-700">
+            Supported Systems
+          </h2>
+          <ManageSupportedSystems
+            emulatorId={emulatorsQuery.data.id}
+            allSystems={systemsQuery.data}
+            currentlySupportedSystems={emulatorsQuery.data.systems}
+          />
+        </section>
+
+        <section className="bg-white dark:bg-gray-800 shadow-xl rounded-lg p-6">
+          <h2 className="text-xl font-semibold mb-6 text-gray-700 dark:text-gray-200 border-b pb-3 dark:border-gray-700">
+            Custom Fields
+          </h2>
+          <Button onClick={() => router.push(`/admin/emulators/${props.emulatorId}/custom-fields`)}>
+            Manage Custom Fields
+          </Button>
+        </section>
+      </div>
+    </div>
+  )
+}
diff --git a/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
new file mode 100644
index 000000000..97f713b3d
--- /dev/null
+++ b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
@@ -0,0 +1,215 @@
+'use client'
+
+import { PlusCircle, Copy, FileJson, ShieldAlert } from 'lucide-react'
+import Link from 'next/link'
+import { useState } from 'react'
+import UnauthenticatedPage from '@/components/auth/UnauthenticatedPage'
+import { Button, LoadingSpinner } from '@/components/ui'
+import { api } from '@/lib/api'
+import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
+import { hasRolePermission } from '@/utils/permissions'
+import { Role } from '@orm'
+import ApplyTemplatesModal from './components/ApplyTemplatesModal'
+import CustomFieldFormModal from './components/CustomFieldFormModal'
+import CustomFieldsDragAndDrop from './components/CustomFieldsDragAndDrop'
+import CustomFieldsJsonModal from './components/CustomFieldsJsonModal'
+import FeedbackCard from './components/FeedbackCard'
+
+interface Props {
+  emulatorId: string
+}
+
+export default function EmulatorCustomFieldsClientPage(props: Props) {
+  const [isFormModalOpen, setIsFormModalOpen] = useState(false)
+  const [isApplyTemplatesModalOpen, setIsApplyTemplatesModalOpen] = useState(false)
+  const [isJsonModalOpen, setIsJsonModalOpen] = useState(false)
+  const [editingFieldId, setEditingFieldId] = useState<string | null>(null)
+
+  const userQuery = api.users.me.useQuery()
+  const emulatorQuery = api.emulators.byId.useQuery(
+    { id: props.emulatorId },
+    { enabled: !!props.emulatorId },
+  )
+
+  const customFieldDefinitionsQuery = api.customFieldDefinitions.getByEmulator.useQuery(
+    { emulatorId: props.emulatorId },
+    { enabled: !!props.emulatorId },
+  )
+
+  const categoriesQuery = api.customFieldCategories.getByEmulator.useQuery(
+    { emulatorId: props.emulatorId },
+    { enabled: !!props.emulatorId },
+  )
+
+  const user = userQuery.data
+  const isLoading = userQuery.isPending && !userQuery.isError
+  const isEmulatorLoading = emulatorQuery.isLoading
+  const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS)
+  const isDeveloper = user?.role === Role.DEVELOPER
+  const isVerifiedDeveloper = Boolean(
+    emulatorQuery.data?.verifiedDevelopers?.some((developer) => developer.userId === user?.id),
+  )
+  const canAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper))
+
+  const backToEmulatorsAction = (
+    <Link href="/admin/emulators" className="inline-flex">
+      <Button variant="outline">Back to Emulators</Button>
+    </Link>
+  )
+
+  if (isLoading || isEmulatorLoading) {
+    return (
+      <div className="flex justify-center py-12">
+        <LoadingSpinner text="Loading custom fields..." />
+      </div>
+    )
+  }
+
+  if (userQuery.isError || !user) {
+    return (
+      <UnauthenticatedPage
+        icon={ShieldAlert}
+        title="Admin Access Required"
+        subtitle="Sign in to access the admin panel"
+        description="This area is restricted to administrators. Please sign in with your admin account to manage custom fields."
+        showSignUp={false}
+      />
+    )
+  }
+
+  if (!canAccess) {
+    return (
+      <FeedbackCard
+        title="Access Denied"
+        description="You do not have permission to manage custom fields for this emulator."
+        actions={backToEmulatorsAction}
+      />
+    )
+  }
+
+  const canApplyTemplates = hasRolePermission(user.role, Role.SUPER_ADMIN)
+
+  function handleOpenCreateModal() {
+    setEditingFieldId(null)
+    setIsFormModalOpen(true)
+  }
+
+  function handleOpenEditModal(fieldId: string) {
+    setEditingFieldId(fieldId)
+    setIsFormModalOpen(true)
+  }
+
+  function handleCloseModal() {
+    setIsFormModalOpen(false)
+    setEditingFieldId(null)
+    void customFieldDefinitionsQuery.refetch()
+  }
+
+  function handleRefresh() {
+    void customFieldDefinitionsQuery.refetch()
+    void categoriesQuery.refetch()
+  }
+
+  function handleFieldDelete() {
+    void customFieldDefinitionsQuery.refetch()
+  }
+
+  if (customFieldDefinitionsQuery.isLoading || categoriesQuery.isLoading) {
+    return (
+      <div className="flex justify-center py-12">
+        <LoadingSpinner text="Loading custom fields..." />
+      </div>
+    )
+  }
+
+  if (emulatorQuery.error) {
+    return (
+      <FeedbackCard
+        title="Failed to load emulator"
+        description={emulatorQuery.error.message ?? 'Please try again later.'}
+        actions={backToEmulatorsAction}
+      />
+    )
+  }
+
+  if (customFieldDefinitionsQuery.error) {
+    return (
+      <FeedbackCard
+        title="Failed to load custom fields"
+        description={customFieldDefinitionsQuery.error.message ?? 'Please try again later.'}
+        actions={backToEmulatorsAction}
+      />
+    )
+  }
+
+  if (!emulatorQuery.data) {
+    return (
+      <FeedbackCard
+        title="Emulator not found"
+        description="The emulator you are trying to manage was not found or you no longer have access to it."
+        actions={backToEmulatorsAction}
+      />
+    )
+  }
+
+  return (
+    <div className="container mx-auto py-8">
+      <div className="flex justify-between items-center mb-6">
+        <h1 className="text-3xl font-bold">Custom Fields for {emulatorQuery.data.name}</h1>
+        <div className="flex space-x-3">
+          <Button variant="outline" icon={FileJson} onClick={() => setIsJsonModalOpen(true)}>
+            View JSON
+          </Button>
+          {canApplyTemplates && (
+            <Button
+              variant="outline"
+              icon={Copy}
+              onClick={() => setIsApplyTemplatesModalOpen(true)}
+            >
+              Apply Templates
+            </Button>
+          )}
+          <Button icon={PlusCircle} onClick={handleOpenCreateModal}>
+            Add Custom Field
+          </Button>
+        </div>
+      </div>
+
+      <CustomFieldsDragAndDrop
+        emulatorId={props.emulatorId}
+        fields={customFieldDefinitionsQuery.data ?? []}
+        categories={categoriesQuery.data ?? []}
+        onFieldEdit={handleOpenEditModal}
+        onFieldDelete={handleFieldDelete}
+        onRefresh={handleRefresh}
+      />
+
+      {isFormModalOpen && (
+        <CustomFieldFormModal
+          emulatorId={props.emulatorId}
+          fieldIdToEdit={editingFieldId}
+          isOpen={isFormModalOpen}
+          onClose={handleCloseModal}
+        />
+      )}
+
+      {isApplyTemplatesModalOpen && (
+        <ApplyTemplatesModal
+          emulatorId={props.emulatorId}
+          isOpen={isApplyTemplatesModalOpen}
+          onClose={() => setIsApplyTemplatesModalOpen(false)}
+          onSuccess={() => customFieldDefinitionsQuery.refetch()}
+        />
+      )}
+
+      {isJsonModalOpen && emulatorQuery.data && customFieldDefinitionsQuery.data && (
+        <CustomFieldsJsonModal
+          isOpen={isJsonModalOpen}
+          onClose={() => setIsJsonModalOpen(false)}
+          emulator={{ id: emulatorQuery.data.id, name: emulatorQuery.data.name }}
+          customFields={customFieldDefinitionsQuery.data}
+        />
+      )}
+    </div>
+  )
+}
diff --git a/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx b/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx
index e6b11c7c1..6caf4e739 100644
--- a/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx
+++ b/src/app/admin/emulators/[emulatorId]/custom-fields/page.tsx
@@ -1,213 +1,20 @@
-'use client'
+import { Suspense } from 'react'
+import EmulatorCustomFieldsClientPage from './EmulatorCustomFieldsClientPage'
 
-import { PlusCircle, Copy, FileJson, ShieldAlert } from 'lucide-react'
-import Link from 'next/link'
-import { useParams } from 'next/navigation'
-import { useState } from 'react'
-import UnauthenticatedPage from '@/components/auth/UnauthenticatedPage'
-import { Button, LoadingSpinner } from '@/components/ui'
-import { api } from '@/lib/api'
-import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
-import { hasRolePermission } from '@/utils/permissions'
-import { Role } from '@orm'
-import ApplyTemplatesModal from './components/ApplyTemplatesModal'
-import CustomFieldFormModal from './components/CustomFieldFormModal'
-import CustomFieldsDragAndDrop from './components/CustomFieldsDragAndDrop'
-import CustomFieldsJsonModal from './components/CustomFieldsJsonModal'
-import FeedbackCard from './components/FeedbackCard'
-
-export default function EmulatorCustomFieldsPage() {
-  const params = useParams()
-  const emulatorId = params.emulatorId as string
-
-  const [isFormModalOpen, setIsFormModalOpen] = useState(false)
-  const [isApplyTemplatesModalOpen, setIsApplyTemplatesModalOpen] = useState(false)
-  const [isJsonModalOpen, setIsJsonModalOpen] = useState(false)
-  const [editingFieldId, setEditingFieldId] = useState<string | null>(null)
-
-  const userQuery = api.users.me.useQuery()
-  const emulatorQuery = api.emulators.byId.useQuery({ id: emulatorId }, { enabled: !!emulatorId })
-
-  const customFieldDefinitionsQuery = api.customFieldDefinitions.getByEmulator.useQuery(
-    { emulatorId },
-    { enabled: !!emulatorId },
-  )
-
-  const categoriesQuery = api.customFieldCategories.getByEmulator.useQuery(
-    { emulatorId },
-    { enabled: !!emulatorId },
-  )
-
-  const user = userQuery.data
-  const isLoading = userQuery.isPending && !userQuery.isError
-  const isEmulatorLoading = emulatorQuery.isLoading
-  const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS)
-  const isDeveloper = user?.role === Role.DEVELOPER
-  const isVerifiedDeveloper = Boolean(
-    emulatorQuery.data?.verifiedDevelopers?.some((developer) => developer.userId === user?.id),
-  )
-  const canAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper))
-
-  const backToEmulatorsAction = (
-    <Link href="/admin/emulators" className="inline-flex">
-      <Button variant="outline">Back to Emulators</Button>
-    </Link>
-  )
-
-  if (isLoading || isEmulatorLoading) {
-    return (
-      <div className="flex justify-center py-12">
-        <LoadingSpinner text="Loading custom fields…" />
-      </div>
-    )
-  }
-
-  if (userQuery.isError || !user) {
-    return (
-      <UnauthenticatedPage
-        icon={ShieldAlert}
-        title="Admin Access Required"
-        subtitle="Sign in to access the admin panel"
-        description="This area is restricted to administrators. Please sign in with your admin account to manage custom fields."
-        showSignUp={false}
-      />
-    )
-  }
-
-  if (!canAccess) {
-    return (
-      <FeedbackCard
-        title="Access Denied"
-        description="You do not have permission to manage custom fields for this emulator."
-        actions={backToEmulatorsAction}
-      />
-    )
-  }
-
-  // Check if user can see Apply Templates button (only SUPER_ADMIN)
-  const canApplyTemplates = hasRolePermission(user.role, Role.SUPER_ADMIN)
-
-  function handleOpenCreateModal() {
-    setEditingFieldId(null)
-    setIsFormModalOpen(true)
-  }
-
-  function handleOpenEditModal(fieldId: string) {
-    setEditingFieldId(fieldId)
-    setIsFormModalOpen(true)
-  }
-
-  function handleCloseModal() {
-    setIsFormModalOpen(false)
-    setEditingFieldId(null)
-    void customFieldDefinitionsQuery.refetch()
-  }
-
-  function handleRefresh() {
-    void customFieldDefinitionsQuery.refetch()
-    void categoriesQuery.refetch()
-  }
-
-  function handleFieldDelete() {
-    void customFieldDefinitionsQuery.refetch()
-  }
-
-  if (customFieldDefinitionsQuery.isLoading || categoriesQuery.isLoading) {
-    return (
-      <div className="flex justify-center py-12">
-        <LoadingSpinner text="Loading custom fields…" />
-      </div>
-    )
-  }
-
-  if (emulatorQuery.error) {
-    return (
-      <FeedbackCard
-        title="Failed to load emulator"
-        description={emulatorQuery.error.message ?? 'Please try again later.'}
-        actions={backToEmulatorsAction}
-      />
-    )
-  }
-
-  if (customFieldDefinitionsQuery.error) {
-    return (
-      <FeedbackCard
-        title="Failed to load custom fields"
-        description={customFieldDefinitionsQuery.error.message ?? 'Please try again later.'}
-        actions={backToEmulatorsAction}
-      />
-    )
-  }
-
-  if (!emulatorQuery.data) {
-    return (
-      <FeedbackCard
-        title="Emulator not found"
-        description="The emulator you are trying to manage was not found or you no longer have access to it."
-        actions={backToEmulatorsAction}
-      />
-    )
-  }
+interface Props {
+  params: Promise<{ emulatorId: string }>
+}
 
+export default function EmulatorCustomFieldsPage(props: Props) {
   return (
-    <div className="container mx-auto py-8">
-      <div className="flex justify-between items-center mb-6">
-        <h1 className="text-3xl font-bold">Custom Fields for {emulatorQuery.data.name}</h1>
-        <div className="flex space-x-3">
-          <Button variant="outline" icon={FileJson} onClick={() => setIsJsonModalOpen(true)}>
-            View JSON
-          </Button>
-          {canApplyTemplates && (
-            <Button
-              variant="outline"
-              icon={Copy}
-              onClick={() => setIsApplyTemplatesModalOpen(true)}
-            >
-              Apply Templates
-            </Button>
-          )}
-          <Button icon={PlusCircle} onClick={handleOpenCreateModal}>
-            Add Custom Field
-          </Button>
-        </div>
-      </div>
-
-      <CustomFieldsDragAndDrop
-        emulatorId={emulatorId}
-        fields={customFieldDefinitionsQuery.data ?? []}
-        categories={categoriesQuery.data ?? []}
-        onFieldEdit={handleOpenEditModal}
-        onFieldDelete={handleFieldDelete}
-        onRefresh={handleRefresh}
-      />
-
-      {isFormModalOpen && (
-        <CustomFieldFormModal
-          emulatorId={emulatorId}
-          fieldIdToEdit={editingFieldId}
-          isOpen={isFormModalOpen}
-          onClose={handleCloseModal}
-        />
-      )}
+    <Suspense fallback={<div className="flex justify-center py-12">Loading custom fields...</div>}>
+      <EmulatorCustomFieldsPageContent params={props.params} />
+    </Suspense>
+  )
+}
 
-      {isApplyTemplatesModalOpen && (
-        <ApplyTemplatesModal
-          emulatorId={emulatorId}
-          isOpen={isApplyTemplatesModalOpen}
-          onClose={() => setIsApplyTemplatesModalOpen(false)}
-          onSuccess={() => customFieldDefinitionsQuery.refetch()}
-        />
-      )}
+async function EmulatorCustomFieldsPageContent(props: Props) {
+  const { emulatorId } = await props.params
 
-      {isJsonModalOpen && emulatorQuery.data && customFieldDefinitionsQuery.data && (
-        <CustomFieldsJsonModal
-          isOpen={isJsonModalOpen}
-          onClose={() => setIsJsonModalOpen(false)}
-          emulator={{ id: emulatorQuery.data.id, name: emulatorQuery.data.name }}
-          customFields={customFieldDefinitionsQuery.data}
-        />
-      )}
-    </div>
-  )
+  return <EmulatorCustomFieldsClientPage emulatorId={emulatorId} />
 }
diff --git a/src/app/admin/emulators/[emulatorId]/page.tsx b/src/app/admin/emulators/[emulatorId]/page.tsx
index 103c4de95..33fa1de08 100644
--- a/src/app/admin/emulators/[emulatorId]/page.tsx
+++ b/src/app/admin/emulators/[emulatorId]/page.tsx
@@ -1,126 +1,20 @@
-'use client'
+import { Suspense } from 'react'
+import EditEmulatorClientPage from './EditEmulatorClientPage'
 
-import { ArrowLeft } from 'lucide-react'
-import { notFound, useParams, useRouter } from 'next/navigation'
-import { Button, PageSkeletonLoading } from '@/components/ui'
-import { api } from '@/lib/api'
-import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
-import { Role } from '@orm'
-import EmulatorEditForm from './components/EmulatorEditForm'
-import ManageSupportedSystems from './components/ManageSupportedSystems'
-
-function EditEmulatorPage() {
-  const router = useRouter()
-  const params = useParams()
-  const emulatorId = params.emulatorId as string
-
-  const emulatorsQuery = api.emulators.byId.useQuery({ id: emulatorId }, { enabled: !!emulatorId })
-  const currentUser = api.users.me.useQuery()
-  const verifiedDeveloperQuery = api.emulators.getVerifiedDeveloper.useQuery(
-    { emulatorId },
-    {
-      enabled: !!emulatorId && currentUser.data?.role === Role.DEVELOPER,
-    },
-  )
-
-  const user = currentUser.data
-  const hasManagePermission = hasPermission(user?.permissions, PERMISSIONS.MANAGE_CUSTOM_FIELDS)
-  const isDeveloper = user?.role === Role.DEVELOPER
-  const isVerifiedDeveloper = Boolean(verifiedDeveloperQuery.data)
-  const hasAccess = Boolean(user && hasManagePermission && (!isDeveloper || isVerifiedDeveloper))
-
-  const systemsQuery = api.systems.get.useQuery({}, { enabled: !!user && hasAccess })
-
-  const isLoading =
-    currentUser.isPending || emulatorsQuery.isLoading || verifiedDeveloperQuery.isLoading
-
-  if (isLoading) return <PageSkeletonLoading />
-
-  if (!user || !hasAccess) {
-    return (
-      <div className="container mx-auto p-8">
-        <Button
-          variant="outline"
-          size="sm"
-          icon={ArrowLeft}
-          onClick={() => router.back()}
-          className="mb-6"
-        >
-          Back
-        </Button>
-        <p className="text-lg text-gray-700 dark:text-gray-200">
-          You do not have permission to manage this emulator.
-        </p>
-      </div>
-    )
-  }
-
-  if (systemsQuery.isPending) return <PageSkeletonLoading />
-
-  if (emulatorsQuery.error || systemsQuery.error) {
-    return (
-      <div className="container mx-auto p-4">
-        <p className="text-red-500">
-          Error loading data:{' '}
-          {emulatorsQuery.error?.message ??
-            systemsQuery.error?.message ??
-            'An unknown error occurred'}
-        </p>
-      </div>
-    )
-  }
-
-  if (!emulatorsQuery.data || !systemsQuery.data) return notFound()
+interface Props {
+  params: Promise<{ emulatorId: string }>
+}
 
+export default function EditEmulatorPage(props: Props) {
   return (
-    <div className="container mx-auto p-4 md:p-8">
-      <Button
-        variant="outline"
-        size="sm"
-        icon={ArrowLeft}
-        onClick={() => router.back()}
-        className="mb-6"
-      >
-        Back
-      </Button>
-
-      <h1 className="text-3xl font-bold mb-8 text-gray-800 dark:text-white">
-        Edit Emulator:{' '}
-        <span className="text-blue-600 dark:text-blue-400">{emulatorsQuery.data.name}</span>
-      </h1>
-
-      <div className="space-y-10">
-        {/* Section for Emulator Details */}
-        <section className="bg-white dark:bg-gray-800 shadow-xl rounded-lg p-6">
-          <h2 className="text-xl font-semibold mb-6 text-gray-700 dark:text-gray-200 border-b pb-3 dark:border-gray-700">
-            Emulator Details
-          </h2>
-          <EmulatorEditForm emulator={emulatorsQuery.data} />
-        </section>
-
-        {/* Section for Supported Systems */}
-        <section className="bg-white dark:bg-gray-800 shadow-xl rounded-lg p-6">
-          <h2 className="text-xl font-semibold mb-6 text-gray-700 dark:text-gray-200 border-b pb-3 dark:border-gray-700">
-            Supported Systems
-          </h2>
-          <ManageSupportedSystems
-            emulatorId={emulatorsQuery.data.id}
-            allSystems={systemsQuery.data}
-            currentlySupportedSystems={emulatorsQuery.data.systems} // emulator.systems should be populated by the byId query
-          />
-        </section>
-
-        <section className="bg-white dark:bg-gray-800 shadow-xl rounded-lg p-6">
-          <h2 className="text-xl font-semibold mb-6 text-gray-700 dark:text-gray-200 border-b pb-3 dark:border-gray-700">
-            Custom Fields
-          </h2>
-          <Button onClick={() => router.push(`/admin/emulators/${emulatorId}/custom-fields`)}>
-            Manage Custom Fields
-          </Button>
-        </section>
-      </div>
-    </div>
+    <Suspense fallback={<div className="container mx-auto p-8">Loading emulator...</div>}>
+      <EditEmulatorPageContent params={props.params} />
+    </Suspense>
   )
 }
 
-export default EditEmulatorPage
+async function EditEmulatorPageContent(props: Props) {
+  const { emulatorId } = await props.params
+
+  return <EditEmulatorClientPage emulatorId={emulatorId} />
+}
diff --git a/src/app/admin/games/[id]/AdminGameEditClientPage.tsx b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
new file mode 100644
index 000000000..f50e0b3da
--- /dev/null
+++ b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
@@ -0,0 +1,92 @@
+'use client'
+
+import { ArrowLeft } from 'lucide-react'
+import Link from 'next/link'
+import { notFound } from 'next/navigation'
+import { Button, PageSkeletonLoading } from '@/components/ui'
+import { api } from '@/lib/api'
+import { GameEditForm } from './components/GameEditForm'
+import { GameRelatedData } from './components/GameRelatedData'
+
+interface Props {
+  gameId: string
+}
+
+export default function AdminGameEditClientPage(props: Props) {
+  const gameQuery = api.games.byId.useQuery({ id: props.gameId })
+
+  if (gameQuery.isPending) return <PageSkeletonLoading />
+
+  if (gameQuery.error || !gameQuery.data) return notFound()
+
+  return (
+    <div className="space-y-6">
+      <div className="flex items-center gap-4">
+        <Button variant="outline" size="sm" asChild>
+          <Link href="/admin/games">
+            <ArrowLeft className="h-4 w-4" />
+            Back
+          </Link>
+        </Button>
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">
+          Edit Game: {gameQuery.data.title}
+        </h1>
+      </div>
+
+      <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
+        <div className="lg:col-span-2 space-y-6">
+          <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
+            <h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
+              Game Details
+            </h2>
+            <GameEditForm game={gameQuery.data} />
+          </div>
+
+          <GameRelatedData game={gameQuery.data} />
+        </div>
+
+        <div className="space-y-6">
+          <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
+            <h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
+              Quick Actions
+            </h3>
+            <div className="space-y-3">
+              <Button asChild variant="outline" className="w-full justify-start">
+                <Link href={`/games/${gameQuery.data.id}`} target="_blank">
+                  View Public Page
+                </Link>
+              </Button>
+              <Button asChild variant="outline" className="w-full justify-start">
+                <Link href={`/listings/new?gameId=${gameQuery.data.id}`}>Add New Listing</Link>
+              </Button>
+            </div>
+          </div>
+
+          <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
+            <h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
+              Compatible Emulators
+            </h3>
+            {gameQuery.data.system.emulators.length > 0 ? (
+              <ul className="space-y-2">
+                {gameQuery.data.system.emulators.map((emulator) => (
+                  <li key={emulator.id}>
+                    <Link
+                      href={`/admin/emulators/${emulator.id}`}
+                      className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
+                    >
+                      {emulator.name}
+                    </Link>
+                  </li>
+                ))}
+              </ul>
+            ) : (
+              <p className="text-gray-500 dark:text-gray-400 text-sm">
+                No emulators support this system yet.
+              </p>
+            )}
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
diff --git a/src/app/admin/games/[id]/page.tsx b/src/app/admin/games/[id]/page.tsx
index 29fc9685c..9186fbe45 100644
--- a/src/app/admin/games/[id]/page.tsx
+++ b/src/app/admin/games/[id]/page.tsx
@@ -1,93 +1,20 @@
-'use client'
+import { Suspense } from 'react'
+import AdminGameEditClientPage from './AdminGameEditClientPage'
 
-import { ArrowLeft } from 'lucide-react'
-import Link from 'next/link'
-import { notFound, useParams } from 'next/navigation'
-import { Button, PageSkeletonLoading } from '@/components/ui'
-import { api } from '@/lib/api'
-import { GameEditForm } from './components/GameEditForm'
-import { GameRelatedData } from './components/GameRelatedData'
-
-function AdminGameEditPage() {
-  const params = useParams()
-  const id = params.id as string
-
-  const gameQuery = api.games.byId.useQuery({ id })
-
-  if (gameQuery.isPending) return <PageSkeletonLoading />
-
-  if (gameQuery.error || !gameQuery.data) return notFound()
+interface Props {
+  params: Promise<{ id: string }>
+}
 
+export default function AdminGameEditPage(props: Props) {
   return (
-    <div className="space-y-6">
-      <div className="flex items-center gap-4">
-        <Button variant="outline" size="sm" asChild>
-          <Link href="/admin/games">
-            <ArrowLeft className="h-4 w-4" />
-            Back
-          </Link>
-        </Button>
-        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">
-          Edit Game: {gameQuery.data.title}
-        </h1>
-      </div>
-
-      <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
-        <div className="lg:col-span-2 space-y-6">
-          <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
-            <h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
-              Game Details
-            </h2>
-            <GameEditForm game={gameQuery.data} />
-          </div>
-
-          <GameRelatedData game={gameQuery.data} />
-        </div>
-
-        <div className="space-y-6">
-          <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
-            <h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
-              Quick Actions
-            </h3>
-            <div className="space-y-3">
-              <Button asChild variant="outline" className="w-full justify-start">
-                <Link href={`/games/${gameQuery.data.id}`} target="_blank">
-                  View Public Page
-                </Link>
-              </Button>
-              <Button asChild variant="outline" className="w-full justify-start">
-                <Link href={`/listings/new?gameId=${gameQuery.data.id}`}>Add New Listing</Link>
-              </Button>
-            </div>
-          </div>
-
-          <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
-            <h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">
-              Compatible Emulators
-            </h3>
-            {gameQuery.data.system.emulators.length > 0 ? (
-              <ul className="space-y-2">
-                {gameQuery.data.system.emulators.map((emulator) => (
-                  <li key={emulator.id}>
-                    <Link
-                      href={`/admin/emulators/${emulator.id}`}
-                      className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
-                    >
-                      {emulator.name}
-                    </Link>
-                  </li>
-                ))}
-              </ul>
-            ) : (
-              <p className="text-gray-500 dark:text-gray-400 text-sm">
-                No emulators support this system yet.
-              </p>
-            )}
-          </div>
-        </div>
-      </div>
-    </div>
+    <Suspense fallback={<div className="space-y-6">Loading game...</div>}>
+      <AdminGameEditPageContent params={props.params} />
+    </Suspense>
   )
 }
 
-export default AdminGameEditPage
+async function AdminGameEditPageContent(props: Props) {
+  const { id } = await props.params
+
+  return <AdminGameEditClientPage gameId={id} />
+}
diff --git a/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
new file mode 100644
index 000000000..a6d1f907e
--- /dev/null
+++ b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
@@ -0,0 +1,59 @@
+'use client'
+
+import { LoadingSpinner } from '@/components/ui'
+import { api } from '@/lib/api'
+import ListingEditForm from './components/ListingEditForm'
+
+interface Props {
+  listingId: string
+}
+
+export default function EditListingClientPage(props: Props) {
+  const listingQuery = api.listings.getForEdit.useQuery(
+    { id: props.listingId },
+    { enabled: !!props.listingId },
+  )
+
+  if (listingQuery.isPending) {
+    return (
+      <div className="container mx-auto px-4 py-8">
+        <LoadingSpinner text="Loading listing..." />
+      </div>
+    )
+  }
+
+  if (listingQuery.error) {
+    return (
+      <div className="container mx-auto px-4 py-8">
+        <div className="text-center py-12">
+          <p className="text-red-600 dark:text-red-400 text-lg">
+            Error loading listing: {listingQuery.error.message}
+          </p>
+        </div>
+      </div>
+    )
+  }
+
+  if (!listingQuery.data) {
+    return (
+      <div className="container mx-auto px-4 py-8">
+        <div className="text-center py-12">
+          <p className="text-gray-600 dark:text-gray-400 text-lg">Listing not found</p>
+        </div>
+      </div>
+    )
+  }
+
+  return (
+    <div className="container mx-auto px-4 py-8">
+      <div className="mb-6">
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">Edit Listing</h1>
+        <p className="text-gray-600 dark:text-gray-400 mt-1">
+          Modify the performance listing for {listingQuery.data.game.title}
+        </p>
+      </div>
+
+      <ListingEditForm listing={listingQuery.data} />
+    </div>
+  )
+}
diff --git a/src/app/admin/listings/[id]/edit/page.tsx b/src/app/admin/listings/[id]/edit/page.tsx
index a32941f8c..7f1f238e1 100644
--- a/src/app/admin/listings/[id]/edit/page.tsx
+++ b/src/app/admin/listings/[id]/edit/page.tsx
@@ -1,58 +1,20 @@
-'use client'
+import { Suspense } from 'react'
+import EditListingClientPage from './EditListingClientPage'
 
-import { useParams } from 'next/navigation'
-import { LoadingSpinner } from '@/components/ui'
-import { api } from '@/lib/api'
-import ListingEditForm from './components/ListingEditForm'
-
-function EditListingPage() {
-  const params = useParams()
-  const listingId = params.id as string
-
-  const listingQuery = api.listings.getForEdit.useQuery({ id: listingId }, { enabled: !!listingId })
-
-  if (listingQuery.isPending) {
-    return (
-      <div className="container mx-auto px-4 py-8">
-        <LoadingSpinner text="Loading listing..." />
-      </div>
-    )
-  }
-
-  if (listingQuery.error) {
-    return (
-      <div className="container mx-auto px-4 py-8">
-        <div className="text-center py-12">
-          <p className="text-red-600 dark:text-red-400 text-lg">
-            Error loading listing: {listingQuery.error.message}
-          </p>
-        </div>
-      </div>
-    )
-  }
-
-  if (!listingQuery.data) {
-    return (
-      <div className="container mx-auto px-4 py-8">
-        <div className="text-center py-12">
-          <p className="text-gray-600 dark:text-gray-400 text-lg">Listing not found</p>
-        </div>
-      </div>
-    )
-  }
+interface Props {
+  params: Promise<{ id: string }>
+}
 
+export default function EditListingPage(props: Props) {
   return (
-    <div className="container mx-auto px-4 py-8">
-      <div className="mb-6">
-        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">Edit Listing</h1>
-        <p className="text-gray-600 dark:text-gray-400 mt-1">
-          Modify the performance listing for {listingQuery.data.game.title}
-        </p>
-      </div>
-
-      <ListingEditForm listing={listingQuery.data} />
-    </div>
+    <Suspense fallback={<div className="container mx-auto px-4 py-8">Loading listing...</div>}>
+      <EditListingPageContent params={props.params} />
+    </Suspense>
   )
 }
 
-export default EditListingPage
+async function EditListingPageContent(props: Props) {
+  const { id } = await props.params
+
+  return <EditListingClientPage listingId={id} />
+}
diff --git a/src/app/games/[id]/page.tsx b/src/app/games/[id]/page.tsx
index 97bdfdd5f..30a7ec1e5 100644
--- a/src/app/games/[id]/page.tsx
+++ b/src/app/games/[id]/page.tsx
@@ -1,5 +1,6 @@
 import { type Metadata } from 'next'
 import { notFound } from 'next/navigation'
+import { Suspense } from 'react'
 import { PageWithMetadata } from '@/components/seo/PageWithMetadata'
 import {
   generatePageMetadata,
@@ -7,6 +8,7 @@ import {
   generateBreadcrumbStructuredData,
 } from '@/lib/seo/metadata'
 import { getGameForSEO } from '@/server/db/seo-queries'
+import { GameDetailsPageSkeleton } from './components/GameDetailsPageSkeleton'
 import GameDetailsPage from './GameDetailsPage'
 
 interface Props {
@@ -29,7 +31,15 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
   )
 }
 
-export default async function Page(props: Props) {
+export default function Page(props: Props) {
+  return (
+    <Suspense fallback={<GameDetailsPageSkeleton />}>
+      <GamePageContent params={props.params} />
+    </Suspense>
+  )
+}
+
+async function GamePageContent(props: Props) {
   const params = await props.params
   const game = await getGameForSEO(params.id)
 
@@ -49,7 +59,9 @@ export default async function Page(props: Props) {
 
   return (
     <PageWithMetadata structuredData={[pageStructuredData, breadcrumbData]}>
-      <GameDetailsPage />
+      <Suspense fallback={<GameDetailsPageSkeleton />}>
+        <GameDetailsPage />
+      </Suspense>
     </PageWithMetadata>
   )
 }
diff --git a/src/app/listings/[id]/page.tsx b/src/app/listings/[id]/page.tsx
index 052f935a1..6cd974828 100644
--- a/src/app/listings/[id]/page.tsx
+++ b/src/app/listings/[id]/page.tsx
@@ -1,5 +1,7 @@
 import { type Metadata } from 'next'
 import { notFound } from 'next/navigation'
+import { Suspense } from 'react'
+import { ListingDetailSkeleton } from '@/app/listings/shared/components'
 import { PageWithMetadata } from '@/components/seo/PageWithMetadata'
 import { generatePageMetadata, generateStructuredData } from '@/lib/seo/metadata'
 import { getListingForSEO } from '@/server/db/seo-queries'
@@ -25,7 +27,15 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
   )
 }
 
-export default async function Page(props: Props) {
+export default function Page(props: Props) {
+  return (
+    <Suspense fallback={<ListingDetailSkeleton variant="handheld" />}>
+      <ListingPageContent params={props.params} />
+    </Suspense>
+  )
+}
+
+async function ListingPageContent(props: Props) {
   const params = await props.params
   const listing = await getListingForSEO(params.id)
 
@@ -43,7 +53,9 @@ export default async function Page(props: Props) {
 
   return (
     <PageWithMetadata structuredData={structuredData}>
-      <ListingDetailsPage />
+      <Suspense fallback={<ListingDetailSkeleton variant="handheld" />}>
+        <ListingDetailsPage />
+      </Suspense>
     </PageWithMetadata>
   )
 }
diff --git a/src/app/pc-listings/[id]/page.tsx b/src/app/pc-listings/[id]/page.tsx
index e4d18bdde..521dbd7a6 100644
--- a/src/app/pc-listings/[id]/page.tsx
+++ b/src/app/pc-listings/[id]/page.tsx
@@ -1,5 +1,7 @@
 import { type Metadata } from 'next'
 import { notFound } from 'next/navigation'
+import { Suspense } from 'react'
+import { ListingDetailSkeleton } from '@/app/listings/shared/components'
 import { PageWithMetadata } from '@/components/seo/PageWithMetadata'
 import { generatePageMetadata, generateStructuredData } from '@/lib/seo/metadata'
 import { getPcListingForSEO } from '@/server/db/seo-queries'
@@ -27,7 +29,15 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
   )
 }
 
-export default async function Page(props: Props) {
+export default function Page(props: Props) {
+  return (
+    <Suspense fallback={<ListingDetailSkeleton variant="pc" />}>
+      <PcListingPageContent params={props.params} />
+    </Suspense>
+  )
+}
+
+async function PcListingPageContent(props: Props) {
   const params = await props.params
   const listing = await getPcListingForSEO(params.id)
 
@@ -47,7 +57,9 @@ export default async function Page(props: Props) {
 
   return (
     <PageWithMetadata structuredData={structuredData}>
-      <PcListingDetailsPage />
+      <Suspense fallback={<ListingDetailSkeleton variant="pc" />}>
+        <PcListingDetailsPage />
+      </Suspense>
     </PageWithMetadata>
   )
 }
diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx
index 686d1e35c..4f5381d72 100644
--- a/src/app/terms/page.tsx
+++ b/src/app/terms/page.tsx
@@ -1,4 +1,3 @@
-import { LocalizedDate } from '@/components/ui/LocalizedDate'
 import type { Metadata } from 'next'
 
 export const metadata: Metadata = {
@@ -17,7 +16,7 @@ function TermsOfServicePage() {
         <h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-8">Terms of Service</h1>
 
         <p className="text-gray-600 dark:text-gray-400 mb-8">
-          <strong>Last updated:</strong> <LocalizedDate date={new Date()} format="date" />
+          <strong>Last updated:</strong> April 1, 2026
         </p>
 
         <div className="space-y-8">
diff --git a/src/app/users/[id]/page.tsx b/src/app/users/[id]/page.tsx
index 36192a3c0..d09a08749 100644
--- a/src/app/users/[id]/page.tsx
+++ b/src/app/users/[id]/page.tsx
@@ -1,7 +1,9 @@
 import { type Metadata } from 'next'
 import { notFound } from 'next/navigation'
+import { Suspense } from 'react'
 import { generatePageMetadata } from '@/lib/seo/metadata'
 import { getUserForSEO } from '@/server/db/seo-queries'
+import UserProfilePageSkeleton from './components/UserProfilePageSkeleton'
 import UserProfilePage from './UserProfilePage'
 
 interface Props {
@@ -25,11 +27,23 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
   )
 }
 
-export default async function Page(props: Props) {
+export default function Page(props: Props) {
+  return (
+    <Suspense fallback={<UserProfilePageSkeleton />}>
+      <UserPageContent params={props.params} />
+    </Suspense>
+  )
+}
+
+async function UserPageContent(props: Props) {
   const params = await props.params
   const user = await getUserForSEO(params.id)
 
   if (!user) notFound()
 
-  return <UserProfilePage />
+  return (
+    <Suspense fallback={<UserProfilePageSkeleton />}>
+      <UserProfilePage />
+    </Suspense>
+  )
 }

From 688127abb8d4d1d0762e85b2d0c8754f9fc2408b Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 15:03:20 +0200
Subject: [PATCH 15/22] feat: service worker registration and caching logic

---
 public/sw-register.js | 13 +++----------
 src/app/layout.tsx    | 11 ++++-------
 src/proxy.ts          |  1 +
 3 files changed, 8 insertions(+), 17 deletions(-)

diff --git a/public/sw-register.js b/public/sw-register.js
index 0b73bc814..4b91c508c 100644
--- a/public/sw-register.js
+++ b/public/sw-register.js
@@ -1,13 +1,10 @@
 const CACHE_NAME = 'emuready_v0.13.1'
 
-// Service worker registration script
 if ('serviceWorker' in navigator) {
-  // Clean up outdated cache versions on load
   window.addEventListener('load', function () {
     if ('caches' in window) {
       caches.keys().then(function (names) {
         for (let name of names) {
-          // Remove cache versions prior
           if (name.startsWith('emuready') && name !== CACHE_NAME) {
             caches
               .delete(name)
@@ -30,12 +27,10 @@ if ('serviceWorker' in navigator) {
     window.location.hostname === '127.0.0.1' ||
     window.location.hostname.includes('.local') ||
     (window.location.protocol === 'http:' && !window.location.hostname.includes('emuready'))
+  const isServiceWorkerEnabled = window.__EMUREADY_SW_ENABLED__ === true
 
-  // Disable SW in development-like environments (including dev.emuready.com behind tunnels)
-  if (isDevelopment) {
-    console.log(
-      'Service Worker disabled in local development mode; unregistering any existing SW and clearing caches',
-    )
+  if (isDevelopment || !isServiceWorkerEnabled) {
+    console.log('Service Worker disabled; unregistering any existing SW and clearing caches')
     // Proactively unregister any active service workers for this origin
     if (navigator.serviceWorker.getRegistrations) {
       navigator.serviceWorker
@@ -43,7 +38,6 @@ if ('serviceWorker' in navigator) {
         .then((regs) => Promise.all(regs.map((r) => r.unregister())))
         .catch((err) => console.warn('SW unregister failed', err))
     }
-    // Clear all caches to avoid stale assets/pages in dev
     if ('caches' in window) {
       caches
         .keys()
@@ -54,7 +48,6 @@ if ('serviceWorker' in navigator) {
     window.addEventListener('load', async function () {
       const swUrl = '/service-worker.js'
 
-      // Unregister any previous service workers that might be controlling the page
       if (navigator.serviceWorker.getRegistrations) {
         const registrations = await navigator.serviceWorker.getRegistrations()
         const currentSwUrl = new URL(swUrl, window.location.href).href
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 5d2a1c30d..b8ad31c2a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -36,13 +36,10 @@ export default function RootLayout(props: PropsWithChildren) {
   return (
     <html lang="en" suppressHydrationWarning>
       <head>
-        {/* Service Worker Registration / Unregister in dev
-         * - In dev (not production), always load to proactively unregister any SW and clear caches
-         * - In prod, only load when explicitly enabled via NEXT_PUBLIC_ENABLE_SW
-         */}
-        {(env.ENABLE_SW || !env.IS_PROD) && (
-          <Script src="/sw-register.js" strategy="afterInteractive" />
-        )}
+        <Script id="service-worker-config" strategy="beforeInteractive">
+          {`window.__EMUREADY_SW_ENABLED__ = ${env.ENABLE_SW ? 'true' : 'false'};`}
+        </Script>
+        <Script src="/sw-register.js" strategy="afterInteractive" />
 
         {env.IS_PROD && env.GA_ID && (
           <Script id="google-analytics-dataLayer" strategy="beforeInteractive">
diff --git a/src/proxy.ts b/src/proxy.ts
index abe405e8e..5200f4975 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -5,6 +5,7 @@ import { ms } from '@/utils/time'
 import type { NextRequest, NextFetchEvent } from 'next/server'
 
 // Process-local rate limiting only; counts are not shared across server instances.
+// TODO: For production abuse control, prefer provider/WAF rate limits before traffic reaches Next.
 const rateLimitMap = new Map<string, { count: number; resetTime: number }>()
 
 const RATE_LIMIT_REQUESTS = process.env.NODE_ENV === 'test' ? 10000 : 100

From 5efb9e6a17cc5cbba5051c11d691ccf1058c14a9 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 15:03:47 +0200
Subject: [PATCH 16/22] feat: update eslint config to clarify TODO for React
 Compiler rule violations

---
 eslint.config.mjs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/eslint.config.mjs b/eslint.config.mjs
index 6470b0b6e..235e19c2f 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -51,7 +51,7 @@ const eslintConfig = [
       'no-case-declarations': 'off',
       'no-prototype-builtins': 'off',
       'no-redeclare': 'off',
-      // TODO: set to error in followup PR
+      // TODO: set to error after fixing existing React Compiler rule violations.
       'react-hooks/purity': 'warn',
       'react-hooks/static-components': 'warn',
       'react-hooks/immutability': 'warn',

From 31e9f62e068192cabe207567d5bc1c1e690dcc9c Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 15:04:03 +0200
Subject: [PATCH 17/22] feat: error handling for game and listing data loading

---
 .../EmulatorCustomFieldsClientPage.tsx        | 25 ++++++++++-
 .../games/[id]/AdminGameEditClientPage.tsx    | 16 ++++++-
 .../[id]/edit/EditListingClientPage.tsx       | 17 ++++---
 src/lib/api.tsx                               |  4 +-
 src/lib/trpc-client-errors.test.ts            | 45 +++++++++++++++++++
 src/lib/trpc-client-errors.ts                 | 13 ++++++
 6 files changed, 108 insertions(+), 12 deletions(-)
 create mode 100644 src/lib/trpc-client-errors.test.ts
 create mode 100644 src/lib/trpc-client-errors.ts

diff --git a/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
index 97f713b3d..b8ffdd1e8 100644
--- a/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
+++ b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
@@ -6,6 +6,7 @@ import { useState } from 'react'
 import UnauthenticatedPage from '@/components/auth/UnauthenticatedPage'
 import { Button, LoadingSpinner } from '@/components/ui'
 import { api } from '@/lib/api'
+import { isTRPCNotFoundError } from '@/lib/trpc-client-errors'
 import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
 import { hasRolePermission } from '@/utils/permissions'
 import { Role } from '@orm'
@@ -123,10 +124,20 @@ export default function EmulatorCustomFieldsClientPage(props: Props) {
   }
 
   if (emulatorQuery.error) {
+    if (isTRPCNotFoundError(emulatorQuery.error)) {
+      return (
+        <FeedbackCard
+          title="Emulator not found"
+          description="The emulator you are trying to manage was not found or you no longer have access to it."
+          actions={backToEmulatorsAction}
+        />
+      )
+    }
+
     return (
       <FeedbackCard
         title="Failed to load emulator"
-        description={emulatorQuery.error.message ?? 'Please try again later.'}
+        description="The emulator data could not be loaded. Please try again later."
         actions={backToEmulatorsAction}
       />
     )
@@ -136,7 +147,17 @@ export default function EmulatorCustomFieldsClientPage(props: Props) {
     return (
       <FeedbackCard
         title="Failed to load custom fields"
-        description={customFieldDefinitionsQuery.error.message ?? 'Please try again later.'}
+        description="The custom fields could not be loaded. Please try again later."
+        actions={backToEmulatorsAction}
+      />
+    )
+  }
+
+  if (categoriesQuery.error) {
+    return (
+      <FeedbackCard
+        title="Failed to load custom field categories"
+        description="The custom field categories could not be loaded. Please try again later."
         actions={backToEmulatorsAction}
       />
     )
diff --git a/src/app/admin/games/[id]/AdminGameEditClientPage.tsx b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
index f50e0b3da..9286b6797 100644
--- a/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
+++ b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
@@ -3,8 +3,10 @@
 import { ArrowLeft } from 'lucide-react'
 import Link from 'next/link'
 import { notFound } from 'next/navigation'
+import { AdminErrorState } from '@/components/admin/AdminErrorState'
 import { Button, PageSkeletonLoading } from '@/components/ui'
 import { api } from '@/lib/api'
+import { isTRPCNotFoundError } from '@/lib/trpc-client-errors'
 import { GameEditForm } from './components/GameEditForm'
 import { GameRelatedData } from './components/GameRelatedData'
 
@@ -17,7 +19,19 @@ export default function AdminGameEditClientPage(props: Props) {
 
   if (gameQuery.isPending) return <PageSkeletonLoading />
 
-  if (gameQuery.error || !gameQuery.data) return notFound()
+  if (gameQuery.error) {
+    if (isTRPCNotFoundError(gameQuery.error)) return notFound()
+
+    return (
+      <AdminErrorState
+        title="Unable to load game"
+        message="The game data could not be loaded. Please try again."
+        onRetry={() => void gameQuery.refetch()}
+      />
+    )
+  }
+
+  if (!gameQuery.data) return notFound()
 
   return (
     <div className="space-y-6">
diff --git a/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
index a6d1f907e..99509f20b 100644
--- a/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
+++ b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
@@ -1,7 +1,10 @@
 'use client'
 
+import { notFound } from 'next/navigation'
+import { AdminErrorState } from '@/components/admin/AdminErrorState'
 import { LoadingSpinner } from '@/components/ui'
 import { api } from '@/lib/api'
+import { isTRPCNotFoundError } from '@/lib/trpc-client-errors'
 import ListingEditForm from './components/ListingEditForm'
 
 interface Props {
@@ -23,14 +26,14 @@ export default function EditListingClientPage(props: Props) {
   }
 
   if (listingQuery.error) {
+    if (isTRPCNotFoundError(listingQuery.error)) return notFound()
+
     return (
-      <div className="container mx-auto px-4 py-8">
-        <div className="text-center py-12">
-          <p className="text-red-600 dark:text-red-400 text-lg">
-            Error loading listing: {listingQuery.error.message}
-          </p>
-        </div>
-      </div>
+      <AdminErrorState
+        title="Unable to load listing"
+        message="The listing data could not be loaded. Please try again."
+        onRetry={() => void listingQuery.refetch()}
+      />
     )
   }
 
diff --git a/src/lib/api.tsx b/src/lib/api.tsx
index b754ad23c..1ec06dded 100644
--- a/src/lib/api.tsx
+++ b/src/lib/api.tsx
@@ -5,6 +5,7 @@ import { httpBatchLink } from '@trpc/client'
 import { createTRPCReact } from '@trpc/react-query'
 import { useState, type PropsWithChildren } from 'react'
 import superjson from 'superjson'
+import { shouldRetryTRPCQuery } from '@/lib/trpc-client-errors'
 import { ms } from '@/utils/time'
 import type { AppRouter } from '@/types/trpc'
 
@@ -16,12 +17,11 @@ export function TRPCProvider(props: PropsWithChildren) {
       new QueryClient({
         defaultOptions: {
           queries: {
-            // Default settings for all queries
             staleTime: ms.seconds(30),
             gcTime: ms.minutes(5),
             refetchOnWindowFocus: true,
             refetchOnReconnect: true,
-            retry: 3,
+            retry: shouldRetryTRPCQuery,
             retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
           },
           mutations: { retry: 1 },
diff --git a/src/lib/trpc-client-errors.test.ts b/src/lib/trpc-client-errors.test.ts
new file mode 100644
index 000000000..59eac143f
--- /dev/null
+++ b/src/lib/trpc-client-errors.test.ts
@@ -0,0 +1,45 @@
+import { TRPCClientError } from '@trpc/client'
+import { describe, expect, it } from 'vitest'
+import { isTRPCNotFoundError, shouldRetryTRPCQuery } from './trpc-client-errors'
+import type { AppRouter } from '@/types/trpc'
+
+type TestTRPCErrorCode = 'NOT_FOUND' | 'INTERNAL_SERVER_ERROR'
+
+function createTRPCError(code: TestTRPCErrorCode): TRPCClientError<AppRouter> {
+  const httpStatus = code === 'NOT_FOUND' ? 404 : 500
+
+  return new TRPCClientError<AppRouter>('Request failed', {
+    result: {
+      error: {
+        message: 'Request failed',
+        code: code === 'NOT_FOUND' ? -32004 : -32603,
+        data: {
+          code,
+          httpStatus,
+          path: 'test.procedure',
+          zodError: null,
+        },
+      },
+    },
+  })
+}
+
+describe('tRPC client error helpers', () => {
+  it('detects tRPC NOT_FOUND errors', () => {
+    expect(isTRPCNotFoundError(createTRPCError('NOT_FOUND'))).toBe(true)
+    expect(isTRPCNotFoundError(createTRPCError('INTERNAL_SERVER_ERROR'))).toBe(false)
+    expect(isTRPCNotFoundError(new Error('plain error'))).toBe(false)
+  })
+
+  it('does not retry NOT_FOUND errors', () => {
+    expect(shouldRetryTRPCQuery(0, createTRPCError('NOT_FOUND'))).toBe(false)
+  })
+
+  it('keeps the default three-attempt retry policy for retryable errors', () => {
+    const error = createTRPCError('INTERNAL_SERVER_ERROR')
+
+    expect(shouldRetryTRPCQuery(0, error)).toBe(true)
+    expect(shouldRetryTRPCQuery(2, error)).toBe(true)
+    expect(shouldRetryTRPCQuery(3, error)).toBe(false)
+  })
+})
diff --git a/src/lib/trpc-client-errors.ts b/src/lib/trpc-client-errors.ts
new file mode 100644
index 000000000..95ee52db3
--- /dev/null
+++ b/src/lib/trpc-client-errors.ts
@@ -0,0 +1,13 @@
+import { isTRPCClientError } from '@trpc/client'
+import type { AppRouter } from '@/types/trpc'
+
+const MAX_QUERY_RETRIES = 3
+const NOT_FOUND_ERROR_CODE = 'NOT_FOUND'
+
+export function isTRPCNotFoundError(error: unknown): boolean {
+  return isTRPCClientError<AppRouter>(error) && error.data?.code === NOT_FOUND_ERROR_CODE
+}
+
+export function shouldRetryTRPCQuery(failureCount: number, error: Error): boolean {
+  return !isTRPCNotFoundError(error) && failureCount < MAX_QUERY_RETRIES
+}

From db136628977adacc87b079dcebd679bfba0046dd Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 16:08:40 +0200
Subject: [PATCH 18/22] feat: simplify import statement for role permission
 utility

---
 src/app/admin/AdminLayoutClient.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/app/admin/AdminLayoutClient.tsx b/src/app/admin/AdminLayoutClient.tsx
index e3ea4effb..72b044b2e 100644
--- a/src/app/admin/AdminLayoutClient.tsx
+++ b/src/app/admin/AdminLayoutClient.tsx
@@ -11,7 +11,7 @@ import { LoadingSpinner } from '@/components/ui/LoadingSpinner'
 import { api } from '@/lib/api'
 import { cn } from '@/lib/utils'
 import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
-import { hasRolePermission as hasRolePermission } from '@/utils/permissions'
+import { hasRolePermission } from '@/utils/permissions'
 import { Role } from '@orm'
 import AdminNavbar from './components/AdminNavbar'
 import {

From 98a76d4dac1f288308edf8b62c9feb1e5446c592 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 16:21:20 +0200
Subject: [PATCH 19/22] feat: error handling and messaging for game, emulator,
 and listing pages

---
 .../[emulatorId]/EditEmulatorClientPage.tsx   | 54 +++++++++++++++----
 .../EmulatorCustomFieldsClientPage.tsx        | 20 +++----
 .../games/[id]/AdminGameEditClientPage.tsx    | 26 ++++++---
 .../[id]/edit/EditListingClientPage.tsx       | 20 +++----
 4 files changed, 83 insertions(+), 37 deletions(-)

diff --git a/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx b/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx
index fdbac088b..a131cd45a 100644
--- a/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx
+++ b/src/app/admin/emulators/[emulatorId]/EditEmulatorClientPage.tsx
@@ -1,9 +1,11 @@
 'use client'
 
 import { ArrowLeft } from 'lucide-react'
-import { notFound, useRouter } from 'next/navigation'
+import { useRouter } from 'next/navigation'
+import { AdminErrorState } from '@/components/admin/AdminErrorState'
 import { Button, PageSkeletonLoading } from '@/components/ui'
 import { api } from '@/lib/api'
+import { isTRPCNotFoundError } from '@/lib/trpc-client-errors'
 import { hasPermission, PERMISSIONS } from '@/utils/permission-system'
 import { Role } from '@orm'
 import EmulatorEditForm from './components/EmulatorEditForm'
@@ -62,20 +64,50 @@ export default function EditEmulatorClientPage(props: Props) {
 
   if (systemsQuery.isPending) return <PageSkeletonLoading />
 
-  if (emulatorsQuery.error || systemsQuery.error) {
+  if (emulatorsQuery.error) {
+    const isNotFound = isTRPCNotFoundError(emulatorsQuery.error)
     return (
-      <div className="container mx-auto p-4">
-        <p className="text-red-500">
-          Error loading data:{' '}
-          {emulatorsQuery.error?.message ??
-            systemsQuery.error?.message ??
-            'An unknown error occurred'}
-        </p>
-      </div>
+      <AdminErrorState
+        title={isNotFound ? 'Emulator not found' : 'Unable to load emulator'}
+        message={
+          isNotFound
+            ? 'The emulator you are trying to edit was not found or is no longer available.'
+            : 'The emulator data could not be loaded. Please try again.'
+        }
+        onRetry={() => void emulatorsQuery.refetch()}
+      />
     )
   }
 
-  if (!emulatorsQuery.data || !systemsQuery.data) return notFound()
+  if (systemsQuery.error) {
+    return (
+      <AdminErrorState
+        title="Unable to load systems"
+        message="The system list could not be loaded. Please try again."
+        onRetry={() => void systemsQuery.refetch()}
+      />
+    )
+  }
+
+  if (!emulatorsQuery.data) {
+    return (
+      <AdminErrorState
+        title="Emulator not found"
+        message="The emulator you are trying to edit was not found or is no longer available."
+        onRetry={() => void emulatorsQuery.refetch()}
+      />
+    )
+  }
+
+  if (!systemsQuery.data) {
+    return (
+      <AdminErrorState
+        title="Unable to load systems"
+        message="The system list could not be loaded. Please try again."
+        onRetry={() => window.location.reload()}
+      />
+    )
+  }
 
   return (
     <div className="container mx-auto p-4 md:p-8">
diff --git a/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
index b8ffdd1e8..a77c87dc5 100644
--- a/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
+++ b/src/app/admin/emulators/[emulatorId]/custom-fields/EmulatorCustomFieldsClientPage.tsx
@@ -78,16 +78,6 @@ export default function EmulatorCustomFieldsClientPage(props: Props) {
     )
   }
 
-  if (!canAccess) {
-    return (
-      <FeedbackCard
-        title="Access Denied"
-        description="You do not have permission to manage custom fields for this emulator."
-        actions={backToEmulatorsAction}
-      />
-    )
-  }
-
   const canApplyTemplates = hasRolePermission(user.role, Role.SUPER_ADMIN)
 
   function handleOpenCreateModal() {
@@ -173,6 +163,16 @@ export default function EmulatorCustomFieldsClientPage(props: Props) {
     )
   }
 
+  if (!canAccess) {
+    return (
+      <FeedbackCard
+        title="Access Denied"
+        description="You do not have permission to manage custom fields for this emulator."
+        actions={backToEmulatorsAction}
+      />
+    )
+  }
+
   return (
     <div className="container mx-auto py-8">
       <div className="flex justify-between items-center mb-6">
diff --git a/src/app/admin/games/[id]/AdminGameEditClientPage.tsx b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
index 9286b6797..23cb620b9 100644
--- a/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
+++ b/src/app/admin/games/[id]/AdminGameEditClientPage.tsx
@@ -2,7 +2,6 @@
 
 import { ArrowLeft } from 'lucide-react'
 import Link from 'next/link'
-import { notFound } from 'next/navigation'
 import { AdminErrorState } from '@/components/admin/AdminErrorState'
 import { Button, PageSkeletonLoading } from '@/components/ui'
 import { api } from '@/lib/api'
@@ -20,18 +19,29 @@ export default function AdminGameEditClientPage(props: Props) {
   if (gameQuery.isPending) return <PageSkeletonLoading />
 
   if (gameQuery.error) {
-    if (isTRPCNotFoundError(gameQuery.error)) return notFound()
-
+    const isNotFound = isTRPCNotFoundError(gameQuery.error)
     return (
       <AdminErrorState
-        title="Unable to load game"
-        message="The game data could not be loaded. Please try again."
+        title={isNotFound ? 'Game not found' : 'Unable to load game'}
+        message={
+          isNotFound
+            ? 'The game you are trying to edit was not found or is no longer available.'
+            : 'The game data could not be loaded. Please try again.'
+        }
         onRetry={() => void gameQuery.refetch()}
       />
     )
   }
 
-  if (!gameQuery.data) return notFound()
+  if (!gameQuery.data) {
+    return (
+      <AdminErrorState
+        title="Game not found"
+        message="The game you are trying to edit was not found or is no longer available."
+        onRetry={() => window.location.reload()}
+      />
+    )
+  }
 
   return (
     <div className="space-y-6">
@@ -71,7 +81,9 @@ export default function AdminGameEditClientPage(props: Props) {
                 </Link>
               </Button>
               <Button asChild variant="outline" className="w-full justify-start">
-                <Link href={`/listings/new?gameId=${gameQuery.data.id}`}>Add New Listing</Link>
+                <Link href={`/listings/new?gameId=${gameQuery.data.id}`}>
+                  Add New Handheld Report
+                </Link>
               </Button>
             </div>
           </div>
diff --git a/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
index 99509f20b..263054801 100644
--- a/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
+++ b/src/app/admin/listings/[id]/edit/EditListingClientPage.tsx
@@ -1,6 +1,5 @@
 'use client'
 
-import { notFound } from 'next/navigation'
 import { AdminErrorState } from '@/components/admin/AdminErrorState'
 import { LoadingSpinner } from '@/components/ui'
 import { api } from '@/lib/api'
@@ -20,18 +19,21 @@ export default function EditListingClientPage(props: Props) {
   if (listingQuery.isPending) {
     return (
       <div className="container mx-auto px-4 py-8">
-        <LoadingSpinner text="Loading listing..." />
+        <LoadingSpinner text="Loading handheld report..." />
       </div>
     )
   }
 
   if (listingQuery.error) {
-    if (isTRPCNotFoundError(listingQuery.error)) return notFound()
-
+    const isNotFound = isTRPCNotFoundError(listingQuery.error)
     return (
       <AdminErrorState
-        title="Unable to load listing"
-        message="The listing data could not be loaded. Please try again."
+        title={isNotFound ? 'Handheld report not found' : 'Unable to load handheld report'}
+        message={
+          isNotFound
+            ? 'The handheld report you are trying to edit was not found or is no longer available.'
+            : 'The handheld report data could not be loaded. Please try again.'
+        }
         onRetry={() => void listingQuery.refetch()}
       />
     )
@@ -41,7 +43,7 @@ export default function EditListingClientPage(props: Props) {
     return (
       <div className="container mx-auto px-4 py-8">
         <div className="text-center py-12">
-          <p className="text-gray-600 dark:text-gray-400 text-lg">Listing not found</p>
+          <p className="text-gray-600 dark:text-gray-400 text-lg">Handheld report not found</p>
         </div>
       </div>
     )
@@ -50,9 +52,9 @@ export default function EditListingClientPage(props: Props) {
   return (
     <div className="container mx-auto px-4 py-8">
       <div className="mb-6">
-        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">Edit Listing</h1>
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">Edit Handheld Report</h1>
         <p className="text-gray-600 dark:text-gray-400 mt-1">
-          Modify the performance listing for {listingQuery.data.game.title}
+          Modify the handheld compatibility report for {listingQuery.data.game.title}
         </p>
       </div>
 

From b4bcdfa05a214d04a927941f0260458824d1d715 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 16:29:25 +0200
Subject: [PATCH 20/22] feat: refactor role import and usage in page component

---
 src/app/admin/users/page.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx
index a3fea999b..896f99c74 100644
--- a/src/app/admin/users/page.tsx
+++ b/src/app/admin/users/page.tsx
@@ -33,7 +33,7 @@ import { getRoleVariant } from '@/utils/badge-colors'
 import getErrorMessage from '@/utils/getErrorMessage'
 import { PERMISSIONS } from '@/utils/permission-system'
 import { hasRolePermission } from '@/utils/permissions'
-import { type Role, Role as RoleEnum } from '@orm'
+import { Role } from '@orm'
 import UserBadgeModal from './components/UserBadgeModal'
 import UserDetailsModal, { type ActivityTab } from './components/UserDetailsModal'
 import UserRoleModal from './components/UserRoleModal'
@@ -95,10 +95,10 @@ function AdminUsersPage() {
   const canManageBadges = currentUserQuery.data?.permissions?.includes(PERMISSIONS.MANAGE_BADGES)
   const canChangeRoles = currentUserQuery.data?.permissions?.includes(PERMISSIONS.CHANGE_USER_ROLES)
   const isModerator =
-    hasRolePermission(currentUserQuery.data?.role, RoleEnum.MODERATOR) &&
-    !hasRolePermission(currentUserQuery.data?.role, RoleEnum.ADMIN)
+    hasRolePermission(currentUserQuery.data?.role, Role.MODERATOR) &&
+    !hasRolePermission(currentUserQuery.data?.role, Role.ADMIN)
   const canBanUsers =
-    hasRolePermission(currentUserQuery.data?.role, RoleEnum.MODERATOR) &&
+    hasRolePermission(currentUserQuery.data?.role, Role.MODERATOR) &&
     currentUserQuery.data?.permissions?.includes(PERMISSIONS.MANAGE_USER_BANS)
 
   const usersQuery = api.users.get.useQuery({

From 5ea131aeb8aff4368d2bd537ec1c47c52f5d0de8 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 16:54:12 +0200
Subject: [PATCH 21/22] feat: add logging for HTTP errors in response
 interceptor

---
 src/rest/http.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/rest/http.ts b/src/rest/http.ts
index a985c2e2e..dfa50ba50 100644
--- a/src/rest/http.ts
+++ b/src/rest/http.ts
@@ -1,5 +1,6 @@
 import axios, { type AxiosError } from 'axios'
 import analytics from '@/lib/analytics'
+import { logger } from '@/lib/logger'
 
 const http = axios.create({
   timeout: 20000,
@@ -11,7 +12,7 @@ const http = axios.create({
 http.interceptors.response.use(
   (response) => response,
   (error: AxiosError) => {
-    // Track errors to analytics
+    logger.error('HTTP Error:', error)
     analytics.performance.errorOccurred({
       errorType: 'api_request_error',
       errorMessage: error.message,

From 182b5be186d9568e3755f3b8569354218d9194d8 Mon Sep 17 00:00:00 2001
From: Producdevity <y.gherbi.dev@gmail.com>
Date: Sat, 23 May 2026 17:04:26 +0200
Subject: [PATCH 22/22] feat: error handling and logging in API routes

---
 src/app/api/catalog/compatibility/route.ts | 22 ++++++++--------------
 src/app/api/mobile/auth/route.ts           |  1 -
 src/app/api/notifications/stream/route.ts  |  7 ++-----
 src/app/api/proxy-image/route.ts           | 13 ++++++-------
 src/lib/errors.ts                          |  4 ----
 5 files changed, 16 insertions(+), 31 deletions(-)

diff --git a/src/app/api/catalog/compatibility/route.ts b/src/app/api/catalog/compatibility/route.ts
index 5ce84cb5c..236b85e72 100644
--- a/src/app/api/catalog/compatibility/route.ts
+++ b/src/app/api/catalog/compatibility/route.ts
@@ -1,7 +1,10 @@
 import { TRPCError } from '@trpc/server'
+import { getHTTPStatusCodeFromError } from '@trpc/server/http'
 import { connection, type NextRequest, NextResponse } from 'next/server'
 import { ZodError } from 'zod'
 import { validateAndConsumeApiKey } from '@/lib/api/validateApiKey'
+import { ERROR_CODES } from '@/lib/errors'
+import { logger } from '@/lib/logger'
 import { GetDeviceCompatibilitySchema } from '@/schemas/mobile'
 import { prisma } from '@/server/db'
 import { getDeviceCompatibility } from '@/server/services/catalog.service'
@@ -67,15 +70,6 @@ export async function GET(request: NextRequest) {
     })
   } catch (error) {
     if (error instanceof TRPCError) {
-      const statusCodeMap: Record<string, number> = {
-        BAD_REQUEST: 400,
-        UNAUTHORIZED: 401,
-        FORBIDDEN: 403,
-        NOT_FOUND: 404,
-        TOO_MANY_REQUESTS: 429,
-        INTERNAL_SERVER_ERROR: 500,
-      }
-      const statusCode = statusCodeMap[error.code] || 500
       return NextResponse.json(
         {
           error: {
@@ -83,7 +77,7 @@ export async function GET(request: NextRequest) {
             message: error.message,
           },
         },
-        { status: statusCode },
+        { status: getHTTPStatusCodeFromError(error) },
       )
     }
 
@@ -91,7 +85,7 @@ export async function GET(request: NextRequest) {
       return NextResponse.json(
         {
           error: {
-            code: 'BAD_REQUEST',
+            code: ERROR_CODES.BAD_REQUEST,
             message: 'Invalid query parameters',
             details: error.errors,
           },
@@ -104,7 +98,7 @@ export async function GET(request: NextRequest) {
       return NextResponse.json(
         {
           error: {
-            code: 'NOT_FOUND',
+            code: ERROR_CODES.NOT_FOUND,
             message: error.message,
           },
         },
@@ -112,11 +106,11 @@ export async function GET(request: NextRequest) {
       )
     }
 
-    console.error('Catalog compatibility API error:', error)
+    logger.error('Catalog compatibility API error:', error)
     return NextResponse.json(
       {
         error: {
-          code: 'INTERNAL_SERVER_ERROR',
+          code: ERROR_CODES.INTERNAL_SERVER_ERROR,
           message: 'An unexpected error occurred',
         },
       },
diff --git a/src/app/api/mobile/auth/route.ts b/src/app/api/mobile/auth/route.ts
index 9c7f47645..ca118682d 100644
--- a/src/app/api/mobile/auth/route.ts
+++ b/src/app/api/mobile/auth/route.ts
@@ -16,7 +16,6 @@ export async function GET(request: NextRequest) {
       )
     }
 
-    // Fetch user data from database using clerkId
     const user = await prisma.user.findUnique({
       where: { clerkId: userId },
       select: { id: true, name: true, role: true, bio: true, createdAt: true },
diff --git a/src/app/api/notifications/stream/route.ts b/src/app/api/notifications/stream/route.ts
index ba7877bdc..6d9718015 100644
--- a/src/app/api/notifications/stream/route.ts
+++ b/src/app/api/notifications/stream/route.ts
@@ -1,5 +1,6 @@
 import { auth } from '@clerk/nextjs/server'
 import { connection, type NextRequest } from 'next/server'
+import { logger } from '@/lib/logger'
 import {
   realtimeNotificationService,
   createSSEResponse,
@@ -9,20 +10,16 @@ export async function GET(request: NextRequest) {
   await connection()
 
   try {
-    // Get authenticated user
     const { userId } = await auth()
 
     if (!userId) return new Response('Unauthorized', { status: 401 })
 
-    // Create SSE stream for the user
     const stream = realtimeNotificationService.createSSEConnection(userId)
-
-    // Pass origin for proper CORS handling
     const origin = request.headers.get('origin') || undefined
 
     return createSSEResponse(stream, origin)
   } catch (error) {
-    console.error('SSE connection error:', error)
+    logger.error('SSE connection error:', error)
     return new Response('Internal Server Error', { status: 500 })
   }
 }
diff --git a/src/app/api/proxy-image/route.ts b/src/app/api/proxy-image/route.ts
index 557a805b8..64fa7e902 100644
--- a/src/app/api/proxy-image/route.ts
+++ b/src/app/api/proxy-image/route.ts
@@ -1,4 +1,5 @@
 import { connection, type NextRequest } from 'next/server'
+import { env } from '@/lib/env'
 import { logger } from '@/lib/logger'
 
 /**
@@ -24,11 +25,10 @@ export async function GET(req: NextRequest) {
 
   try {
     const upstream = await fetch(url.toString(), {
-      // In dev, always bypass caches to avoid stale images; in prod let CDN cache images
-      cache: process.env.NODE_ENV !== 'production' ? 'no-store' : 'force-cache',
+      cache: env.IS_PROD ? 'force-cache' : 'no-store',
       redirect: 'follow',
       headers: {
-        'User-Agent': 'EmuReadyImageProxy/1.0 (+https://emuready.com)',
+        'User-Agent': 'EmuReadyImageProxy/1.0 (+https://www.emuready.com)',
       },
     })
 
@@ -37,10 +37,9 @@ export async function GET(req: NextRequest) {
     }
 
     const contentType = upstream.headers.get('content-type') || 'application/octet-stream'
-    const cacheControl =
-      process.env.NODE_ENV !== 'production'
-        ? 'no-store, no-cache, must-revalidate'
-        : 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600'
+    const cacheControl = env.IS_PROD
+      ? 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600'
+      : 'no-store, no-cache, must-revalidate'
 
     return new Response(upstream.body, {
       status: 200,
diff --git a/src/lib/errors.ts b/src/lib/errors.ts
index 79379008e..975ff3156 100644
--- a/src/lib/errors.ts
+++ b/src/lib/errors.ts
@@ -6,7 +6,6 @@ import { toArray } from '@/utils/array'
 import { formatUserRole } from '@/utils/format'
 import { type Role } from '@orm'
 
-// Define error types as constants for better type safety
 export const ERROR_CODES = {
   BAD_REQUEST: 'BAD_REQUEST',
   CONFLICT: 'CONFLICT',
@@ -17,8 +16,6 @@ export const ERROR_CODES = {
   UNAUTHORIZED: 'UNAUTHORIZED',
 } as const
 
-// Specific error identifiers for client-side error handling
-// These are used in error.cause to identify specific error types
 export const APP_ERROR_CODES = {
   GAME_ALREADY_EXISTS: 'GAME_ALREADY_EXISTS',
   GAME_SUBMISSION_LIMIT_EXCEEDED: 'GAME_SUBMISSION_LIMIT_EXCEEDED',
@@ -28,7 +25,6 @@ export type AppErrorCode = (typeof APP_ERROR_CODES)[keyof typeof APP_ERROR_CODES
 
 export type ErrorCode = keyof typeof ERROR_CODES
 
-// Define common error scenarios with default messages
 export const ERROR_MESSAGES = {
   // Authentication & Authorization
   FORBIDDEN: 'You do not have permission to perform this action',
