diff --git a/cannamanage-frontend/public/sw.js b/cannamanage-frontend/public/sw.js
index d3ab16c..1230d4e 100644
--- a/cannamanage-frontend/public/sw.js
+++ b/cannamanage-frontend/public/sw.js
@@ -1,6 +1,10 @@
///
-const CACHE_NAME = "cannamanage-v1"
+// Bump this version on every release that changes cached assets. The `activate`
+// handler below deletes all caches whose name !== CACHE_NAME, so incrementing
+// this string force-purges stale bundles from clients that cached the old
+// (broken) build — fixes "website hasn't changed after redeploy".
+const CACHE_NAME = "cannamanage-v2"
const OFFLINE_URL = "/offline"
// Assets to pre-cache
diff --git a/cannamanage-frontend/src/app/layout.tsx b/cannamanage-frontend/src/app/layout.tsx
index 39af928..94502be 100644
--- a/cannamanage-frontend/src/app/layout.tsx
+++ b/cannamanage-frontend/src/app/layout.tsx
@@ -1,4 +1,6 @@
import { Cairo, Lato } from "next/font/google"
+import { NextIntlClientProvider } from "next-intl"
+import { getMessages } from "next-intl/server"
import { cn } from "@/lib/utils"
@@ -50,9 +52,17 @@ const cairoFont = Cairo({
variable: "--font-cairo",
})
-export default function RootLayout(props: { children: ReactNode }) {
+export default async function RootLayout(props: { children: ReactNode }) {
const { children } = props
+ // Load messages at the root so GLOBAL components rendered here (PwaInstallPrompt,
+ // Toaster, etc.) have next-intl context. Without this, those components — which
+ // are siblings of {children} and therefore outside every route-group's
+ // NextIntlClientProvider — call useTranslations() with no provider and crash
+ // hydration on EVERY page (the "Oops" error). Nested route-group providers
+ // remain valid; next-intl supports provider nesting.
+ const messages = await getMessages()
+
return (
-
- {children}
-
-
-
-
-
+
+
+ {children}
+
+
+
+
+
+
)
diff --git a/cannamanage-frontend/src/middleware.ts b/cannamanage-frontend/src/middleware.ts
index 26b5038..49a2993 100644
--- a/cannamanage-frontend/src/middleware.ts
+++ b/cannamanage-frontend/src/middleware.ts
@@ -61,8 +61,13 @@ export const config = {
// - /portal-login (portal auth page)
// - /api/auth (NextAuth API routes)
// - /_next/static, /_next/image (Next.js internals)
- // - /favicon.ico, /images (public assets)
+ // - /favicon.ico, /images, /icons (public assets)
+ // - /manifest.json, /sw.js, /offline (PWA assets — MUST be public, otherwise the
+ // browser fetches them unauthenticated, gets a 307→/login HTML page, and:
+ // (1) parses the HTML as JSON → "manifest.json:1 Syntax error",
+ // (2) registers an HTML "sw.js" or keeps a STALE service worker in control →
+ // cached old bundles keep serving → "website hasn't changed" after redeploys.
// - /pricing, /impressum, /datenschutz, /agb (public marketing pages)
- "/((?!login|register|forgot-password|portal-login|api/auth|_next/static|_next/image|favicon.ico|images|pricing|impressum|datenschutz|agb).*)",
+ "/((?!login|register|forgot-password|portal-login|api/auth|_next/static|_next/image|favicon.ico|images|icons|manifest.json|sw.js|offline|pricing|impressum|datenschutz|agb).*)",
],
}
diff --git a/scripts/debug/.gitignore b/scripts/debug/.gitignore
new file mode 100644
index 0000000..8b9a6cb
--- /dev/null
+++ b/scripts/debug/.gitignore
@@ -0,0 +1,4 @@
+# Debug probe deps — installed on demand via `npm install playwright`
+node_modules/
+package-lock.json
+*.png
diff --git a/scripts/debug/dashboard-probe.mjs b/scripts/debug/dashboard-probe.mjs
new file mode 100644
index 0000000..8785607
--- /dev/null
+++ b/scripts/debug/dashboard-probe.mjs
@@ -0,0 +1,54 @@
+// Headless-browser probe: logs in and captures the REAL client-side error on /dashboard.
+// Run via the official Playwright Docker image (no local Node needed):
+// docker run --rm --network host -v "$PWD/scripts/debug:/work" -w /work \
+// mcr.microsoft.com/playwright:v1.49.0-jammy \
+// sh -c "npm i playwright@1.49.0 -s && node dashboard-probe.mjs"
+//
+// Captures: console messages, page errors (un-minified via sourcemaps if present),
+// failed requests, and a screenshot.
+
+import { chromium } from "playwright"
+
+const BASE = process.env.BASE_URL || "http://192.168.188.119:3000"
+const EMAIL = process.env.LOGIN_EMAIL || "admin@test.de"
+const PASSWORD = process.env.LOGIN_PASSWORD || "test123"
+
+const browser = await chromium.launch()
+const ctx = await browser.newContext({ ignoreHTTPSErrors: true })
+const page = await ctx.newPage()
+
+const log = []
+page.on("console", (m) => log.push(`[console.${m.type()}] ${m.text()}`))
+page.on("pageerror", (e) => log.push(`[pageerror] ${e.stack || e.message}`))
+page.on("requestfailed", (r) =>
+ log.push(`[requestfailed] ${r.method()} ${r.url()} — ${r.failure()?.errorText}`)
+)
+page.on("response", (r) => {
+ if (r.status() >= 400) log.push(`[http ${r.status()}] ${r.url()}`)
+})
+
+try {
+ console.log(`==> GET ${BASE}/login`)
+ await page.goto(`${BASE}/login`, { waitUntil: "networkidle", timeout: 20000 })
+
+ await page.fill('input[type="email"], input[name="email"], #email', EMAIL)
+ await page.fill('input[type="password"], input[name="password"], #password', PASSWORD)
+ await page.click('button[type="submit"]')
+
+ // Wait for navigation to settle on the dashboard (or wherever it lands)
+ await page.waitForTimeout(5000)
+ console.log(`==> landed on: ${page.url()}`)
+
+ // Force-navigate to dashboard to reproduce the crash deterministically
+ await page.goto(`${BASE}/dashboard`, { waitUntil: "networkidle", timeout: 20000 })
+ await page.waitForTimeout(3000)
+
+ await page.screenshot({ path: "dashboard-probe.png", fullPage: true })
+ console.log("==> screenshot saved: dashboard-probe.png")
+} catch (e) {
+ console.log(`[probe-error] ${e.message}`)
+} finally {
+ console.log("\n===== CAPTURED BROWSER EVENTS =====")
+ console.log(log.join("\n") || "(none)")
+ await browser.close()
+}
diff --git a/scripts/debug/package.json b/scripts/debug/package.json
new file mode 100644
index 0000000..7c1a22f
--- /dev/null
+++ b/scripts/debug/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "debug",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "playwright": "^1.60.0"
+ }
+}