# AGENTS.md – Yumder

Diese Datei richtet sich an Coding-Agenten, die an Yumder arbeiten. Sie ergänzt die `README.md` um konventions- und workflowrelevante Details.

## Projektübersicht

Yumder ist eine Next.js-15-Web-App mit serverseitiger SQLite-Datenbank. Die App ist mobile-first und als PWA konzipiert. Kernlogik: Nutzer erstellen oder treten einer Session bei, swipen über Restaurant-Vorschläge und finden per Mehrheit oder Joker ein Restaurant.

## Tech Stack (detailliert)

- **Next.js:** 15.5.21, App Router, React Server Components als Default
- **React:** 19.1.0
- **TypeScript:** 5.6+
- **Styling:** Tailwind CSS 3.4, eigene Farbtokens (`yumder-pink`, `yumder-teal`, `admin*`-Palette)
- **Animationen:** `motion` (framer-motion-Nachfolger)
- **Datenbank:** SQLite via `better-sqlite3`, WAL-Mode aktiviert
- **Auth:** bcrypt für Hashes, otplib für TOTP, serverseitige Sessions in `admin_sessions`
- **Bilder:** Lokaler Cache unter `public/img/pexels-cache/`, verwaltet über `lib/image-resolver.ts` / `lib/image-cache.ts`
- **i18n:** Eigenes Context-basiertes i18n-System in `lib/i18n/`

## Entwicklungs- und Build-Befehle

```bash
# Entwicklungsserver auf Port 3001
npm run dev

# Produktionsbuild
npm run build

# Produktionsserver auf Port 3001
npm start

# Unit-Tests einmalig ausführen
npx vitest run

# Tests im Watch-Modus
npm test

# Cleanup abgelaufener Sessions (manuell oder per Cron)
npm run cleanup
```

## Wichtige Konventionen

### Datenbank

- Die autoritative Schemadefinition liegt in `db/schema.sql`.
- `lib/db.ts` erstellt beim ersten Start automatisch `db/yumder.db` und führt `schema.sql` aus.
- Bei Schemaänderungen:
  1. `db/schema.sql` aktualisieren.
  2. Für bestehende Deployments eine Migration in `migrations/` anlegen.
  3. Niemals die Produktions-DB ungesichert manipulieren.
- Fremdschlüssel sind aktiviert (`PRAGMA foreign_keys = ON`). Viele Tabellen nutzen `ON DELETE CASCADE`.

### Code-Stil

- TypeScript strict ist aktiv (siehe `tsconfig.json`).
- Komponenten sind größtenteils funktionale React-Komponenten.
- Client-Komponenten werden mit `'use client';` markiert.
- Tailwind-Klassen bevorzugt; globale Styles nur in `app/globals.css`.
- Kommentare im Code sind oft auf Deutsch gehalten – bestehenden Stil beibehalten.

### Design-System & Styleguide

Ausführliche Referenz für Menschen: [`docs/styleguide.md`](docs/styleguide.md).

- **Keine hartkodierten Hex-Werte** in neuen Komponenten. Stattdessen die definierten Tailwind-Tokens verwenden.
- **Brand-Farben:** `yumder-pink` (`#FE3C72`) und `yumder-teal` (`#00C2A8`).
- **Admin-Palette:** `adminbg`, `admincard`, `admintext`, `adminmuted`, `adminborder`, `adminsuccess`, `adminwarning`, `admindanger`.
- **Wiederverwendbare CSS-Utilities** aus `app/globals.css` nutzen:
  - `.glass` für Glassmorphism-Cards
  - `.scrollbar-hide`, `.safe-top`, `.safe-bottom`, `.min-h-dvh-safe`
