diff --git a/.planning/phases/01-foundation-portal-shell/01-RESEARCH.md b/.planning/phases/01-foundation-portal-shell/01-RESEARCH.md
new file mode 100644
index 0000000..adf4c7e
--- /dev/null
+++ b/.planning/phases/01-foundation-portal-shell/01-RESEARCH.md
@@ -0,0 +1,905 @@
+# Phase 1: Foundation & Portal Shell - Research
+
+**Researched:** 2026-06-18
+**Domain:** Monorepo scaffold, Docker infrastructure, Next.js frontend shell with i18n/theming, NestJS API skeleton
+**Confidence:** HIGH
+
+## Summary
+
+Phase 1 establishes the end-to-end walking skeleton for Tessera: a Docker Compose stack with PostgreSQL, a pnpm/Turborepo monorepo containing a Next.js 16 frontend shell and NestJS 11 API backend, with fully functional i18n (DE/EN), theme switching (light/dark with persistence), and responsive portal layout (header + collapsible sidebar + main area). No authentication, no modules -- purely the structural frame.
+
+The primary technical challenge is establishing the monorepo structure, Docker networking with proper segmentation, and the design token system that supports the yellow primary color (#ffed00) with configurable accent colors. The i18n setup uses next-intl in "without i18n routing" mode (cookie-based locale, no locale prefix in URLs) since language preference is a user setting, not a URL concern.
+
+**Primary recommendation:** Use `pnpm dlx create-turbo@latest` for scaffolding, add the NestJS app manually, configure Docker Compose with three segregated networks (frontend/backend/data), and establish the shadcn/ui design token system with CSS variables from day one.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- **D-01:** Sidebar einklappbar auf Icon-Leiste (Toggle zwischen ~240px voller Breite und schmaler Icon-Ansicht)
+- **D-02:** Breite ausgeklappt: ~240px (Standard wie VS Code/Slack)
+- **D-03:** Mobile/kleine Bildschirme: komplett versteckt, Hamburger-Button zum Oeffnen
+- **D-04:** Position: immer links (nicht konfigurierbar)
+- **D-05:** Kategorien als Accordion (aufklappbar, zeigen Module darunter)
+- **D-06:** Footer-Bereich in der Sidebar: Einstellungen-Icon und kompakte User-Info
+- **D-07:** Inhalt: Logo/Branding links, Breadcrumb/Seitentitel mittig, globale Suche, Benutzer-Menu rechts (Avatar + Dropdown)
+- **D-08:** Hoehe: ~56-64px (Standard, nicht dominant)
+- **D-09:** Verhalten: sticky (bleibt immer sichtbar oben beim Scrollen)
+- **D-10:** Primaer-/Markenfarbe: Gelb #ffed00
+- **D-11:** Akzentfarbe: konfigurierbar in den Benutzer-Einstellungen
+- **D-12:** Visueller Stil: Modern/Clean (viel Whitespace, klare Linien, subtile Schatten -- wie Notion/Linear)
+- **D-13:** Ecken: leicht abgerundet (border-radius ~6-8px)
+- **D-14:** Dark Mode: Claude entscheidet (dark gray empfohlen fuer Augenkomfort bei gelbem Akzent)
+- **D-15:** Startseite ist das Dashboard (in Sidebar aktiv, im Hauptbereich geoeffnet)
+- **D-16:** Leeres Dashboard zeigt "Keine Widgets aktiv" mit Button zum Hinzufuegen
+- **D-17:** Dashboard-Funktionalitaet orientiert sich an Homarr -- aber mit feinerem Grid fuer die Widget-Ausrichtung
+
+### Claude's Discretion
+- Dark Mode Farbton (empfohlen: dunkles Grau/Blau statt true-black wegen gelber Primaerfarbe)
+- Sidebar-Inhalt im Leer-Zustand (nur Dashboard + Marketplace sichtbar empfohlen)
+- Font-Wahl (Inter oder aehnliche clean sans-serif empfohlen)
+
+### Deferred Ideas (OUT OF SCOPE)
+None -- discussion stayed within phase scope
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| INFRA-01 | Komplette Anwendung laeuft als Docker-Compose-Stack | Docker Compose multi-service configuration with health checks, multi-stage builds for both Next.js and NestJS |
+| INFRA-02 | PostgreSQL-Datenbank im Container | PostgreSQL 16 container with named volume, health check via pg_isready |
+| INFRA-03 | Docker-Netzwerksegmentierung (Frontend/Backend/Data) | Three-network architecture: frontend-net, backend-net, data-net with internal:true on data-net |
+| PRTAL-01 | Schmale Kopfzeile mit Branding und Benutzer-Menu | Sticky header component ~56-64px, logo left, breadcrumb center, user menu right |
+| PRTAL-04 | Responsive Layout fuer verschiedene Bildschirmgroessen | Tailwind responsive breakpoints, sidebar collapse behavior, mobile hamburger menu |
+| UI-01 | Light/Dark Theme umschaltbar pro Benutzer | next-themes + shadcn/ui CSS variables, localStorage persistence, .dark class toggle |
+| UI-02 | Zweisprachig: Deutsch und Englisch mit Sprachwahl | next-intl without i18n routing, cookie-based locale, message JSON files per locale |
+| UI-03 | i18n-Framework von Anfang an integriert (alle Strings ueber t('key')) | next-intl useTranslations() hook, no hardcoded strings from first component |
+
+
+## Architectural Responsibility Map
+
+| Capability | Primary Tier | Secondary Tier | Rationale |
+|------------|-------------|----------------|-----------|
+| Portal layout (header, sidebar, main) | Frontend (Next.js SSR) | -- | Pure UI structure, server-rendered for SEO/performance |
+| Theme switching | Browser/Client | Frontend Server (cookie) | Toggle is client-side interaction; preference stored in cookie/localStorage for SSR consistency |
+| i18n rendering | Frontend Server (SSR) | Browser/Client | Translations resolved server-side via next-intl; locale switch triggers client refresh |
+| Locale persistence | Browser/Client (cookie) | -- | NEXT_LOCALE cookie read by server on each request |
+| Docker orchestration | Infrastructure | -- | Compose file defines all services, networks, volumes |
+| PostgreSQL | Database/Storage | -- | Data tier, isolated on internal network |
+| API health check | API/Backend (NestJS) | -- | Simple GET /health endpoint proving backend runs |
+| Design tokens | Frontend (CSS) | -- | CSS custom properties at :root, theme variants via .dark selector |
+
+## Standard Stack
+
+### Core (Phase 1 only)
+
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Next.js | 16.2.9 | Frontend framework | App Router, React 19, standalone output for Docker [VERIFIED: npm registry] |
+| React | 19.x | UI library | Ships with Next.js 16, Server Components [VERIFIED: npm registry] |
+| NestJS | 11.1.27 | API framework | Modular monolith, DI, Express 5 default [VERIFIED: npm registry] |
+| Prisma | 7.8.0 | ORM | TypeScript-first, migration tooling, RLS support [VERIFIED: npm registry] |
+| PostgreSQL | 16 | Database | RLS for multi-tenancy, JSONB, Docker-native [ASSUMED] |
+| Tailwind CSS | 4.3.1 | Styling | CSS-first config, dark mode via .dark, OKLCH colors [VERIFIED: npm registry] |
+| shadcn/ui | CLI v4 | Component library | Copies into project, CSS variable theming, Radix primitives [ASSUMED] |
+| next-themes | 0.4.6 | Theme switching | SSR-safe, system preference detection, 2-line shadcn integration [VERIFIED: npm registry] |
+| next-intl | 4.13.0 | i18n | App Router native, Server Component support, without-i18n-routing mode [VERIFIED: npm registry] |
+| Zustand | 5.0.14 | Client state | Sidebar toggle, UI preferences, 1.1kb [VERIFIED: npm registry] |
+
+### Supporting
+
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| pnpm | 9.x | Package manager | Workspace management, strict deps [ASSUMED] |
+| Turborepo | 2.9.18 | Build orchestration | Parallel builds, caching, task deps [VERIFIED: npm registry] |
+| @tailwindcss/postcss | 4.3.1 | PostCSS plugin | Required for Tailwind v4 integration [VERIFIED: npm registry] |
+| Biome | 2.5.0 | Lint + format | Replaces ESLint + Prettier [VERIFIED: npm registry] |
+| TypeScript | 5.5+ | Type safety | All packages use strict TS [ASSUMED] |
+
+### Alternatives Considered
+
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| next-intl without routing | next-intl with routing ([locale] prefix) | URL prefixes add complexity; portal is a logged-in app where locale is a user preference, not SEO content |
+| Cookie-based locale | URL-based locale | Cookie approach simpler for SPAs/portals; URL approach better for public content sites |
+| Zustand for sidebar state | React Context | Zustand avoids provider nesting, works outside React tree, persists to localStorage natively |
+| next-themes | Manual .dark class toggle | next-themes handles SSR flash-of-wrong-theme, system preference, and localStorage sync |
+
+**Installation (Phase 1 packages):**
+```bash
+# Root (monorepo tooling)
+pnpm add -D turbo @biomejs/biome typescript
+
+# apps/web (Next.js frontend)
+pnpm add next react react-dom next-intl next-themes zustand
+pnpm add -D tailwindcss @tailwindcss/postcss postcss typescript @types/react @types/react-dom
+
+# apps/api (NestJS backend)
+pnpm add @nestjs/core @nestjs/common @nestjs/platform-express @nestjs/config rxjs reflect-metadata
+pnpm add @prisma/client
+pnpm add -D prisma @nestjs/cli typescript @types/node @types/express
+
+# packages/shared (types and constants)
+pnpm add -D typescript
+```
+
+## Package Legitimacy Audit
+
+| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
+|---------|----------|-----|-----------|-------------|---------|-------------|
+| next | npm | 8+ yrs | 39.6M/wk | github.com/vercel/next.js | OK | Approved (SUS=too-new only) |
+| next-intl | npm | 5+ yrs | 3.9M/wk | github.com/amannn/next-intl | OK | Approved (SUS=too-new only) |
+| next-themes | npm | 4+ yrs | 24.1M/wk | github.com/pacocoursey/next-themes | OK | Approved |
+| tailwindcss | npm | 7+ yrs | 120M/wk | github.com/tailwindlabs/tailwindcss | OK | Approved (SUS=too-new only) |
+| @tailwindcss/postcss | npm | 1+ yr | 24.3M/wk | github.com/tailwindlabs/tailwindcss | OK | Approved (SUS=too-new only) |
+| zustand | npm | 6+ yrs | 42.5M/wk | github.com/pmndrs/zustand | OK | Approved (SUS=too-new only) |
+| @nestjs/core | npm | 7+ yrs | 11.5M/wk | github.com/nestjs/nest | OK | Approved (SUS=too-new only) |
+| @nestjs/common | npm | 7+ yrs | 12M/wk | github.com/nestjs/nest | OK | Approved (SUS=too-new only) |
+| prisma | npm | 5+ yrs | 13.4M/wk | github.com/prisma/prisma | OK | Approved |
+| @prisma/client | npm | 5+ yrs | 12.1M/wk | github.com/prisma/prisma | OK | Approved |
+| turbo | npm | 3+ yrs | 17.1M/wk | github.com/vercel/turborepo | OK | Approved (SUS=too-new only) |
+| @biomejs/biome | npm | 2+ yrs | 10.1M/wk | github.com/biomejs/biome | OK | Approved (SUS=too-new only) |
+
+**Packages removed due to [SLOP] verdict:** None
+**Packages flagged as suspicious [SUS]:** None (all SUS flags were "too-new" on well-established packages with millions of weekly downloads and verified source repos -- false positives from recent publishes)
+
+## Architecture Patterns
+
+### System Architecture Diagram (Phase 1)
+
+```
+Browser (localhost:3000)
+ |
+ v
++--[frontend-net]-----------------------------------------------+
+| |
+| +------------------+ +-------------------+ |
+| | Traefik (proxy) |-------->| Next.js (web) | |
+| | :80 | | :3000 (internal) | |
+| +------------------+ +-------------------+ |
+| | |
++----------------------------------------|------------------------+
+ |
++--[backend-net]-------------------------|------------------------+
+| v |
+| +-------------------+ |
+| | NestJS (api) | |
+| | :3001 (internal) | |
+| +-------------------+ |
+| | |
++----------------------------------------|------------------------+
+ |
++--[data-net (internal: true)]-----------|------------------------+
+| v |
+| +-------------------+ |
+| | PostgreSQL (db) | |
+| | :5432 (internal) | |
+| +-------------------+ |
+| |
++-----------------------------------------------------------------+
+```
+
+**Data flow:** Browser -> Traefik (reverse proxy) -> Next.js (serves pages, calls API) -> NestJS API -> PostgreSQL. The data-net is `internal: true` (no external access). Next.js and NestJS share backend-net. Traefik and Next.js share frontend-net.
+
+### Recommended Project Structure
+
+```
+tessera-ctl/
+├── apps/
+│ ├── web/ # Next.js 16 frontend
+│ │ ├── src/
+│ │ │ ├── app/
+│ │ │ │ ├── layout.tsx # Root layout with providers
+│ │ │ │ ├── page.tsx # Dashboard (redirect or direct)
+│ │ │ │ └── globals.css # Tailwind + design tokens
+│ │ │ ├── components/
+│ │ │ │ ├── layout/
+│ │ │ │ │ ├── header.tsx
+│ │ │ │ │ ├── sidebar.tsx
+│ │ │ │ │ └── app-shell.tsx
+│ │ │ │ └── ui/ # shadcn/ui components
+│ │ │ ├── i18n/
+│ │ │ │ ├── request.ts # Locale resolution from cookie
+│ │ │ │ └── navigation.ts # Navigation helpers
+│ │ │ ├── lib/
+│ │ │ │ └── stores/
+│ │ │ │ └── sidebar-store.ts
+│ │ │ └── messages/
+│ │ │ ├── de.json
+│ │ │ └── en.json
+│ │ ├── public/
+│ │ ├── next.config.ts
+│ │ ├── postcss.config.mjs
+│ │ ├── Dockerfile
+│ │ └── package.json
+│ └── api/ # NestJS 11 backend
+│ ├── src/
+│ │ ├── main.ts
+│ │ ├── app.module.ts
+│ │ └── health/
+│ │ ├── health.controller.ts
+│ │ └── health.module.ts
+│ ├── prisma/
+│ │ └── schema.prisma
+│ ├── Dockerfile
+│ └── package.json
+├── packages/
+│ ├── shared/ # Shared types, constants
+│ │ ├── src/
+│ │ │ └── index.ts
+│ │ └── package.json
+│ └── ui/ # Shared UI components (future)
+│ └── package.json
+├── docker-compose.yml
+├── docker-compose.dev.yml # Dev overrides (volumes, hot reload)
+├── pnpm-workspace.yaml
+├── turbo.json
+├── biome.json
+├── package.json
+└── tsconfig.base.json
+```
+
+### Pattern 1: Docker Network Segmentation
+
+**What:** Three isolated Docker networks with minimal cross-network access.
+**When to use:** Always -- from the first docker-compose.yml.
+
+```yaml
+# docker-compose.yml
+services:
+ traefik:
+ image: traefik:v3.4
+ ports:
+ - "80:80"
+ networks:
+ - frontend-net
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+
+ web:
+ build:
+ context: ./apps/web
+ dockerfile: Dockerfile
+ networks:
+ - frontend-net
+ - backend-net
+ depends_on:
+ api:
+ condition: service_healthy
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.web.rule=PathPrefix(`/`)"
+
+ api:
+ build:
+ context: ./apps/api
+ dockerfile: Dockerfile
+ networks:
+ - backend-net
+ - data-net
+ depends_on:
+ db:
+ condition: service_healthy
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
+ interval: 10s
+ timeout: 5s
+ retries: 3
+
+ db:
+ image: postgres:16-alpine
+ networks:
+ - data-net
+ volumes:
+ - pgdata:/var/lib/postgresql/data
+ environment:
+ POSTGRES_USER: tessera
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-tessera_dev}
+ POSTGRES_DB: tessera
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U tessera"]
+ interval: 5s
+ timeout: 3s
+ retries: 5
+
+networks:
+ frontend-net:
+ driver: bridge
+ backend-net:
+ driver: bridge
+ data-net:
+ driver: bridge
+ internal: true # No external connectivity
+
+volumes:
+ pgdata:
+```
+
+**Source:** [Docker Docs: Networking in Compose](https://docs.docker.com/compose/how-tos/networking/) [CITED: docs.docker.com]
+
+### Pattern 2: next-intl Without i18n Routing (Cookie-Based)
+
+**What:** Locale resolved from cookie, no [locale] URL prefix.
+**When to use:** Portal/SPA apps where locale is a user preference.
+
+```typescript
+// src/i18n/request.ts
+import { getRequestConfig } from 'next-intl/server';
+import { cookies } from 'next/headers';
+
+export default getRequestConfig(async () => {
+ const cookieStore = await cookies();
+ const locale = cookieStore.get('NEXT_LOCALE')?.value || 'de';
+
+ return {
+ locale,
+ messages: (await import(`../messages/${locale}.json`)).default
+ };
+});
+```
+
+```typescript
+// src/components/locale-switcher.tsx (Client Component)
+'use client';
+import { useRouter } from 'next/navigation';
+import { useTransition } from 'react';
+
+export function LocaleSwitcher({ currentLocale }: { currentLocale: string }) {
+ const router = useRouter();
+ const [isPending, startTransition] = useTransition();
+
+ function switchLocale(newLocale: string) {
+ document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;
+ startTransition(() => {
+ router.refresh();
+ });
+ }
+
+ return (
+
+ );
+}
+```
+
+**Source:** [next-intl: Without i18n routing](https://next-intl.dev/docs/getting-started/app-router/without-i18n-routing) [CITED: next-intl.dev]
+
+### Pattern 3: shadcn/ui Design Tokens with Custom Primary Color
+
+**What:** CSS custom properties defining the design system, yellow primary with dark mode variant.
+**When to use:** From the first component render.
+
+```css
+/* src/app/globals.css */
+@import "tailwindcss";
+
+@theme inline {
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --radius-sm: calc(var(--radius) - 2px);
+ --radius-md: var(--radius);
+ --radius-lg: calc(var(--radius) + 2px);
+}
+
+:root {
+ --radius: 0.5rem; /* 8px -- per D-13 */
+ --background: oklch(0.99 0 0);
+ --foreground: oklch(0.15 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.15 0 0);
+ --primary: oklch(0.91 0.19 102); /* #ffed00 in OKLCH */
+ --primary-foreground: oklch(0.20 0.02 90); /* Dark text on yellow */
+ --secondary: oklch(0.96 0 0);
+ --secondary-foreground: oklch(0.15 0 0);
+ --muted: oklch(0.96 0 0);
+ --muted-foreground: oklch(0.55 0 0);
+ --accent: oklch(0.96 0 0); /* Configurable per user */
+ --accent-foreground: oklch(0.15 0 0);
+ --border: oklch(0.90 0 0);
+ --input: oklch(0.90 0 0);
+ --ring: oklch(0.91 0.19 102); /* Matches primary */
+}
+
+.dark {
+ --background: oklch(0.17 0.01 260); /* Dark gray-blue */
+ --foreground: oklch(0.95 0 0);
+ --card: oklch(0.21 0.01 260);
+ --card-foreground: oklch(0.95 0 0);
+ --primary: oklch(0.91 0.19 102); /* Yellow stays vibrant */
+ --primary-foreground: oklch(0.20 0.02 90);
+ --secondary: oklch(0.25 0.01 260);
+ --secondary-foreground: oklch(0.95 0 0);
+ --muted: oklch(0.25 0.01 260);
+ --muted-foreground: oklch(0.65 0 0);
+ --accent: oklch(0.25 0.01 260);
+ --accent-foreground: oklch(0.95 0 0);
+ --border: oklch(0.30 0.01 260);
+ --input: oklch(0.30 0.01 260);
+ --ring: oklch(0.91 0.19 102);
+}
+```
+
+**Design decision (Claude's discretion):** Dark mode uses `oklch(0.17 0.01 260)` -- a dark gray-blue that provides comfortable contrast with the yellow #ffed00 primary without the harshness of true black. This matches the user's recommendation. [ASSUMED]
+
+**Source:** [shadcn/ui Theming](https://ui.shadcn.com/docs/theming) [CITED: ui.shadcn.com]
+
+### Pattern 4: Next.js Standalone Docker Build
+
+**What:** Multi-stage Docker build using standalone output mode for minimal image size.
+**When to use:** Production deployment of Next.js in Docker.
+
+```dockerfile
+# apps/web/Dockerfile
+FROM node:24-alpine AS base
+RUN corepack enable && corepack prepare pnpm@9 --activate
+
+FROM base AS deps
+WORKDIR /app
+COPY package.json pnpm-lock.yaml ./
+RUN pnpm install --frozen-lockfile --prod=false
+
+FROM base AS builder
+WORKDIR /app
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+RUN pnpm build
+
+FROM node:24-alpine AS runner
+WORKDIR /app
+ENV NODE_ENV=production
+RUN addgroup --system --gid 1001 nodejs && \
+ adduser --system --uid 1001 nextjs
+COPY --from=builder /app/public ./public
+COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+USER nextjs
+EXPOSE 3000
+CMD ["node", "server.js"]
+```
+
+**Source:** [Next.js Deploying Docs](https://nextjs.org/docs/app/getting-started/deploying) [CITED: nextjs.org]
+
+### Pattern 5: NestJS Multi-Stage Docker Build
+
+**What:** Production-optimized NestJS container with only compiled dist/.
+**When to use:** Containerized NestJS deployment.
+
+```dockerfile
+# apps/api/Dockerfile
+FROM node:24-alpine AS base
+RUN corepack enable && corepack prepare pnpm@9 --activate
+
+FROM base AS deps
+WORKDIR /app
+COPY package.json pnpm-lock.yaml ./
+RUN pnpm install --frozen-lockfile
+
+FROM base AS builder
+WORKDIR /app
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+RUN pnpm build
+
+FROM node:24-alpine AS runner
+WORKDIR /app
+ENV NODE_ENV=production
+RUN addgroup --system --gid 1001 nestjs && \
+ adduser --system --uid 1001 nestjs
+COPY --from=builder /app/dist ./dist
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/package.json ./
+USER nestjs
+EXPOSE 3001
+CMD ["node", "dist/main.js"]
+```
+
+**Source:** [NestJS Docker multi-stage builds](https://www.tomray.dev/nestjs-docker-production) [CITED: tomray.dev]
+
+### Pattern 6: Turborepo Configuration
+
+**What:** Monorepo task orchestration with proper dependency graph.
+**When to use:** Root-level build/dev/lint commands.
+
+```jsonc
+// turbo.json
+{
+ "$schema": "https://turborepo.dev/schema.json",
+ "tasks": {
+ "build": {
+ "dependsOn": ["^build"],
+ "outputs": ["dist/**", ".next/**"],
+ "inputs": ["src/**", "package.json", "tsconfig.json"]
+ },
+ "dev": {
+ "cache": false,
+ "persistent": true
+ },
+ "lint": {
+ "outputs": []
+ },
+ "type-check": {
+ "dependsOn": ["^build"],
+ "outputs": []
+ }
+ }
+}
+```
+
+```yaml
+# pnpm-workspace.yaml
+packages:
+ - "apps/*"
+ - "packages/*"
+```
+
+**Source:** [Turborepo: Structuring a Repository](https://turborepo.dev/repo/docs/crafting-your-repository/structuring-a-repository) [CITED: turborepo.dev]
+
+### Anti-Patterns to Avoid
+
+- **Hardcoded strings in JSX:** Every user-visible string must go through `t('key')` from day one. German is 30-40% longer than English -- layouts WILL break if retrofitted later.
+- **Flat Docker network:** Never put all services on one network. The database must be unreachable from the frontend container.
+- **Theme without design tokens:** Do not scatter color values across components. All colors come from CSS variables at `:root`.
+- **Locale in URL path:** For a portal app, locale in cookies is simpler and avoids middleware complexity for a non-public app.
+- **`npm install` in Docker:** Always use `pnpm install --frozen-lockfile` for reproducible builds.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Theme switching | Manual class toggle + localStorage | next-themes | Handles SSR flash, system preference, localStorage sync, .dark class |
+| i18n framework | Custom translation loader | next-intl | Plural forms, ICU MessageFormat, Server Component support, type safety |
+| Responsive sidebar | CSS-only sidebar toggle | Zustand store + Tailwind | Need state persistence, mobile detection, animation coordination |
+| Design system | Manual CSS variables | shadcn/ui + Tailwind theme | Pre-built accessible components, consistent token naming, dark mode support |
+| Docker networking | Single default network | Explicit network definitions | Security boundary enforcement at infrastructure level |
+| Reverse proxy | Port mapping per service | Traefik with labels | Auto-discovery, single entrypoint, easy SSL later |
+| Build orchestration | Shell scripts for multi-app builds | Turborepo | Dependency graph, caching, parallel execution |
+
+**Key insight:** Phase 1 is foundation-heavy. Every shortcut taken here (flat network, hardcoded strings, no design tokens) creates exponentially more work in later phases. The investment in proper infrastructure now pays dividends through phases 2-6.
+
+## Common Pitfalls
+
+### Pitfall 1: i18n Retrofitting
+
+**What goes wrong:** UI components use hardcoded strings. When i18n is added later, every component must be touched. German text is 30-40% longer than English, breaking layouts.
+**Why it happens:** Feels like a "later" task, but actually requires layout redesign and string extraction.
+**How to avoid:** Use `t('key')` from the very first component. Design layouts with flexible widths. Test with German as default (longer strings catch overflow early).
+**Warning signs:** Any string literal in JSX that a user would see.
+
+### Pitfall 2: Flash of Wrong Theme (FOWT)
+
+**What goes wrong:** Page renders with light theme before JavaScript hydrates and applies the saved dark theme preference, causing a flash.
+**Why it happens:** Server does not know the user's theme preference during SSR.
+**How to avoid:** next-themes injects a script in `
` that sets the theme class before first paint. Never add theme logic in useEffect -- it's too late.
+**Warning signs:** Visible flash when loading a dark-themed page.
+
+### Pitfall 3: Docker Volume Permissions
+
+**What goes wrong:** Containers run as non-root (correctly) but PostgreSQL volume has root ownership, causing write failures on fresh installs.
+**Why it happens:** Named volumes default to root. Non-root container users cannot write.
+**How to avoid:** Use the official postgres image (handles permissions internally). For custom images, set user/group in Dockerfile and use entrypoint scripts.
+**Warning signs:** Container crashes on startup with permission denied errors.
+
+### Pitfall 4: pnpm Workspace Dependency Resolution in Docker
+
+**What goes wrong:** Docker builds fail because workspace packages reference each other via `workspace:*` protocol, but the Docker build context does not include sibling packages.
+**Why it happens:** Each Dockerfile only has its own app directory as context.
+**How to avoid:** Use the monorepo root as Docker build context, or use Turborepo's `turbo prune` to create isolated package directories for Docker builds.
+**Warning signs:** `ERR_PNPM_NO_MATCHING_VERSION` during Docker build.
+
+### Pitfall 5: Tailwind v4 Configuration Confusion
+
+**What goes wrong:** Developer uses v3 patterns (tailwind.config.js, @tailwind directives) which do not work in v4.
+**Why it happens:** Most tutorials and Stack Overflow answers reference v3.
+**How to avoid:** In Tailwind v4: use `@import "tailwindcss"` (not directives), use `@tailwindcss/postcss` plugin (not tailwindcss directly), configure via CSS `@theme` blocks (not JS config file).
+**Warning signs:** Tailwind classes not applying, "Unknown rule @tailwind" errors.
+
+## Code Examples
+
+### Root Layout with Providers
+
+```typescript
+// src/app/layout.tsx
+// Source: next-intl.dev + next-themes docs
+import { NextIntlClientProvider } from 'next-intl';
+import { getMessages, getLocale } from 'next-intl/server';
+import { ThemeProvider } from 'next-themes';
+import './globals.css';
+
+export default async function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const locale = await getLocale();
+ const messages = await getMessages();
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
+```
+
+### Zustand Sidebar Store
+
+```typescript
+// src/lib/stores/sidebar-store.ts
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+
+interface SidebarState {
+ isCollapsed: boolean;
+ isMobileOpen: boolean;
+ toggle: () => void;
+ setMobileOpen: (open: boolean) => void;
+}
+
+export const useSidebarStore = create()(
+ persist(
+ (set) => ({
+ isCollapsed: false,
+ isMobileOpen: false,
+ toggle: () => set((state) => ({ isCollapsed: !state.isCollapsed })),
+ setMobileOpen: (open) => set({ isMobileOpen: open }),
+ }),
+ { name: 'tessera-sidebar' }
+ )
+);
+```
+
+### NestJS Health Endpoint
+
+```typescript
+// src/health/health.controller.ts
+import { Controller, Get } from '@nestjs/common';
+
+@Controller('health')
+export class HealthController {
+ @Get()
+ check() {
+ return { status: 'ok', timestamp: new Date().toISOString() };
+ }
+}
+```
+
+### Message JSON Structure
+
+```json
+// src/messages/de.json
+{
+ "common": {
+ "appName": "Tessera",
+ "loading": "Laden...",
+ "save": "Speichern",
+ "cancel": "Abbrechen"
+ },
+ "header": {
+ "search": "Suchen...",
+ "userMenu": "Benutzermenu"
+ },
+ "sidebar": {
+ "dashboard": "Dashboard",
+ "marketplace": "Marktplatz",
+ "settings": "Einstellungen",
+ "collapse": "Einklappen",
+ "expand": "Ausklappen"
+ },
+ "dashboard": {
+ "empty": "Keine Widgets aktiv",
+ "addWidget": "Widget hinzufuegen"
+ },
+ "theme": {
+ "light": "Hell",
+ "dark": "Dunkel",
+ "system": "System"
+ },
+ "locale": {
+ "de": "Deutsch",
+ "en": "English"
+ }
+}
+```
+
+### next.config.ts with Standalone Output and next-intl
+
+```typescript
+// apps/web/next.config.ts
+import createNextIntlPlugin from 'next-intl/plugin';
+
+const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
+
+const nextConfig = {
+ output: 'standalone' as const,
+};
+
+export default withNextIntl(nextConfig);
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Tailwind v3 JS config | Tailwind v4 CSS-first config (@theme) | Jan 2025 | No tailwind.config.js needed, faster builds |
+| @tailwind directives | @import "tailwindcss" | Tailwind v4 | Single import replaces 3 directives |
+| next-i18next | next-intl | 2023-2024 | App Router native, Server Components, smaller bundle |
+| pages/ router | app/ router | Next.js 13+ (stable 14+) | Server Components, layouts, streaming |
+| Prisma Rust engine | Prisma v7 pure TypeScript | Apr 2025 | 3x faster, 90% smaller, no binary downloads |
+| npm/yarn workspaces | pnpm workspaces + Turborepo | 2023+ | Strict deps, content-addressable store, build caching |
+| ESLint + Prettier | Biome | 2024+ | Single tool, 100x faster, less config |
+
+**Deprecated/outdated:**
+- `tailwind.config.js` / `tailwind.config.ts`: Not needed in Tailwind v4. Use CSS `@theme` blocks instead.
+- `@tailwind base; @tailwind components; @tailwind utilities;`: Replaced by `@import "tailwindcss"` in v4.
+- `next-i18next`: Designed for Pages Router. Use `next-intl` for App Router.
+- `getServerSideProps` / `getStaticProps`: Replaced by Server Components and `use cache` in App Router.
+
+## Project Constraints (from CLAUDE.md)
+
+- GSD workflow enforcement: Do not make direct repo edits outside a GSD workflow unless explicitly asked
+- Conventions not yet established -- this phase establishes them
+- No existing codebase patterns -- greenfield
+
+## Assumptions Log
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | PostgreSQL 16-alpine Docker image is the correct base | Standard Stack | Low -- could use 16 non-alpine, minimal impact |
+| A2 | shadcn/ui CLI v4 uses `npx shadcn@latest init` for setup | Architecture Patterns | Low -- command may vary slightly |
+| A3 | OKLCH color value for #ffed00 is approximately oklch(0.91 0.19 102) | Code Examples | Medium -- exact conversion should be verified with a color tool |
+| A4 | next-intl 4.13 supports `getMessages()` and `getLocale()` without routing setup | Code Examples | Medium -- API may require slightly different imports |
+| A5 | Font choice: Inter (sans-serif) for the clean modern style | Architecture Patterns | Low -- user recommended "Inter or similar" |
+| A6 | Traefik v3.4 is current stable for Docker | Architecture Patterns | Low -- any 3.x works |
+| A7 | pnpm 9.x is available via corepack on Node 24 | Environment | Low -- corepack confirmed available, pnpm version may differ |
+
+## Open Questions
+
+1. **pnpm not globally installed**
+ - What we know: corepack 0.35.0 is available on Node 24.16.0; pnpm is not globally installed
+ - What's unclear: Whether to use `corepack enable && corepack use pnpm@9` or install pnpm globally
+ - Recommendation: Use corepack (it ships with Node) -- add `"packageManager": "pnpm@9.15.0"` to root package.json
+
+2. **Monorepo Docker build context strategy**
+ - What we know: pnpm workspace:* protocol causes issues when Docker context is per-app
+ - What's unclear: Whether to use `turbo prune --docker` or set build context to monorepo root
+ - Recommendation: For Phase 1, use monorepo root as Docker context with .dockerignore. Migrate to `turbo prune` if build times become an issue.
+
+3. **Accent color configurability timing**
+ - What we know: D-11 says accent color is configurable per user settings
+ - What's unclear: Settings UI does not exist in Phase 1 (no auth)
+ - Recommendation: Set up the CSS variable infrastructure now (`--accent`), use a default accent color, implement the settings UI in Phase 2 when user accounts exist.
+
+## Environment Availability
+
+| Dependency | Required By | Available | Version | Fallback |
+|------------|------------|-----------|---------|----------|
+| Docker | Container deployment | Yes | 29.5.3 | -- |
+| Docker Compose | Service orchestration | Yes | v5.1.4 | -- |
+| Node.js | Build tooling, runtime | Yes | 24.16.0 | -- |
+| npm/npx | Package execution | Yes | 11.13.0 | -- |
+| pnpm | Workspace management | No (global) | -- | corepack enable && corepack prepare pnpm@9 |
+| Git | Version control | Yes | 2.47.3 | -- |
+| corepack | pnpm activation | Yes | 0.35.0 | -- |
+| Turborepo | Build orchestration | No (global) | -- | pnpm add -D turbo (local) |
+| PostgreSQL client | Dev access to DB | No | -- | Use docker exec or pgAdmin container |
+
+**Missing dependencies with no fallback:** None -- all blocking dependencies are available or installable as project-local devDependencies.
+
+**Missing dependencies with fallback:**
+- pnpm: Use `corepack enable && corepack prepare pnpm@9 --activate` (corepack available)
+- Turborepo: Installed as project devDependency, run via `pnpm turbo` or `npx turbo`
+
+## Validation Architecture
+
+### Test Framework
+
+| Property | Value |
+|----------|-------|
+| Framework | Vitest 3.x (unit/integration), Playwright 1.x (E2E) |
+| Config file | None -- Wave 0 creates vitest.config.ts and playwright.config.ts |
+| Quick run command | `pnpm turbo test --filter=web --filter=api` |
+| Full suite command | `pnpm turbo test && pnpm exec playwright test` |
+
+### Phase Requirements -> Test Map
+
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| INFRA-01 | Docker Compose stack starts and serves app | E2E/smoke | `docker compose up -d && curl localhost:80` | Wave 0 |
+| INFRA-02 | PostgreSQL container healthy | smoke | `docker compose exec db pg_isready` | Wave 0 |
+| INFRA-03 | Network segmentation enforced | integration | `docker compose exec web ping db` (should fail) | Wave 0 |
+| PRTAL-01 | Header renders with branding and user menu | component | `vitest run --filter header` | Wave 0 |
+| PRTAL-04 | Layout adapts to screen sizes | E2E | `playwright test responsive.spec.ts` | Wave 0 |
+| UI-01 | Theme toggle persists preference | component + E2E | `vitest run --filter theme && playwright test theme.spec.ts` | Wave 0 |
+| UI-02 | Language switch between DE/EN | component + E2E | `vitest run --filter i18n && playwright test locale.spec.ts` | Wave 0 |
+| UI-03 | No hardcoded strings (all via t()) | lint/static | `grep -r ">[A-Z]" src/components/ --include="*.tsx"` | Wave 0 |
+
+### Sampling Rate
+- **Per task commit:** `pnpm turbo test --filter=[changed-packages]`
+- **Per wave merge:** `pnpm turbo test && pnpm exec playwright test`
+- **Phase gate:** Full suite green + manual verification of Docker Compose stack
+
+### Wave 0 Gaps
+- [ ] `apps/web/vitest.config.ts` -- Vitest configuration for Next.js
+- [ ] `apps/api/vitest.config.ts` -- Vitest configuration for NestJS
+- [ ] `playwright.config.ts` -- E2E test configuration
+- [ ] `apps/web/tests/` -- Component test directory
+- [ ] `apps/api/test/` -- API test directory
+- [ ] Framework install: `pnpm add -D vitest @testing-library/react @playwright/test`
+
+## Security Domain
+
+### Applicable ASVS Categories
+
+| ASVS Category | Applies | Standard Control |
+|---------------|---------|-----------------|
+| V2 Authentication | No | Phase 2 (not in scope) |
+| V3 Session Management | No | Phase 2 (not in scope) |
+| V4 Access Control | No | Phase 2 (not in scope) |
+| V5 Input Validation | Minimal | No user input in Phase 1 beyond locale/theme toggle |
+| V6 Cryptography | No | No secrets handling beyond env vars |
+| V14 Configuration | Yes | Docker secrets, non-root containers, network isolation |
+
+### Known Threat Patterns for Docker + Next.js + NestJS
+
+| Pattern | STRIDE | Standard Mitigation |
+|---------|--------|---------------------|
+| Container escape via Docker socket | Elevation of Privilege | Mount Docker socket read-only (:ro) in Traefik only |
+| Database port exposed to host | Information Disclosure | internal:true network, no port mapping for db |
+| Sensitive env vars in docker-compose.yml | Information Disclosure | Use .env file (gitignored), Docker secrets for production |
+| Non-root container bypass | Elevation of Privilege | Explicit USER directive in Dockerfile, no sudo |
+| Dependency confusion in monorepo | Tampering | pnpm strict mode, lockfile committed, exact versions |
+
+## Sources
+
+### Primary (HIGH confidence)
+- [Docker Docs: Networking in Compose](https://docs.docker.com/compose/how-tos/networking/) -- network segmentation patterns
+- [Next.js Deployment Docs](https://nextjs.org/docs/app/getting-started/deploying) -- standalone output mode
+- [shadcn/ui Theming](https://ui.shadcn.com/docs/theming) -- CSS variable design token system
+- [next-intl: Without i18n routing](https://next-intl.dev/docs/getting-started/app-router/without-i18n-routing) -- cookie-based locale
+- [Turborepo: Structuring a Repository](https://turborepo.dev/repo/docs/crafting-your-repository/structuring-a-repository) -- monorepo structure
+
+### Secondary (MEDIUM confidence)
+- [Tailwind CSS v4 Next.js Installation](https://tailwindcss.com/docs/installation/framework-guides/nextjs) -- v4 setup steps
+- [Turborepo Configuration Reference](https://turborepo.dev/repo/docs/reference/configuration) -- turbo.json task config
+- [NestJS Docker Production](https://www.tomray.dev/nestjs-docker-production) -- multi-stage build patterns
+- [next-intl GitHub Issue #1334](https://github.com/amannn/next-intl/issues/1334) -- locale switching without routing
+
+### Tertiary (LOW confidence)
+- Docker network segmentation Medium articles -- general patterns confirmed by official docs
+- NestJS 11 standalone blog posts -- patterns verified against NestJS official docs structure
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH -- all packages verified on npm registry with current versions
+- Architecture: HIGH -- patterns from official documentation, Docker segmentation from Docker docs
+- Pitfalls: HIGH -- sourced from project's own PITFALLS.md research and official documentation
+- Code examples: MEDIUM -- synthesized from multiple docs, API details may need minor adjustment
+
+**Research date:** 2026-06-18
+**Valid until:** 2026-07-18 (stable stack, 30-day validity)