Files
cannamanage/docs/ROO-HANDOVER.md
T
Patrick Plate 26a77b5e16
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
docs: record 'Oops' crash fix (intl + PWA middleware) verified via Playwright
2026-06-13 10:45:44 +02:00

14 KiB

🔁 Roo Handover — CannaManage Docker Deploy on TrueNAS

Date: 2026-06-13
Written by: Lumen (Homelab mode, claude-sonnet-4-6)
For: Next Roo session (new model)


"OOPS" CRASH FIXED — 2026-06-13 (update 3) — verified in a real browser

The previous "RESOLVED" was wrong: login was verified with curl only, which cannot execute client-side JS. In a real browser every page (including /login) crashed on hydration with the "Oops! Something went wrong" boundary. Diagnosed by installing a Playwright browser probe (scripts/debug/dashboard-probe.mjs) that logs in and captures real console/network errors + a screenshot. Three root causes, all fixed in commit 4be9c4c (frontend rebuilt + redeployed):

  1. App-wide intl hydration crash ("Oops" on every page). Root app/layout.tsx renders global client components (PwaInstallPromptuseTranslations, Toaster, Sonner) as siblings of {children} inside <Providers>, but only each route-group layout wrapped its own children in NextIntlClientProvider. Those global components mounted with no intl context → "No intl context found" → hydration crash. Fix: root layout is now async and wraps the body in NextIntlClientProvider via getMessages(). Nested route-group providers remain valid (next-intl nests).

  2. PWA assets intercepted by auth middleware (manifest.json syntax error + stale cache). The middleware matcher did not exclude /manifest.json or /sw.js, so unauthenticated browsers got 307 → /login (HTML) for both → browser parsed HTML as JSON (manifest.json:1 Syntax error) and an HTML/old service worker kept serving stale bundles ("website hasn't changed after redeploy"). Fix: matcher now excludes manifest.json, sw.js, icons, offline. Verified: manifest.json → 200 application/json, sw.js → 200 application/javascript.

  3. Service-worker stale cache. Bumped CACHE_NAME v1v2 in public/sw.js so the activate handler purges old cached bundles from clients that loaded the broken build.

Browser verification (Playwright): login admin@test.de/test123 → lands on /dashboard, no pageerror, dashboard renders. The "Oops" is gone.

⚠️ Known follow-up (non-fatal — dashboard renders via mock fallback)

