# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash npm run dev # Vite dev server (default http://localhost:5173) npm run build # Production build → dist/ npm run preview # Serve the built dist/ locally ``` There is **no test runner and no linter** configured — `package.json` only defines `dev`, `build`, `preview`. Don't suggest `npm test`/`npm run lint`. Deployment is a two-stage Docker build (`Dockerfile`): Node builds `dist/`, then it's served by nginx (`nginx.conf`) with SPA fallback (`try_files … /index.html`). ## Environment `VITE_API_URL` (in `.env` / `.env.production`) points at the backend. It's the **only** runtime config — every API call in `src/api/directus.js` reads `import.meta.env.VITE_API_URL`. `.env` is gitignored. ## Architecture React 19 + Vite SPA, **no router package**. There are no DB cards/feed entries fetched directly — the app talks to a custom backend, not Directus directly (see below). **Routing** is `useState`-based in `src/App.jsx`: a `PAGES` map (`feed`/`game`/`pro`/`profil`) swaps the active page component, driven by `BottomNav`. `Game` and `Pro` are placeholders. **Auth gating:** `App.jsx` renders `` unless the user has `username`, `language_native_id`, and `language_target_id`. `AuthContext` (`src/context/AuthContext.jsx`) holds the JWT (localStorage key **`hejyou_token`**), calls `getMe()` on mount, and clears the token on failure. Registration is two steps: `RegisterStep1` (email+password) → `RegisterStep2` (username + native/target language). ### The API client is misnamed `src/api/directus.js` is **not** a Directus client anymore — the module name is kept "aus historischen Gründen". It is a thin fetch wrapper around a **custom backend (snakkimo-API)** exposing `/auth/*` endpoints: | Function | Endpoint | Notes | |---|---|---| | `login` / `registerUser` | `POST /auth/login`, `/auth/register` | return `{ token, userId, needsProfile }` | | `getMe` | `GET /auth/me` | auth check + progress: `total_ep`, `streak_days`, `level`, `ep_into_level`, `ep_to_next_level`, `last_practice_at`; also `language_target_greeting` (Profil-Anrede „Hej, …") | | `checkUsername` | `GET /auth/check-username` | | | `createProfile` | `POST /auth/profile` | username + native/target lang | | `getLanguageOptions` | `GET /auth/languages` | merged with local `LANG_META` (flag + Web Speech code) | | `getFeedPairs` | `GET /auth/feed?lang=&limit=&exclude=` | returns "pairs", the feed unit | | `saveProgress` | `POST /auth/progress` | books EP/streak; returns the **milestone contract** (see below) | | `getStats` | `GET /auth/stats` | Profil-Daten: `daily`/`today`/`totals`/`skills` + `categories[]` (Punkte je Kategorie) | | `getAchievements` | `GET /auth/achievements` | `[{ key, label, icon, unlocked, unlocked_at }]` für die Profil-Sektion | | `setDailyGoal` | `PUT /auth/goal` | Tagesziel (EP/Tag) setzen; Backend klemmt auf 5–500 | `saveProgress` returns `{ total_ep, level, prev_level, streak_days, streak_increased, daily_ep, daily_goal_ep, goal_just_reached, unlocked_achievements }` — `Feed.jsx` leitet daraus die Feier-Momente ab (Level-Up/Streak/Tagesziel/Achievement). Felder degradieren defensiv: fehlen sie (älteres Backend), greifen lokale Fallbacks. `src/pages/Profil.jsx` rendert die Begrüßung (`language_target_greeting`), **führt mit Momentum** (`% bis Level X` + Capability-Satz), zeigt Kategorie-Stufen (`stats.categories` + `categoryTier`), Wochenvergleich, Streak-Status, Erfolge-Grid (`getAchievements`) und einen Sound-Toggle. ### Fortschritts-/Feier-System (Momente) Macht Fortschritt spürbar statt nur zählbar. Bausteine: - **`src/utils/leveling.js`** — spiegelt die Backend-Level-Kurve (`levelForEp`/`levelInfo`, Level 1 bei 20 EP). Backend ist Single Source of Truth; das ist Fallback + %-Anzeige. - **`MilestoneOverlay`** (`components/`) — Vollbild-Feier, getriggert aus der `saveProgress`-Response. Typen: `level` / `streak` (Schwellen 3/7/14/30/50/100/200/365) / `goal` / `achievement`. Konfetti via `utils/confetti.js`. - **`EpFloat`** — „+N EP" schwebt am Bestätigen-Button auf; EP-Badge zählt hoch (`hooks/useCountUp.js`). - **`SessionSummary`** — ersetzt die End-Sackgasse mit Zahlen + Story-Zeilen. - **Combo** + variables Lob/ermutigendes Fehler-Feedback (`utils/praise.js`, dort auch `categoryTier`/`capabilitySentence`). - **`utils/sound.js`** — dezente WebAudio-Belohnung (Mute-Pref in localStorage). - **`utils/streak.js`** + **`utils/streakReminder.js`** — Loss-Aversion-Nudge im Feed („Serie endet in X Std") und lokale Tages-Erinnerung via **`@capacitor/local-notifications`** (kein APNs nötig; nur nativ, web no-op — braucht `npx cap sync ios`). Several content functions (`getWords`, `getQuestions`, `getActiveLearningPair`, `assetUrl`, …) are **stubs** returning empty/null — content endpoints are not built yet. Don't assume they fetch anything. ### Feed = "pairs" → cards `src/pages/Feed.jsx` fetches an array of *pairs* and maps each to one card by its `answer_type`: | `answer_type` | Component | Points (`POINTS` map) | |---|---|---| | `text` | `PairSentenceCard` | 2 | | `yes_no` | `PairYesNoCard` | 2 | | `word` | `PairWordCard` | 3 | | `question` | `PairWordCard` | 3 | On completion, `handleComplete` adds the pair id to a local `done` set (cards are hidden, not removed) and POSTs to `saveProgress`. Target language comes from `user.language_target_short` (fallback `de`). The **`Pair*` cards** (`PairSentenceCard`, `PairYesNoCard`, `PairWordCard`) are the live card components. The other card files (`NewWord*`, `ImagePick/ImageQuiz`, `AudioQuiz`, `LetterOrder`, `SentenceFill`, `LanguageParentCard`) are an **earlier card model** not wired into the current feed — check `Feed.jsx` before assuming a component is in use. Several cards use the browser **Web Speech API** (`SpeechRecognition` / `speechSynthesis`) for the `speak`/`listen` flow; `src/utils/confetti.js` wraps `canvas-confetti` for correct-answer feedback. ## `knowledge/directus_struktur.md` A detailed reference for the **backend data model** (collections, fields, relations) and the original app concept. Useful for understanding the domain and DB schema, but note it predates the custom-backend migration: its "Frontend API-Funktionen" table describes **direct Directus calls that no longer exist** — trust `src/api/directus.js` for the actual frontend contract.