- **Border-Radius** konsistent halten: bevorzugt `rounded-2xl`, `rounded-xl` oder `rounded-full`.
- **Container** orientieren sich an `max-w-6xl mx-auto px-5`.
- **Schriftart:** Standard Tailwind-Sans (`Inter`, `system-ui`); `Pacifico` ist nicht im Projekt vorhanden.
- Die CSS-Klassen `.btn-primary`, `.btn-teal`, `.btn-secondary` existieren nicht; Buttons werden inline per Tailwind gestylt.
- **Keine transluzenten Hintergründe** für Buttons, Bottom-Nav, Karten/Pillen: deckende Tailwind-Farben verwenden (z. B. `bg-neutral-900` statt `bg-neutral-900/60 backdrop-blur-xl`, `bg-yumder-pink` statt `bg-yumder-pink/15`). Ausnahmen: Overlay-Scrims (`bg-black/70` u. ä.), Bild-Verläufe zur Lesbarkeit, die Sprachwahl (`language-switch.tsx`) und der Tutorial-Button („So geht's").

### Match-Screen, Ranking & Kartenreihenfolge

- Der Match-/Joker-Ergebnis-Screen (`components/match-overlay.tsx`, Export `MatchResult`) ist eine **Vollbild-Ansicht** (kein Modal mehr) mit Stempel, Siegerkarte, Runner-up-Liste (Platz 2/3, nur echtes Match ab 3 aktiven Teilnehmern) und fixer Bottom-Bar („Neuen Tisch starten" + „Tisch verlassen"). Beim Joker-Ergebnis statt der Liste ein „Vom Joker erwählt"-Badge + „Ausgewählt aus X unentschiedenen Restaurants" (`matches.pool_count`). Der Lieferando-/Bestell-Button ist aktuell **deaktiviert** (ausgeblendet), die Affiliate-/Tracking-Logik bleibt erhalten.
- Die Runner-ups liefert `GET /api/session/[code]/ranking` (`lib/ranking.ts`); der aktuelle Sieger wird per `?exclude=<restaurantId>` herausgefiltert.
- **Kartenreihenfolge:** Alle Teilnehmer sehen dieselben Karten, aber in unterschiedlicher, personenstabiler Reihenfolge. Der Restaurants-Endpoint (`app/api/session/[code]/restaurants/route.ts`) mischt deterministisch per `?seed=` (Seed = `participantId`, Fallback `anonymousId`). Nur die Reihenfolge wird gemischt, **nicht** die Auswahl – sonst könnten Matches seltener werden, weil 2/3-Ja dieselbe Karte erfordert.
- **Match-Quorum:** Eine Karte matcht erst, wenn **alle aktiven Teilnehmer abgestimmt haben** (yes *oder* no) **und** die Mehrheit steht (2/2 bei 2, ≥2/3 bei ≥3). Damit kann ein schnelles Paar keine Mehrheit erreichen, bevor der Rest die Karte gesehen hat. Der Swipe-Endpoint ruft `evaluateMatch` deshalb nach **jedem** Swipe auf (auch „no"), weil ein „no" die letzte fehlende Stimme liefern kann.
- **Joker-Flow:** Die Joker-Karte (`components/joker-card.tsx`) liegt als Peek-Karte am Ende des Swipe-Stapels. Ist der Stapel leer, kann der Host sie antippen (Animation: Nebel/Zoom/3D-Flip in `components/joker-reveal.tsx`); alle anderen sehen „<Name> befragt gerade das Orakel…". Der Joker darf **erst gezogen werden, wenn alle aktiven Teilnehmer fertig geswipet haben** (harter Server-Gate im Joker-Endpoint via `lib/restaurant-filter.ts#getUnfinishedParticipants`, Antwort 409 mit `unfinished`-Namen).
- **Weitere Karten anfordern:** Host-Aktion über `POST /api/session/[code]/expand` (erhöht `sessions.expand_level`, max 2). Der Restaurants-Endpoint vergrößert damit den Suchradius für ALLE; Clients erkennen die neue Stufe über den Poll (`session.expand_level`) und laden nach, der Joker rutscht wieder unter den Stapel. `matches.pool_count` speichert die Joker-Poolgröße für die „aus X unentschiedenen"-Anzeige bei allen Teilnehmern.
- **Match-Verlauf:** Einträge auf `/matches` (`components/match-history.tsx`) zeigen zusätzlich die Teilnehmer-Kürzel als Avatar-Kreise („A, M, R haben teilgenommen") und den Session-Code (antippbar zum Kopieren). Der Code wird seit dem Tab-Umbau in `addMatchToHistory` mitgespeichert; ältere Einträge ohne Code zeigen ihn einfach nicht.
- **Daten-Löschen:** Die Seite `/delete-data` bleibt eine eigene URL (Play-Store-Pflicht) und ist nur noch aus den Einstellungen (`/settings`) verlinkt – nicht mehr von der Startseite.
- **Begriffe:** In allen Nutzer-Texten heißt eine „Session" jetzt **„Tisch"** (EN: „table"). Code-Identifier, DB-Tabellen und API-Pfade bleiben unverändert (`session`, `/api/session/...`).
- **App-Gefühl:** Home bietet „Weiter swipen" (Resume aus `lib/storage.ts#getActiveSession`). Das Tutorial öffnet nur bei der ersten Session automatisch (`yumder_tutorial_seen`). Tab-Wechsel animiert `components/tab-transition.tsx`, Ladezustand `app/loading.tsx`.

### Datei- und Verzeichnisstruktur

- `app/api/`: API-Route-Handler
- `app/admin/`: Admin-Oberfläche
- `app/(tabs)/`: Haupt-Tabs (`/`, `/matches`, `/settings`) mit geteilter Bottom-Navigation
- `app/session/[code]/`: Öffentliche Teilnehmer-UI
- `components/`: Wiederverwendbare UI-Komponenten
- `components/bottom-nav.tsx`: Bottom-Navigation (Start/Matches/Einstellungen), interaktiv via `layoutId`-Pill
- `components/nav-visibility.tsx`: Context, blendet die Bottom-Navigation temporär aus (z. B. Erstellen-Wizard)
- `lib/`: Geschäftslogik, DB, Auth, i18n, Zeitzonen, Hilfsfunktionen
- `lib/timezone.ts`: Reporting-Zeitzone (Europe/Berlin) und Hilfsfunktionen
- `lib/shuffle.ts`: deterministisches, seeded Mischen der Kartenreihenfolge pro Teilnehmer (FNV-1a + mulberry32 + Fisher-Yates)
- `lib/ranking.ts`: Zwischen-Ranking (Platz 2/3) aus der `swipes`-Tabelle
- `lib/feedback.ts`: Haptik (`navigator.vibrate`) + Synthesizer-Jingle (Web Audio), Toggles im LocalStorage (`yumder_haptics_enabled` / `yumder_sound_enabled`)
- `public/img/`: Statische Bilder und lokaler Pexels-Cache
- `uploads/`: Hochgeladene Dateien (z. B. Sponsoren-Bilder)

## Nicht im Repository

Folgende Dateien/Verzeichnisse sind per `.gitignore` ausgeschlossen und dürfen nicht committed werden:

- `node_modules/`
- `.next/`, `out/`, `dist/`, `build/`
- `.env`, `.env.*` (außer `.env.example`)
- `logs/`
- `*.sqlite`, `*.sqlite3`, `*.db`, `*.db-journal`, `*.db-shm`, `*.db-wal`
- `uploads/`, `public/uploads/`, `storage/`, `backups/`, `public/img/`
- `coverage/`
- `db/yumder.db.bak*`, `db/migrations/`, `*.bak`, `*.ts-bak`

## Tests

- **Test-Runner:** [Vitest](https://vitest.dev/)
- **Test-Dateien:** `lib/__tests__/*.test.ts`
- **In-Memory-DB:** `lib/test-helpers.ts` stellt eine frische SQLite-In-Memory-DB mit `db/schema.sql` für Tests bereit.

Wichtige bestehende Test-Suites:
- `match-logic.test.ts` – Match-Regeln (Quorum: alle müssen abgestimmt haben, 2/3-Mehrheit, Vetos, inaktive Teilnehmer)
- `opening-hours.test.ts` – Öffnungsstatus-Parser
- `session-code.test.ts` – Code-Generierung und erlaubtes Alphabet
- `ranking.test.ts` – Zwischen-Ranking (Sortierung, Veto-/Sieger-Ausschluss)
- `shuffle.test.ts` – seeded Shuffle (Determinismus, Permutation)

Bei Änderungen an kritischer Logik sollten `npm run build` **und** `npx vitest run` erfolgreich durchlaufen, bevor committed wird.

## Commit-Workflow

Wenn ein Task abgeschlossen ist und der Build erfolgreich durchläuft, soll ein Commit vorgeschlagen oder durchgeführt werden:

- Commit-Nachrichten auf Deutsch oder Englisch, konsistent mit der letzten Nachrichtenwahl.
- Kleine, fokussierte Commits bevorzugen.
- Keine sensiblen Daten, `.env`-Dateien oder generierte Datenbanken committen.

## Security / Admin

- Admin-Sessions werden serverseitig in `admin_sessions` verwaltet, nicht als JWT im Client.
- `ADMIN_SESSION_SECRET` muss in Produktion ein langer, zufälliger String sein.
- Admin-Accounts werden aktuell nicht über ein öffentliches UI angelegt.
- Uploadedateien werden über `app/api/uploads/sponsors/[filename]/route.ts` ausgeliefert; hierbei auf Pfad-Traversal achten.

## Zeitzonen

- SQLite speichert alle Zeitstempel als UTC (`datetime('now')`).
- Admin-Reporting verwendet für Diagramme und Zeitfilter die feste Zeitzone `Europe/Berlin` (`lib/timezone.ts`).
- Neue zeitbasierte Auswertungen sollten `REPORTING_TZ` und die Hilfsfunktionen aus `lib/timezone.ts` nutzen, statt `datetime('now', 'localtime')` oder Browser-lokale Zeit.

## Häufige Fallstricke

- `better-sqlite3` ist synchron; bei längeren Queries den Event-Loop nicht blockieren.
- `db/yumder.db` ist lokal und wird bei Neuinstallationen neu angelegt. Für Migrationen auf bestehenden Datenbanken separate SQL-Skripte verwenden.
- Der Pexels-Bildercache wird lokal gespeichert; ohne `PEXELS_API_KEY` funktioniert der Fallback nicht.
- `.env.local` ist für lokale Entwicklung gedacht, `.env` für Produktion. Beide sind im Git ausgeschlossen.

## Kommunikation mit dem Nutzer

- Der Nutzer spricht Deutsch; Antworten sollten auf Deutsch erfolgen.
- Vor größeren Architekturänderungen oder dem Hinzufügen neuer Dependencies kurz Rücksprache halten.