Dashboard API endpoints are mis-wired: frontend calls /api/backend/dashboard/stats, /distributions/recent, /consent/check → proxy rewrites to /api/v1/dashboard/stats etc., but the backend has no such routes (real stats route is /api/v1/clubs/me/stats in ClubController; /dashboard only exists in PortalController) → HTTP 500/404. The dashboard falls back to mockClubStats/mockRecentDistributions so it still renders. Frontend ClubStats type also differs from backend ClubStatsResponse. Fix = align service paths + DTO shape, or add /api/v1/dashboard/* endpoints to the backend.

🔧 New tooling installed on the Fedora 44 workstation

  • Node.js 22.22.2 + npm 10.9.7 via sudo dnf install -y nodejs npm (was MISSING).
  • @playwright/mcp v0.0.76 (run via npx @playwright/mcp@latest) + Chromium headless-shell (npx playwright install chromium). Wired into Roo mcp_settings.json (server name playwright). Chromium launches fine on Fedora 44 despite the "OS not officially supported" warning.
  • Probe: scripts/debug/dashboard-probe.mjs — reusable client-side debugger.

LOGIN WORKING — 2026-06-13 (update 2) — full auth flow verified

After deploy, login showed a client-side "Oops! Something went wrong" error boundary. Four more root causes, all fixed and verified (signin → 302 → /dashboard, session populated with role: ADMIN + clubId):

  1. NextAuth MissingSecret → "Oops" error boundary — override env (TrueNAS, not git) NextAuth v5 (Auth.js) reads AUTH_SECRET, not NEXTAUTH_SECRET. The runtime env had only NEXTAUTH_SECRET, so signIn threw MissingSecret → the React error boundary fired. Added AUTH_SECRET (+ AUTH_TRUST_HOST=true) to the frontend service in the TrueNAS override.

  2. No seed data — DB had 0 users. Seeded club + admin (admin@test.de). The seed comment's BCrypt hash was for "password", not "test123" — regenerated a correct hash for test123.

  3. Backend HTTP 500 after successful auth: Illegal base64 character: '-' — commit dac884c JwtService.getSigningKey() does Decoders.BASE64.decode(secret). The compose secret docker-dev-secret-key-...-for-hmac is plaintext with hyphens (not valid base64), so signing the JWT threw once credentials passed. Replaced with a real base64 secret (openssl rand -base64 48).

  4. NextAuth CredentialsSignin — API/frontend contract mismatch — commit 281adda authorize() read data.member.id/email/clubName/clubId, but the backend LoginResponse is flat: { accessToken, refreshToken, expiresIn, role } — no member object. Accessing data.member.id on undefined threw → authorize returned null. Fixed by decoding the JWT payload for identity claims (sub=userId, email, tenant_id=clubId) + the flat role.

Login credentials: admin@test.de / test123 (dev seed).


RESOLVED — 2026-06-13 — CannaManage live at http://192.168.188.119:3000

All blocking issues fixed. Stack is up on TrueNAS (backend + db healthy, frontend serving). The frontend pnpm build crash was not a NextAuth problem — the 6 earlier fixes targeted the wrong layer. Three real root causes, fixed in order:

  1. Frontend build crash (ERR_INVALID_URL, input: 'undefined') — commit f6a7143 Root layout src/app/layout.tsx evaluated new URL(process.env.BASE_URL) at module load. BASE_URL was never a build-time ENV → new URL(undefined) threw. Because it's the root layout, its metadata is collected for every route during "Collecting page data", explaining why both /impressum (marketing) and /portal-login (non-marketing) failed identically. Fix: ?? "http://localhost:3000" fallback + BASE_URL build ENV in Dockerfile. The middleware/force-dynamic fixes were irrelevant (metadata is evaluated before middleware ever runs).

  2. Backend crash: Schema validation: missing table [audit_events] — commit 8490da4 This is Spring Boot 4.0.6, which modularized autoconfiguration. FlywayAutoConfiguration moved into a dedicated spring-boot-flyway module pulled in only by spring-boot-starter-flyway. The pom had only flyway-database-postgresql (+ transitive flyway-core) but NOT the starter, so spring.flyway.enabled=true was inert: no migrations ran, no flyway_schema_history, Hibernate ddl-auto=validate failed on the empty schema. Fix: add spring-boot-starter-flyway. Ref: https://spring.io/blog/2025/10/28/modularizing-spring-boot/

  3. Backend unhealthy (503 on /actuator/health) — commit 60844ef spring-boot-starter-mail registers a mail health indicator that tries localhost:1025. No SMTP container in this deployment → DOWN → aggregate health DOWN → Docker marks the container unhealthy → frontend refused to start. Fix: management.health.mail.enabled=false in the docker profile.

Infra note: Host port 8080 was already taken by odysseus-searxng-1. The TrueNAS override (/mnt/VM_SSD_Pool/cannamanage/docker-compose.truenas.yml, not in git) remaps the backend host port to 8081 using ports: !reset [] (compose merges list-type keys by concat otherwise). The internal container port stays 8080, so BACKEND_URL=http://backend:8080 is unaffected.

Verified: frontend /login 200, /impressum 200 (was the failing SSG page), / 307→login; backend /actuator/health UP; reachable from the workstation over LAN.


What We Were Doing

Deploying the full CannaManage stack (Spring Boot backend + Next.js frontend + PostgreSQL) on TrueNAS.local at 192.168.188.119 so Patrick can test the app in a browser.


Current State: ⚠️ Frontend Build Still Failing

Backend: Built and ready (image exists on TrueNAS)
DB: Postgres pulled and ready
Frontend: pnpm build failing inside Docker with ERR_INVALID_URL, input: 'undefined'

Root cause: NextAuth v5 tries to construct its internal URL at static page collection time during next build. It reads AUTH_URL env var and crashes if it's undefined. The marketing pages (/pricing, /impressum, /datenschutz, /agb) are the ones triggering it because the middleware runs on them.


All Fixes Already Applied & Pushed to Gitea

These commits are all on main at http://192.168.188.119:30008/pplate/cannamanage:

Commit Fix
61707ff Added spring-boot-starter-websocket to cannamanage-service/pom.xml
d0c53a9 Fixed DsgvoService.java: getMembershipNumber() + removed setPhone(null)
106229e Added NEXTAUTH_URL as build ARG to frontend Dockerfile
805bc4f Added AUTH_URL + AUTH_SECRET build ARGs (NextAuth v5 uses AUTH_URL)
3e4fdee Added export const dynamic = "force-dynamic" to marketing layout
b57be8a Switched Dockerfile to hardcoded build-time placeholder ENVs
d650987 Guarded auth.ts redirect callback against undefined url
9a4df56 Latest fix — excluded marketing routes from NextAuth middleware matcher

The latest fix (9a4df56) excludes /pricing, /impressum, /datenschutz, /agb from the middleware matcher pattern so NextAuth never runs on those routes during SSG.


How to Resume

Step 1: Pull latest on TrueNAS and rebuild frontend

ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && git pull && docker compose -f docker-compose.yml -f docker-compose.truenas.yml build --no-cache frontend 2>&1 | tail -20"

Step 2: If build succeeds, start the stack

ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && docker compose -f docker-compose.yml -f docker-compose.truenas.yml up -d"

Step 3: Wait for backend to be healthy (Flyway runs migrations on first start)

ssh truenas.local "docker ps --filter name=cannamanage"
# Wait for cannamanage-backend to show (healthy)

Step 4: Load seed data

ssh truenas.local "docker exec -i cannamanage-db psql -U cannamanage -d cannamanage < /mnt/VM_SSD_Pool/cannamanage/scripts/seed/init.sql"

Step 5: Open the app


If Frontend Build Still Fails

The build error is always:

[Error: Failed to collect page data for /pricing]
ERR_INVALID_URL, input: 'undefined'
    at chunk 7624.js (NextAuth internal URL construction)

This is NextAuth v5 initializing its session URL. The last fix excluded the marketing routes from the middleware — if it still fails, the problem is that NextAuth's auth.ts is being imported somewhere in the page tree even without middleware running.

Nuclear option: Add SKIP_ENV_VALIDATION=1 to the frontend builder ENV in the Dockerfile, or better: create a src/auth-server.ts that lazy-initializes auth only at runtime (not build time) and update the pages that import it.

Simpler workaround: Add export const dynamic = "force-dynamic" to each individual marketing page file:

  • src/app/(marketing)/pricing/page.tsx — line 1 after "use client" → add export const dynamic = "force-dynamic"
  • src/app/(marketing)/impressum/page.tsx
  • src/app/(marketing)/datenschutz/page.tsx
  • src/app/(marketing)/agb/page.tsx

Note: "use client" pages can't export dynamic, so it would need to go in the layout instead — which was already tried and didn't work. The real fix is the middleware matcher fix in commit 9a4df56.


Files Modified (all in /home/pplate/IdeaProjects/cannamanage/)

File Change
cannamanage-service/pom.xml +websocket dep
cannamanage-service/src/.../DsgvoService.java method name fix
cannamanage-frontend/Dockerfile build-time ENV placeholders
cannamanage-frontend/src/lib/auth.ts null guard in redirect callback
cannamanage-frontend/src/app/(marketing)/layout.tsx force-dynamic export
cannamanage-frontend/src/middleware.ts marketing routes excluded from matcher

TrueNAS Deploy Location

/mnt/VM_SSD_Pool/cannamanage/
├── docker-compose.yml           (base)
├── docker-compose.truenas.yml   (override: NEXTAUTH_URL=http://192.168.188.119:3000)
├── Dockerfile.backend
├── cannamanage-frontend/
│   └── Dockerfile
└── scripts/seed/init.sql        (seed data)

Seed Data Summary (loaded into DB after first boot)

  • Club: Grüner Daumen e.V., Berlin, max 500 members
  • Admin: admin@test.de / test123 (ROLE_ADMIN)
  • Members: 5 (Max, Anna, Jonas[under-21], Lisa, Tom)
  • Strains: Northern Lights (18.5% THC), Amnesia Haze (22% THC), CBD Critical Mass (5% THC)
  • Batches: 3 available batches (500g, 300g, 200g)
  • Tour guide: docs/TOURGUIDE.md

Container Management Commands

# Status
ssh truenas.local "docker ps --filter name=cannamanage"

# Logs
ssh truenas.local "docker logs cannamanage-backend -f --tail=50"
ssh truenas.local "docker logs cannamanage-frontend -f --tail=50"

# Rebuild specific service
ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && git pull && docker compose -f docker-compose.yml -f docker-compose.truenas.yml up -d --build frontend"

# Stop all
ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && docker compose -f docker-compose.yml -f docker-compose.truenas.yml down"

Generated 2026-06-13 by Lumen (Homelab mode)