diff --git a/CLAUDE.md b/CLAUDE.md
index fcb7d18..2750d2a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,1295 +1,220 @@
# Mitai Jinkendo – Entwickler-Kontext für Claude Code
## Projekt-Übersicht
-**Mitai Jinkendo** (身体 Jinkendo) ist eine selbst-gehostete PWA für Körper-Tracking (Gewicht, Körperfett, Umfänge, Ernährung, Aktivität) mit KI-Auswertung. Teil der **Jinkendo**-App-Familie (人拳道 – Der menschliche Weg der Kampfkunst).
-
-**Produktfamilie:** mitai · miken · ikigai · shinkan · kenkou (alle unter jinkendo.de)
+**Mitai Jinkendo** (身体 Jinkendo) – selbst-gehostete PWA für Körper-Tracking mit KI-Auswertung.
+Teil der **Jinkendo**-App-Familie (人拳道). Domains: jinkendo.de / .com / .life
## Tech-Stack
-| Komponente | Technologie | Version |
-|-----------|-------------|---------|
-| Frontend | React 18 + Vite + PWA | Node 20 |
-| Backend | FastAPI (Python) | Python 3.12 |
-| Datenbank | PostgreSQL 16 (Alpine) | v9b |
-| Container | Docker + Docker Compose | - |
-| Webserver | nginx (Reverse Proxy) | Alpine |
-| Auth | Token-basiert + bcrypt | - |
-| KI | OpenRouter API (claude-sonnet-4) | - |
+| Komponente | Technologie |
+|-----------|-------------|
+| Frontend | React 18 + Vite + PWA (Node 20) |
+| Backend | FastAPI Python 3.12 |
+| Datenbank | PostgreSQL 16 Alpine |
+| Container | Docker + Docker Compose |
+| Auth | Token-basiert + bcrypt |
+| KI | OpenRouter API (claude-sonnet-4) |
-## Ports
-| Service | Prod | Dev |
-|---------|------|-----|
-| Frontend | 3002 | 3099 |
-| Backend | 8002 | 8099 |
+**Ports:** Prod 3002/8002 · Dev 3099/8099 – nie ändern!
## Verzeichnisstruktur
```
-mitai-jinkendo/
-├── backend/
-│ ├── main.py # FastAPI App Setup + Router Registration (~75 Zeilen)
-│ ├── db.py # PostgreSQL Connection Pool + Helpers
-│ ├── auth.py # Auth Functions (hash, verify, sessions)
-│ ├── models.py # Pydantic Models (11 Models)
-│ ├── routers/ # Modular Endpoint Structure (14 Router)
-│ │ ├── auth.py # Login, Logout, Password Reset (7 Endpoints)
-│ │ ├── profiles.py # Profile CRUD + Current User (7 Endpoints)
-│ │ ├── weight.py # Weight Tracking (5 Endpoints)
-│ │ ├── circumference.py # Body Measurements (4 Endpoints)
-│ │ ├── caliper.py # Skinfold Tracking (4 Endpoints)
-│ │ ├── activity.py # Workout Logging + CSV Import (6 Endpoints)
-│ │ ├── nutrition.py # Nutrition + FDDB Import (4 Endpoints)
-│ │ ├── photos.py # Progress Photos (3 Endpoints)
-│ │ ├── insights.py # AI Analysis + Pipeline (8 Endpoints)
-│ │ ├── prompts.py # AI Prompt Management (2 Endpoints)
-│ │ ├── admin.py # User Management (7 Endpoints)
-│ │ ├── stats.py # Dashboard Stats (1 Endpoint)
-│ │ ├── exportdata.py # CSV/JSON/ZIP Export (3 Endpoints)
-│ │ └── importdata.py # ZIP Import (1 Endpoint)
-│ ├── requirements.txt
-│ └── Dockerfile
-├── frontend/
-│ ├── src/
-│ │ ├── App.jsx # Root, Auth-Gates, Navigation
-│ │ ├── app.css # Globale Styles, CSS-Variablen
-│ │ ├── context/
-│ │ │ ├── AuthContext.jsx # Session, Login, Logout
-│ │ │ └── ProfileContext.jsx # Aktives Profil
-│ │ ├── pages/ # Alle Screens
-│ │ └── utils/
-│ │ ├── api.js # Alle API-Calls (injiziert Token automatisch)
-│ │ ├── calc.js # Körperfett-Formeln
-│ │ ├── interpret.js # Regelbasierte Auswertung
-│ │ ├── Markdown.jsx # Eigener MD-Renderer
-│ │ └── guideData.js # Messanleitungen
-│ └── public/ # Icons (Jinkendo Ensō-Logo)
-├── .gitea/workflows/
-│ ├── deploy-prod.yml # Auto-Deploy bei Push auf main
-│ ├── deploy-dev.yml # Auto-Deploy bei Push auf develop
-│ └── test.yml # Build-Test bei jedem Push
-├── docker-compose.yml # Produktion (Ports 3002/8002)
-├── docker-compose.dev-env.yml # Development (Ports 3099/8099)
-└── CLAUDE.md # Diese Datei
+backend/
+├── main.py # App-Setup + Router-Registration (~75 Zeilen)
+├── db.py # PostgreSQL Connection Pool
+├── auth.py # Hash, Verify, Sessions
+├── models.py # Pydantic Models
+└── routers/ # 14 Router-Module
+ auth · profiles · weight · circumference · caliper
+ activity · nutrition · photos · insights · prompts
+ admin · stats · exportdata · importdata
+
+frontend/src/
+├── App.jsx # Root, Auth-Gates, Navigation
+├── app.css # CSS-Variablen + globale Styles
+├── context/ # AuthContext · ProfileContext
+├── pages/ # Alle Screens
+└── utils/
+ api.js # ALLE API-Calls – Token automatisch injiziert
+ calc.js · interpret.js · Markdown.jsx · guideData.js
+
+.claude/
+├── settings.json
+├── commands/ # /deploy /merge-to-prod /refactor /ui-responsive etc.
+└── docs/
+ ├── BACKLOG.md
+ ├── functional/ # Fachliche Specs (TRAINING_TYPES, AI_PROMPTS, RESPONSIVE_UI)
+ └── technical/ # MEMBERSHIP_SYSTEM.md
```
-## Aktuelle Version: v9c-dev (März 2026)
-
-### Was implementiert ist:
-- ✅ Multi-User mit E-Mail + Passwort Login (bcrypt)
-- ✅ Auth-Middleware auf ALLE Endpoints (60+ Endpoints geschützt)
-- ✅ Rate Limiting (Login: 5/min, Reset: 3/min)
-- ✅ CORS konfigurierbar via ALLOWED_ORIGINS in .env
-- ✅ Admin/User Rollen, KI-Limits (simple daily limits), Export-Berechtigungen
-- ✅ Gewicht, Umfänge, Caliper (4 Formeln), Ernährung, Aktivität
-- ✅ FDDB CSV-Import (Ernährung), Apple Health CSV-Import (Aktivität)
-- ✅ KI-Analyse: 6 Einzel-Prompts + 3-stufige Pipeline (parallel)
-- ✅ **KI-Analyse Historisierung**: Alle Analysen werden gespeichert (nicht überschrieben)
-- ✅ **Pipeline korrekt**: Speichert unter scope='pipeline', erscheint nur 1x in UI
-- ✅ Konfigurierbare Prompts mit Template-Variablen (Admin kann bearbeiten)
-- ✅ Verlauf mit 5 Tabs + Zeitraumfilter + KI pro Sektion
-- ✅ Dashboard mit Kennzahlen, Zielfortschritt, Combo-Chart
-- ✅ Assistent-Modus (Schritt-für-Schritt Messung)
-- ✅ PWA (iPhone Home Screen), Jinkendo Ensō-Logo
-- ✅ E-Mail (SMTP) für Password-Recovery
-- ✅ Admin-Panel: User verwalten, KI-Limits, E-Mail-Test, PIN/Email setzen
-- ✅ Multi-Environment: Prod (mitai.jinkendo.de) + Dev (dev.mitai.jinkendo.de)
-- ✅ Gitea CI/CD mit Auto-Deploy auf Raspberry Pi 5
-- ✅ PostgreSQL 16 Migration (vollständig von SQLite migriert)
-- ✅ Export: CSV, JSON, ZIP (mit Fotos)
-- ✅ Automatische SQLite→PostgreSQL Migration bei Container-Start
-- ✅ **Modulare Backend-Architektur**: 14 Router-Module, main.py von 1878→75 Zeilen (-96%)
-
-### Aktuelles KI-Limit-System (Simple):
-```sql
--- Tägliche Limits pro Profil
-profiles.ai_enabled BOOLEAN -- KI an/aus
-profiles.ai_limit_day INTEGER -- Tägliches Limit (NULL = unbegrenzt)
-ai_usage (profile_id, date, count) -- Täglicher Counter
-
--- Funktionen in routers/insights.py:
-check_ai_limit(pid) -- Prüft Limit vor KI-Call
-inc_ai_usage(pid) -- Inkrementiert Counter nach Call
-```
-
-### Feature-Roadmap & Dokumentation
-
-> **Für Claude Code – Dokumentations-Struktur:**
->
-> ```
-> .claude/docs/
-> ├── BACKLOG.md ← Vollständiges Feature-Backlog (Übersicht)
-> ├── functional/ ← Fachliche Anforderungen (was soll es können?)
-> │ ├── SLEEP_MODULE.md ← v9d Schlaf
-> │ ├── TRAINING_TYPES.md ← v9d Trainingstypen + HF
-> │ ├── GOALS_VITALS.md ← v9e Ziele + Vitalwerte
-> │ ├── AI_PROMPTS.md ← v9f KI-Prompt Flexibilisierung
-> │ └── MEDITATION.md ← v9g Meditation + Selbstwahrnehmung
-> └── technical/ ← Technische Implementierung (wie wird es gebaut?)
-> └── MEMBERSHIP_SYSTEM.md ← v9c kombiniert (fachlich + technisch, bereits vorhanden)
-> ```
->
-> **Workflow:**
-> 1. Fachliche Beschreibung (`functional/`) prüfen und freigeben
-> 2. Technischen Plan aus Fachlichem ableiten (`technical/`)
-> 3. Implementierung starten
-
-### Aktuelle Version: v9c – Membership & Subscription
-📚 **Dokumentation:** `.claude/docs/technical/MEMBERSHIP_SYSTEM.md`
-*(kombinierte fachliche + technische Beschreibung)*
-
-**Offen:**
-- 🔲 Feature-Enforcement-System (Redesign nach Rollback 20.03.2026)
-- 🔲 Selbst-Registrierung mit E-Mail-Verifizierung
-- 🔲 Trial-System UI (Countdown-Banner)
-
-### Nächste Versionen
-| Version | Feature | Fachlich | Technisch |
-|---------|---------|----------|-----------|
-| v9d | Schlaf-Modul + Trainingstypen + HF | `functional/SLEEP_MODULE.md` | wird erstellt |
-| v9d | Trainingstypen + HF + Ruhepuls | `functional/TRAINING_TYPES.md` ✅ | wird erstellt |
-| v9d | Ruhepuls + HF-Zonen + VO2Max | `functional/TRAINING_TYPES.md` | wird erstellt |
-| v9e | Primärziele + Vitalwerte | `functional/GOALS_VITALS.md` | wird erstellt |
-| v9f | KI-Prompt Flexibilisierung | `functional/AI_PROMPTS.md` ✅ | wird erstellt |
-| v9g | Meditation + Selbstwahrnehmung | `functional/MEDITATION.md` | wird erstellt |
-| v9h | Fitness-Connectoren + Stripe | wird erstellt | wird erstellt |
-
-### Responsive UI (parallel)
-📚 **Command:** `/ui-responsive` | **Dok:** `.claude/docs/functional/RESPONSIVE_UI.md`
-
-
-## Was in v9c kommt: Subscription & Coupon Management System
-📚 **Detail-Dokumentation:** `.claude/docs/MEMBERSHIP_SYSTEM.md` – beim Implementieren zuerst lesen!
-**Phase 1 (DB-Schema): ✅ DONE**
-**Phase 2 (Backend API): ✅ DONE**
-**Phase 3 (Admin Frontend): ✅ DONE**
-**Phase 4 (Feature Enforcement): ⚠️ DEAKTIVIERT** (Rollback am 20.03.2026 - Bugs)
-
-**Core Features (Backend & Admin-UI komplett):**
-- ✅ DB-Schema (11 neue Tabellen, Feature-Registry Pattern)
-- ✅ Flexibles Tier-System (free/basic/premium/selfhosted) - Admin-editierbar via API
-- ✅ **Coupon-System** (3 Typen: single_use, period, wellpass)
-- ✅ Coupon-Stacking-Logik (Pause + Resume bei Wellpass-Override)
-- ✅ Access-Grant-System (zeitlich begrenzte Zugriffe mit Quelle-Tracking)
-- ✅ User-Activity-Log (JSONB details)
-- ✅ User-Stats (Aggregierte Statistiken)
-- ✅ Individuelle User-Restrictions (Admin kann Limits pro User setzen)
-- ✅ 7 neue Router, 30+ neue Endpoints (subscription, coupons, features, tiers, tier-limits, user-restrictions, access-grants)
-
-**Admin-Frontend (vollständig implementiert):**
-- ✅ **AdminFeaturesPage** - Feature-Konfiguration (sortierung, reset_period, limits, visibility)
-- ✅ **AdminTiersPage** - Tier-Verwaltung (CRUD, pricing monthly/yearly)
-- ✅ **AdminTierLimitsPage** - Matrix-Editor (Tier x Feature, responsive mobile/desktop views)
-- ✅ **AdminCouponsPage** - Coupon-Manager (CRUD, 3 Typen, auto-generate codes, redemption history)
-- ✅ **AdminUserRestrictionsPage** - User-Override-System (effektive Werte, auto-remove redundant overrides)
-- ✅ **SubscriptionPage** - User Subscription-Info + Coupon-Einlösung (tier badge, limits, usage progress bars)
-- ✅ Alle Routes in App.jsx registriert
-
-**⚠️ Feature Enforcement - ROLLBACK (20.03.2026):**
-- Initial implementation broke core functionality (analysis history, export visibility, counters)
-- Complete rollback to working state (commit 4fcde4a)
-- Simple AI limit system (ai_enabled, ai_limit_day) now active
-- v9c backend/admin UI remains functional but NOT enforcing limits
-- Needs complete reimplementation with proper testing before re-enabling
-
-**Noch NICHT implementiert:**
-- 🔲 Feature-Enforcement-System (needs redesign)
-- 🔲 Selbst-Registrierung mit E-Mail-Verifizierung
-- 🔲 Trial-System UI (Countdown-Banner)
-- 🔲 App-Settings Admin-Panel (globale Konfiguration)
-
-**📚 Vollständige v9c Dokumentation:**
-Siehe `/docs/MEMBERSHIP_SYSTEM.md` für:
-- Vollständige Architektur-Dokumentation
-- Datenbank-Schema Details
-- API-Endpoints Übersicht
-- Design-Entscheidungen und Rationale
-- Lessons Learned vom Feature-Enforcement-Rollback
-
-**E-Mail Templates (v9c):**
-- 🔲 Registrierung + E-Mail-Verifizierung
-- 🔲 Einladungslink
-- 🔲 Passwort-Reset (bereits vorhanden)
-
-**Spätere Features (v9d/v9e):**
-- 🔲 Bonus-System (Login-Streaks → Punkte → Geschenk-Coupons)
-- 🔲 Trial-Reminder-E-Mails (3 Tage vor Ablauf)
-- 🔲 Monatliches Nutzungs-Summary per E-Mail
-- 🔲 Self-Service Upgrade (Stripe-Integration)
-- 🔲 Partner-Verwaltung (Wellpass, Hansefit, etc.)
-- 🔲 Admin-Benachrichtigungen (neue Registrierungen, etc.)
-
-### Was in v9d kommt: Schlaf + Sport-Vertiefung
-📚 **Detail-Dokumentation:** `.claude/docs/SLEEP_MODULE.md` – wird erstellt wenn v9d startet
-- 🔲 **Schlaf-Modul** – Einschlafzeit, Aufwachzeit, Qualität (1-5), Schlafdauer-Trend
-- 🔲 Schlaf-Import aus Apple Health CSV
-- 🔲 Trainingstypen-Kategorisierung (Cardio, Kraft, Schnellkraft, Mobility, HIIT, Erholung)
-- 🔲 Ruhetage als bewusste Einheit erfassen
-- 🔲 Herzfrequenz-Ruhepuls erfassen + Trend
-- 🔲 HF-Zonen definieren (5 Zonen)
-- 🔲 VO2Max-Schätzung
-
-### Was in v9e kommt: Ziele & Vitalwerte
-📚 **Detail-Dokumentation:** `.claude/docs/GOALS_VITALS.md` – wird erstellt wenn v9e startet
-- 🔲 Primärziele (Gewichtsabnahme, Muskelaufbau, Kondition, Gesundheit, Wettkampf)
-- 🔲 Ziel-spezifische Dashboard-Ansicht
-- 🔲 Vitalwerte: Blutdruck, SpO2, HRV
-- 🔲 Hydration-Tracking
-- 🔲 Sportartprofile (Laufen, Radfahren, Kampfsport, Yoga, etc.)
-
-### Was in v9f kommt: KI-Prompt Flexibilisierung
-📚 **Detail-Dokumentation:** `.claude/docs/AI_PROMPTS.md` – wird erstellt wenn v9f startet
-- 🔲 Prompt-Bibliothek mit Kategorien (Körper, Ernährung, Training, Schlaf, Mental)
-- 🔲 Platzhalter-Browser (kategorisiert, mit Beschreibung + Beispielwert)
-- 🔲 Prompt-Vorschau mit echten Daten
-- 🔲 Pipeline konfigurierbar (welche Module, Gewichtung, Zeitraum je Modul)
-- 🔲 Mehrere Pipeline-Konfigurationen speichern
-
-### Was in v9g kommt: Meditation & Selbstwahrnehmung
-📚 **Detail-Dokumentation:** `.claude/docs/MEDITATION.md` – wird erstellt wenn v9g startet
-- 🔲 Täglicher Check-in (Energie, Stimmung, Stress 1-5)
-- 🔲 Meditationssessions erfassen (Dauer, Art)
-- 🔲 Streak-Tracking
-- 🔲 Selbstwahrnehmungs-Journal (Freitext)
-- 🔲 Korrelations-Analysen (Schlaf ↔ Ruhepuls ↔ Leistung ↔ Stimmung)
-- 🔲 → Basis für `miken.jinkendo.de` (eigene App)
-
-### Was in v9h kommt: Fitness-Connectoren & Gamification
-- 🔲 OAuth2-Grundgerüst
-- 🔲 Strava Connector
-- 🔲 Withings Connector (Waage)
-- 🔲 Garmin Connector
-- 🔲 Bonus-System (Streaks → Punkte → Coupons)
-- 🔲 Stripe-Integration
-
-### Responsive UI (parallel zu Features)
-- 🔲 Desktop: Sidebar Navigation + Content volle Breite
-- 🔲 Tablet: 2-spaltige Cards
-- 🔲 Mobile: wie jetzt (Bottom Navigation)
-- 🔲 Siehe `/commands/ui-responsive.md`
-
-### Detaillierte Feature-Dokumentation
-Liegt in `.claude/docs/` – nur laden wenn aktiv implementiert:
-- `MEMBERSHIP_SYSTEM.md` – v9c Subscription-System (aktuell aktiv)
-- `SLEEP_MODULE.md` – v9d Schlaf-Modul (wenn v9d startet)
-- `AI_PROMPTS.md` – v9f KI-Prompt Flexibilisierung
-
----
-
-## v9c Architektur-Details: Subscription & Coupon System
-
-### Datenbank-Schema (Neue Tabellen)
-
-#### **app_settings** - Globale Konfiguration
-```sql
-key, value, value_type, description, updated_at, updated_by
-
-Beispiele:
-- trial_days: 14
-- trial_behavior: 'downgrade' | 'lock'
-- allow_registration: true/false
-- default_tier_trial: 'premium'
-- gift_coupons_per_month: 3
-```
-
-#### **tiers** - Tier-Konfiguration (vereinfacht)
-```sql
-id, slug, name, description, sort_order, active
-
-Initial Tiers:
-- free, basic, premium, selfhosted
-
-Limits sind jetzt in tier_limits Tabelle (siehe unten)!
-```
-
-#### **features** - Feature-Registry (alle limitierbaren Features)
-```sql
-id, slug, name, category, description, unit
-default_limit (NULL = unbegrenzt)
-reset_period ('monthly' | 'daily' | 'never')
-visible_in_admin, sort_order, active
-
-Initial Features:
-- weight_entries: Gewichtseinträge, default: 30, never
-- circumference_entries: Umfangsmessungen, default: 30, never
-- caliper_entries: Caliper-Messungen, default: 30, never
-- nutrition_entries: Ernährungseinträge, default: 30, never
-- activity_entries: Aktivitäten, default: 30, never
-- photos: Progress-Fotos, default: 5, never
-- ai_calls: KI-Analysen, default: 0, monthly
-- ai_pipeline: KI-Pipeline, default: 0, monthly
-- csv_import: CSV-Importe, default: 0, monthly
-- data_export: Daten-Exporte, default: 0, monthly
-- fitness_connectors: Fitness-Connectoren, default: 0, never
-
-Neue Features einfach per INSERT hinzufügen - kein Schema-Change!
-```
-
-#### **tier_limits** - Limits pro Tier + Feature
-```sql
-id, tier_slug, feature_slug, limit_value, enabled
-
-Beispiel Free Tier:
-- ('free', 'weight_entries', 30, true)
-- ('free', 'ai_calls', 0, false) -- KI deaktiviert
-- ('free', 'data_export', 0, false)
-
-Beispiel Premium:
-- ('premium', 'weight_entries', NULL, true) -- unbegrenzt
-- ('premium', 'ai_calls', NULL, true) -- unbegrenzt
-
-Admin kann in UI Matrix bearbeiten: Tier x Feature
-```
-
-#### **user_feature_restrictions** - Individuelle User-Limits
-```sql
-id, profile_id, feature_slug, limit_value, enabled
-reason, set_by (admin_id)
-
-Überschreibt Tier-Limits für spezifische User.
-Admin kann jeden User individuell einschränken oder erweitern.
-```
-
-#### **user_feature_usage** - Nutzungs-Tracking
-```sql
-id, profile_id, feature_slug, period_start, usage_count, last_used
-
-Für Features mit reset_period (z.B. ai_calls monthly).
-Wird automatisch zurückgesetzt am Monatsanfang.
-```
-
-#### **coupons** - Coupon-Verwaltung
-```sql
-code, type ('single_use' | 'multi_use_period' | 'gift')
-valid_from, valid_until, grants_tier, duration_days
-max_redemptions, current_redemptions
-created_by, created_for, notes, active
-
-Beispiel Single-Use:
- Code: FRIEND-GIFT-XYZ, 30 Tage Premium, max 1x
-
-Beispiel Multi-Use Period:
- Code: WELLPASS-2026-03, gültig 01.03-31.03, unbegrenzte Einlösungen
-```
-
-#### **coupon_redemptions** - Einlösungs-Historie
-```sql
-coupon_id, profile_id, redeemed_at, access_grant_id
-UNIQUE(coupon_id, profile_id) - User kann denselben Coupon nur 1x einlösen
-```
-
-#### **access_grants** - Zeitlich begrenzte Zugriffe
-```sql
-profile_id, granted_tier, valid_from, valid_until
-source ('coupon' | 'admin_grant' | 'trial')
-active (false wenn pausiert durch Wellpass-Override)
-paused_at, paused_by (access_grant_id das pausiert hat)
-
-Stacking-Logik:
-- Multi-Use Period Coupon (Wellpass): pausiert andere grants
-- Single-Use Coupon: stackt zeitlich (Resume nach Ablauf)
-```
-
-#### **user_activity_log** - Aktivitäts-Tracking
-```sql
-profile_id, activity_type, details (JSONB), ip_address, user_agent, created
-
-Activity Types:
-- login, password_change, email_change, coupon_redeemed
-- tier_change, export, ai_analysis, registration
-```
-
-#### **user_stats** - Aggregierte Statistiken
-```sql
-profile_id, first_login, last_login, total_logins
-current_streak_days, longest_streak_days, last_streak_date
-total_weight_entries, total_ai_analyses, total_exports
-bonus_points (später), gift_coupons_available (später)
-```
-
-#### **profiles** - Erweiterte Spalten
-```sql
-tier, tier_locked (Admin kann Tier festnageln)
-trial_ends_at, email_verified, email_verify_token
-invited_by, contract_type, contract_valid_until
-stripe_customer_id (vorbereitet für v9d)
-```
-
----
-
-### Backend-Erweiterungen
-
-#### Neue Router (v9c):
-```
-routers/tiers.py - Tier-Verwaltung (List, Edit, Create)
-routers/features.py - Feature-Registry (List, Add, Edit, Delete) ⭐ NEU
-routers/tier_limits.py - Tier-Limits-Matrix (Admin bearbeitet Tier x Feature) ⭐ NEU
-routers/coupons.py - Coupon-System (Redeem, Admin CRUD)
-routers/access_grants.py - Zugriffs-Verwaltung (Current, Grant, Revoke)
-routers/user_admin.py - Erweiterte User-Verwaltung (Activity, Stats, Feature-Restrictions)
-routers/settings.py - App-Einstellungen (Admin)
-routers/registration.py - Registrierung + E-Mail-Verifizierung
-```
-
-#### Neue Middleware:
-```python
-check_feature_access(profile_id, feature_slug, action='use')
- """
- Zentrale Feature-Access-Prüfung.
- Hierarchie:
- 1. User-Restriction (höchste Priorität)
- 2. Tier-Limit
- 3. Feature-Default
-
- Returns: {'allowed': bool, 'limit': int, 'used': int, 'remaining': int, 'reason': str}
- """
-
-increment_feature_usage(profile_id, feature_slug)
- """
- Inkrementiert Nutzungszähler.
- Berücksichtigt reset_period (monthly, daily, never).
- """
-
-log_activity(profile_id, activity_type, details=None)
- """
- Loggt User-Aktivitäten in user_activity_log.
- """
-```
-
-#### Hintergrund-Tasks (Cron):
-```python
-check_expired_access() # Täglich 00:00 - Trial/Coupon-Ablauf prüfen
-reset_monthly_limits() # 1. jeden Monats - AI-Calls zurücksetzen
-update_user_streaks() # Täglich 23:59 - Login-Streaks aktualisieren
-```
-
----
-
-### Zugriffs-Hierarchie
-
-```
-Effektiver Tier wird ermittelt durch (Priorität absteigend):
-1. Admin-Override (tier_locked=true) → nutzt profiles.tier
-2. Aktiver access_grant (nicht pausiert, valid_until > now)
-3. Trial (trial_ends_at > now)
-4. Base tier (profiles.tier)
-
-Wellpass-Override-Logik:
-- User hat Single-Use Coupon (20 Tage verbleibend)
-- User löst Wellpass-Coupon ein (gültig bis 31.03)
-- Single-Use access_grant wird pausiert (active=false, paused_by=wellpass_grant_id)
-- Nach Wellpass-Ablauf: Single-Use wird reaktiviert (noch 20 Tage)
-```
-
----
-
-### Tier-Limits & Feature-Gates
-
-**Daten-Sichtbarkeit bei Downgrade:**
-- Frontend: Buttons/Features ausblenden (Export, KI, Import)
-- Backend: API limitiert Rückgabe (z.B. nur letzte 30 Gewichtseinträge bei free)
-- Daten bleiben erhalten, werden nur versteckt
-- Bei Upgrade wieder sichtbar
-
-**Feature-Checks:**
-```python
-# Beispiel: Gewicht-Eintrag erstellen
-@check_feature_limit('weight', 'create')
-def create_weight_entry():
- # Prüft: Hat User max_weight_entries erreicht?
- # Falls ja: HTTPException 403 "Limit erreicht - Upgrade erforderlich"
-```
-
----
-
-### Frontend-Erweiterungen
-
-#### Neue Seiten:
-```
-RegisterPage.jsx - Registrierung (Name, E-Mail, Passwort)
-VerifyEmailPage.jsx - E-Mail-Verifizierung (Token aus URL)
-RedeemCouponPage.jsx - Coupon-Eingabe (oder Modal)
-AdminCouponsPage.jsx - Coupon-Verwaltung (Admin)
-AdminTiersPage.jsx - Tier-Verwaltung (CRUD) (Admin)
-AdminFeaturesPage.jsx - Feature-Registry (List, Add, Edit) ⭐ NEU
-AdminTierLimitsPage.jsx - Tier x Feature Matrix (bearbeiten) ⭐ NEU
-AdminUserRestrictionsPage.jsx - User-spezifische Limits (bearbeiten) ⭐ NEU
-AdminSettingsPage.jsx - App-Einstellungen (Admin)
-```
-
-#### Neue Komponenten:
-```jsx
- // Tier-Anzeige mit Icon
-...> // Feature-basierte Sichtbarkeit ⭐ GEÄNDERT
- // "Trial endet in 5 Tagen" Banner
- // Coupon-Eingabefeld
- // User-Activity-Log
- // "5/10 verwendet" Anzeige ⭐ NEU
- // Matrix-Editor ⭐ NEU
- // Login-Streak (später)
-```
-
-#### Erweiterte Admin-Seiten:
-```
-AdminUsersPage.jsx erweitert um:
-- Activity-Log Button → zeigt user_activity_log
-- Stats Button → zeigt user_stats
-- Access-Grants Button → zeigt aktive/abgelaufene Zugriffe
-- Feature-Restrictions Button → individuelle Feature-Limits setzen ⭐ GEÄNDERT
-- Grant Access Button → manuell Tier-Zugriff gewähren
-- Usage-Overview → zeigt user_feature_usage für alle Features ⭐ NEU
-```
-
-#### Admin-Interface-Details:
-
-**AdminFeaturesPage.jsx** - Feature-Registry verwalten
-```jsx
-// Alle Features auflisten + neue hinzufügen
-
- {features.map(f => (
-
- {f.name}
- {f.category}
- {f.unit}
- {f.reset_period}
- {f.default_limit ?? '∞'}
-
-
-
-
-
- ))}
-
-
-```
-
-**AdminTierLimitsPage.jsx** - Matrix-Editor
-```jsx
-// Matrix-View: Tiers (Spalten) x Features (Zeilen)
-
-
-
- | Feature |
- Free |
- Basic |
- Premium |
- Selfhosted |
-
-
-
-
- | Gewichtseinträge |
- |
- |
- |
- |
-
-
- | KI-Analysen/Monat |
- 0 |
- |
- ∞ |
- ∞ |
-
-
-
-```
-
-**AdminUserRestrictionsPage.jsx** - Individuelle User-Limits
-```jsx
-
-
-
-
- {features.map(f => (
-
- {f.name}
- {getTierLimit(user.tier, f.slug)}
- {getUsage(user.id, f.slug)}
-
-
-
-
-
- ))}
-
-```
-
----
-
-### E-Mail Templates (v9c)
-
-**1. Registrierung + E-Mail-Verifizierung:**
-```
-Betreff: Willkommen bei Mitai Jinkendo - E-Mail bestätigen
-
-Hallo {name},
-
-vielen Dank für deine Registrierung bei Mitai Jinkendo!
-
-Bitte bestätige deine E-Mail-Adresse:
-{app_url}/verify-email?token={token}
-
-Nach der Bestätigung startet dein 14-Tage Premium Trial automatisch.
-
-Viel Erfolg bei deinem Training!
-Dein Mitai Jinkendo Team
-```
-
-**2. Einladungslink (Admin):**
-```
-Betreff: Du wurdest zu Mitai Jinkendo eingeladen
-
-Hallo,
-
-{admin_name} hat dich zu Mitai Jinkendo eingeladen!
-
-Registriere dich jetzt:
-{app_url}/register?invite={token}
-
-Du erhältst {tier} Zugriff.
-
-Dein Mitai Jinkendo Team
-```
-
----
-
-### Migrations-Reihenfolge (v9c)
-
-```
-Phase 1 - DB Schema:
-1. app_settings Tabelle + Initialdaten
-2. tiers Tabelle + 4 Standard-Tiers
-3. coupons Tabelle
-4. coupon_redemptions Tabelle
-5. access_grants Tabelle
-6. user_activity_log Tabelle
-7. user_stats Tabelle
-8. user_restrictions Tabelle
-9. profiles Spalten erweitern
-10. Bestehende Profile migrieren (Lars → tier='selfhosted', email_verified=true)
-
-Phase 2 - Backend:
-11. Tier-System Router + Middleware
-12. Registrierungs-Flow
-13. Coupon-System
-14. Access-Grant-Logik
-15. Activity-Logging
-16. Erweiterte Admin-Endpoints
-
-Phase 3 - Frontend:
-17. Registrierungs-Seiten
-18. Tier-System UI-Komponenten
-19. Coupon-Eingabe
-20. Erweiterte Admin-Panels
-21. Feature-Gates in bestehende Seiten einbauen
-
-Phase 4 - Cron-Jobs:
-22. Expired-Access-Checker
-23. Monthly-Reset
-24. Streak-Updater
-
-Phase 5 - Testing & Deployment:
-25. Dev-Testing
-26. Prod-Deployment
-```
-
----
+## Aktuelle Version: v9c-dev
+
+### Implementiert ✅
+- Login (E-Mail + bcrypt), Auth-Middleware alle Endpoints, Rate Limiting
+- Gewicht · Umfänge · Caliper · Ernährung · Aktivität + CSV-Imports
+- KI-Analyse: 6 Prompts + 3-stufige Pipeline
+- Dashboard · Verlauf · Assistent-Modus · Fotos
+- Admin-Panel · E-Mail (SMTP) · PWA
+- PostgreSQL 16 · Modulare Router-Architektur
+- Membership-System: Tiers · Coupons · Access-Grants · Admin-UI
+- Export: CSV · JSON · ZIP
+
+### Offen v9c 🔲
+- Feature-Enforcement (Rollback 20.03.2026 – Redesign nötig)
+- Selbst-Registrierung + E-Mail-Verifizierung
+- Trial-System UI
+
+📚 Details: `.claude/docs/technical/MEMBERSHIP_SYSTEM.md`
+
+## Feature-Roadmap
+
+> Vollständiges Backlog: `.claude/docs/BACKLOG.md`
+> Beim Implementieren: verlinkte Dok-Datei zuerst lesen!
+
+| Version | Feature | Dokumentation |
+|---------|---------|---------------|
+| v9c | Membership (aktiv) | `technical/MEMBERSHIP_SYSTEM.md` ✅ |
+| v9d | Schlaf-Modul | `functional/SLEEP_MODULE.md` (ausstehend) |
+| v9d | Trainingstypen + HF | `functional/TRAINING_TYPES.md` ✅ |
+| v9e | Ziele + Vitalwerte | `functional/GOALS_VITALS.md` (ausstehend) |
+| v9f | KI-Prompt Flexibilisierung | `functional/AI_PROMPTS.md` ✅ |
+| v9g | Meditation + Selbstwahrnehmung | `functional/MEDITATION.md` (ausstehend) |
+| v9h | Connectoren + Stripe | ausstehend |
+| — | Responsive UI | `functional/RESPONSIVE_UI.md` ✅ |
## Deployment
-### Infrastruktur
```
-Internet → privat.stommer.com (Fritz!Box DynDNS)
- → Synology NAS (Reverse Proxy + Let's Encrypt)
- → Raspberry Pi 5 (192.168.2.49, Docker)
+Internet → Fritz!Box (privat.stommer.com) → Synology NAS → Raspberry Pi 5 (192.168.2.49)
+
+Git Workflow:
+ develop → Auto-Deploy → dev.mitai.jinkendo.de (bodytrack-dev/, Port 3099/8099)
+ main → Auto-Deploy → mitai.jinkendo.de (bodytrack/, Port 3002/8002)
+
+Gitea: http://192.168.2.144:3000/Lars/mitai-jinkendo
+Runner: Raspberry Pi (/home/lars/gitea-runner/)
+
+Manuell:
+ cd /home/lars/docker/bodytrack[-dev]
+ docker compose -f docker-compose[.dev-env].yml build --no-cache && up -d
```
-### Git Workflow
+## Datenbank-Schema (PostgreSQL 16)
```
-develop branch → Auto-Deploy → dev.mitai.jinkendo.de (Port 3099/8099)
-main branch → Auto-Deploy → mitai.jinkendo.de (Port 3002/8002)
+profiles – Nutzer (role, pin_hash/bcrypt, email, tier)
+sessions – Auth-Tokens
+weight_log – Gewicht (profile_id, date, weight)
+circumference_log – 8 Umfangspunkte
+caliper_log – Hautfalten, 4 Methoden
+nutrition_log – Kalorien + Makros
+activity_log – Training
+photos – Progress-Fotos
+ai_insights – KI-Auswertungen (scope = prompt-slug)
+ai_prompts – Konfigurierbare Prompts (11 Standard)
+ai_usage – KI-Calls pro Tag pro Profil
+
+v9c neu (Membership):
+subscriptions · coupons · coupon_redemptions · features
+tier_limits · user_restrictions · access_grants · user_activity_log
+
+Schema-Datei: backend/schema.sql
```
-### Deployment-Befehle (manuell falls nötig)
-```bash
-# Prod
-cd /home/lars/docker/bodytrack
-docker compose -f docker-compose.yml build --no-cache
-docker compose -f docker-compose.yml up -d
-
-# Dev
-cd /home/lars/docker/bodytrack-dev
-docker compose -f docker-compose.dev-env.yml build --no-cache
-docker compose -f docker-compose.dev-env.yml up -d
+## API & Auth
```
+Alle Endpoints: /api/...
+Auth-Header: X-Auth-Token:
+Fehler: {"detail": "Fehlermeldung"}
+Rate Limit: HTTP 429
-## Datenbank-Schema (PostgreSQL 16, v9b)
-### Wichtige Tabellen:
-- `profiles` – Nutzer (role, pin_hash/bcrypt, email, auth_type, ai_enabled, tier)
-- `sessions` – Auth-Tokens mit Ablaufdatum
-- `weight_log` – Gewichtseinträge (profile_id, date, weight)
-- `circumference_log` – 8 Umfangspunkte
-- `caliper_log` – Hautfaltenmessung, 4 Methoden
-- `nutrition_log` – Kalorien + Makros (aus FDDB-CSV)
-- `activity_log` – Training (aus Apple Health oder manuell)
-- `photos` – Progress Photos
-- `ai_insights` – KI-Auswertungen (scope = prompt-slug)
-- `ai_prompts` – Konfigurierbare Prompts mit Templates (11 Prompts)
-- `ai_usage` – KI-Calls pro Tag pro Profil
-
-**Schema-Datei:** `backend/schema.sql` (vollständiges PostgreSQL-Schema)
-**Migration-Script:** `backend/migrate_to_postgres.py` (SQLite→PostgreSQL, automatisch)
-
-## Auth-Flow (v9b)
+Auth-Flow:
+ Login → E-Mail + Passwort → Token in localStorage
+ Token → X-Auth-Token Header → require_auth()
+ Profile-Id → immer aus Session, nie aus Header!
+ SHA256 → automatisch zu bcrypt migriert beim Login
```
-Login-Screen → E-Mail + Passwort → Token im localStorage
-Token → X-Auth-Token Header → Backend require_auth()
-Profile-Id → aus Session (nicht aus Header!)
-SHA256 Passwörter → automatisch zu bcrypt migriert beim Login
-```
-
-## API-Konventionen
-- Alle Endpoints: `/api/...`
-- Auth-Header: `X-Auth-Token: `
-- Responses: immer JSON
-- Fehler: `{"detail": "Fehlermeldung"}`
-- Rate Limit überschritten: HTTP 429
## Umgebungsvariablen (.env)
```
-# Database (PostgreSQL)
-DB_HOST=postgres
-DB_PORT=5432
-DB_NAME=mitai_prod
-DB_USER=mitai_prod
-DB_PASSWORD= # REQUIRED
-
-# AI
-OPENROUTER_API_KEY= # KI-Calls (optional, alternativ ANTHROPIC_API_KEY)
+DB_HOST/PORT/NAME/USER/PASSWORD # PostgreSQL
+OPENROUTER_API_KEY # KI
OPENROUTER_MODEL=anthropic/claude-sonnet-4
-ANTHROPIC_API_KEY= # Direkte Anthropic API (optional)
-
-# Email
-SMTP_HOST= # E-Mail (für Recovery)
-SMTP_PORT=587
-SMTP_USER=
-SMTP_PASS=
-SMTP_FROM=
-
-# App
+SMTP_HOST/PORT/USER/PASS/FROM # E-Mail
APP_URL=https://mitai.jinkendo.de
ALLOWED_ORIGINS=https://mitai.jinkendo.de
-DATA_DIR=/app/data
PHOTOS_DIR=/app/photos
-ENVIRONMENT=production
```
-## Wichtige Hinweise für Claude Code
-1. **Ports immer 3002/8002 (Prod) oder 3099/8099 (Dev)** – nie ändern
-2. **npm install** (nicht npm ci) – kein package-lock.json vorhanden
-3. **PostgreSQL-Migrations** – Schema-Änderungen in `backend/schema.sql`, dann Container neu bauen
-4. **Pipeline-Prompts** haben slug-Prefix `pipeline_` – nie als Einzelanalyse zeigen
-5. **dayjs.week()** braucht Plugin – stattdessen native JS ISO-Wochenberechnung
-6. **useNavigate()** nur in React-Komponenten, nicht in Helper-Functions
-7. **api.js nutzen** für alle API-Calls – injiziert Token automatisch
-8. **bcrypt** für alle neuen Passwort-Operationen verwenden
-9. **session=Depends(require_auth)** als separater Parameter – nie in Header() einbetten
-10. **RealDictCursor verwenden** – `get_cursor(conn)` statt `conn.cursor()` für dict-like row access
+## Kritische Regeln für Claude Code
-## Code-Style
-- React: Functional Components, Hooks
-- CSS: Inline-Styles + globale CSS-Variablen (var(--accent), var(--text1), etc.)
-- API-Calls: immer über `api.js` (injiziert Token automatisch)
-- Kein TypeScript (bewusst, für Einfachheit)
-- Python: keine Type-Hints Pflicht, aber bei neuen Funktionen erwünscht
+### Must-Do:
+1. `api.js` für ALLE API-Calls nutzen – nie direktes `fetch()` ohne Token
+2. `session: dict = Depends(require_auth)` als **separater** Parameter – nie in `Header()` einbetten
+3. `bcrypt` für alle Passwort-Operationen
+4. Neue DB-Spalten nur via Schema-Migration, nicht direkt
+5. `npm install` (nicht npm ci) – kein package-lock.json
-## Design-System
+### Bekannte Fallstricke:
+```python
+# ❌ FALSCH – führt zu ungeschütztem Endpoint:
+def endpoint(x: str = Header(default=None, session=Depends(require_auth))):
-### Farben (CSS-Variablen)
-```css
---accent: #1D9E75 /* Jinkendo Grün – Buttons, Links, Akzente */
---accent-dark: #085041 /* Dunkelgrün – Icon-Hintergrund, Header */
---accent-light: #E1F5EE /* Hellgrün – Hintergründe, Badges */
---bg: /* Seitenhintergrund (hell/dunkel auto) */
---surface: /* Card-Hintergrund */
---surface2: /* Sekundäre Fläche */
---border: /* Rahmen */
---text1: /* Primärer Text */
---text2: /* Sekundärer Text */
---text3: /* Muted Text, Labels */
---danger: #D85A30 /* Fehler, Warnungen */
+# ✅ RICHTIG:
+def endpoint(x: str = Header(default=None), session: dict = Depends(require_auth)):
```
-### CSS-Klassen
-```css
-.card /* Weißer Container, border-radius 12px, box-shadow */
-.btn /* Basis-Button */
-.btn-primary /* Grüner Button (#1D9E75) */
-.btn-secondary /* Grauer Button */
-.btn-full /* 100% Breite */
-.form-input /* Eingabefeld, volle Breite */
-.form-label /* Feldbezeichnung, klein, uppercase */
-.form-row /* Label + Input + Unit nebeneinander */
-.form-unit /* Einheit rechts (kg, cm, etc.) */
-.section-gap /* margin-bottom zwischen Sektionen */
-.spinner /* Ladekreis, animiert */
-```
-
-### Abstände & Größen
-```
-Seiten-Padding: 16px seitlich
-Card-Padding: 16-20px
-Border-Radius: 12px (Cards), 8px (Buttons/Inputs), 50% (Avatare)
-Icon-Größe: 16-20px inline, 24px standalone
-Font-Größe: 12px (Labels), 14px (Body), 16-18px (Subtitel), 20-24px (Titel)
-Font-Weight: 400 (normal), 600 (semi-bold), 700 (bold)
-Bottom-Padding: 80px (für Mobile-Navigation)
-```
-
-### Komponenten-Muster
-
-**Titelzeile einer Seite:**
-```jsx
-
-
- Seitentitel
-
-
-
-```
-
-**Ladezustand:**
-```jsx
-if (loading) return (
-
-)
-```
-
-**Fehlerzustand:**
-```jsx
-if (error) return (
-
- {error}
-
-)
-```
-
-**Leerer Zustand:**
-```jsx
-{items.length === 0 && (
-
-
📭
-
Noch keine Einträge
-
-)}
-```
-
-**Metric Card:**
-```jsx
-
-
LABEL
-
- {value}
-
-
Einheit
-
-```
-
-### Jinkendo Logo-System
-```
-Grundelement: Ensō-Kreis (offen, Lücke 4-5 Uhr)
-Farbe Ensō: #1D9E75
-Hintergrund: #085041 (dunkelgrün)
-Kern-Symbol: #5DCAA5 (mintgrün)
-Wortmarke: Jin(light) + ken(bold #1D9E75) + do(light)
-```
-
-### Verfügbare Custom Commands
-```
-/deploy → Commit + Push vorbereiten
-/merge-to-prod → develop → main mergen
-/test → Manuelle Tests durchführen
-/new-feature → Neues Feature-Template
-/ui-component → Neue Komponente erstellen
-/ui-page → Neue Seite erstellen
-/fix-bug → Bug analysieren und beheben
-/add-endpoint → Neuen API-Endpoint hinzufügen
-/db-add-column → Neue DB-Spalte hinzufügen
-```
-
-## Jinkendo App-Familie & Markenarchitektur
-
-### Philosophie
-**Jinkendo** (人拳道) = Jin (人 Mensch) + Ken (拳 Faust) + Do (道 Weg)
-"Der menschliche Weg der Kampfkunst" – ruhig aber kraftvoll, Selbstwahrnehmung, Meditation, Zielorientiert
-
-### App-Familie (Subdomain-Architektur)
-```
-mitai.jinkendo.de → Körper-Tracker (身体 = eigener Körper) ← DIESE APP
-miken.jinkendo.de → Meditation (眉間 = drittes Auge)
-ikigai.jinkendo.de → Lebenssinn/Ziele (生き甲斐)
-shinkan.jinkendo.de → Kampfsport (真観 = wahre Wahrnehmung)
-kenkou.jinkendo.de → Gesundheit allgemein (健康) – für später aufsparen
-```
-
-### Registrierte Domains
-- jinkendo.de, jinkendo.com, jinkendo.life – alle registriert bei Strato
-
-## v9b Detailplan – Freemium Tier-System
-
-### Tier-Modell
-```
-free → Selbst-Registrierung, 14-Tage Trial, eingeschränkt
-basic → Kernfunktionen (Abo Stufe 1)
-premium → Alles inkl. KI und Connectoren (Abo Stufe 2)
-selfhosted → Lars' Heimversion, keine Einschränkungen
-```
-
-### Geplante DB-Erweiterungen (profiles Tabelle)
-```sql
-tier TEXT DEFAULT 'free'
-trial_ends_at TEXT -- ISO datetime
-sub_valid_until TEXT -- ISO datetime
-email_verified INTEGER DEFAULT 0
-email_verify_token TEXT
-invited_by TEXT -- profile_id FK
-invitation_token TEXT
-```
-
-### Tier-Limits (geplant)
-| Feature | free | basic | premium | selfhosted |
-|---------|------|-------|---------|------------|
-| Gewicht-Einträge | 30 | unbegrenzt | unbegrenzt | unbegrenzt |
-| KI-Analysen/Monat | 0 | 3 | unbegrenzt | unbegrenzt |
-| Ernährung Import | ❌ | ✅ | ✅ | ✅ |
-| Export | ❌ | ✅ | ✅ | ✅ |
-| Fitness-Connectoren | ❌ | ❌ | ✅ | ✅ |
-
-### Registrierungs-Flow (geplant)
-```
-1. Selbst-Registrierung: Name + E-Mail + Passwort
-2. Auto-Trial: tier='free', trial_ends_at=now+14d
-3. E-Mail-Bestätigung → email_verified=1
-4. Trial läuft ab → Upgrade-Prompt
-5. Einladungslinks: Admin generiert Token → direkt basic-Tier
-6. Stripe Integration: später (v9b ohne Stripe, nur Tier-Logik)
-```
-
-## Infrastruktur Details
-
-### Heimnetzwerk
-```
-Internet
- → Fritz!Box 7530 AX (DynDNS: privat.stommer.com)
- → Synology NAS (192.168.2.63, Reverse Proxy + Let's Encrypt)
- → Raspberry Pi 5 (192.168.2.49, Docker)
- → MiniPC (192.168.2.144, Gitea auf Port 3000)
-```
-
-### Synology Reverse Proxy Regeln
-```
-mitai.jinkendo.de → HTTP 192.168.2.49:3002 (Prod Frontend)
-dev.mitai.jinkendo.de → HTTP 192.168.2.49:3099 (Dev Frontend)
-```
-
-### AdGuard DNS Rewrites (für internes Routing)
-```
-mitai.jinkendo.de → 192.168.2.63
-dev.mitai.jinkendo.de → 192.168.2.63
-```
-
-### Fritz!Box DNS-Rebind Ausnahmen
-```
-jinkendo.de
-mitai.jinkendo.de
-```
-
-### Pi Verzeichnisstruktur
-```
-/home/lars/docker/
-├── bodytrack/ → Prod (main branch, docker-compose.yml)
-└── bodytrack-dev/ → Dev (develop branch, docker-compose.dev-env.yml)
-```
-
-### Gitea Runner
-```
-Runner: raspberry-pi (auf Pi installiert)
-Service: /etc/systemd/system/gitea-runner.service
-Binary: /home/lars/gitea-runner/act_runner
-```
-
-### Container Namen
-```
-Prod: mitai-api, mitai-ui
-Dev: dev-mitai-api, dev-mitai-ui
-```
-
-## Bekannte Probleme & Lösungen
-
-### Admin User-Erstellung – Email fehlt (v9c TODO)
-**Problem:** Bei Admin-CRUD `/api/profiles` (POST) wird keine Email-Adresse abgefragt.
-**Impact:** Neue User können sich nicht einloggen (Login erfordert Email).
-**Workaround:** Email manuell via `/api/admin/profiles/{pid}/email` setzen.
-**Fix-TODO:** POST `/api/profiles` sollte Email als optionales Feld akzeptieren.
-
-### AdminUserRestrictionsPage – Effektive Werte anzeigen (v9c GELÖST)
-**Problem:** Ursprüngliches Design zeigte leere Felder wenn kein Override existierte.
-**Issues:**
-- User konnte nicht sehen welcher Wert aktuell gilt (Override vs Tier-Standard)
-- "unlimited" konnte nicht eingegeben/gespeichert werden (nur Platzhalter)
-- Redundante Overrides (Wert = Tier-Standard) wurden nicht verhindert
-- Tier-Limits verwendeten falschen Fallback (default_limit statt null)
-
-**Lösung (März 2026):**
```javascript
-// getDisplayValue() zeigt effektiven Wert (Override ODER Tier-Limit)
-const restriction = restrictions.find(r => r.feature_id === featureId)
-if (restriction) return formatValue(restriction.limit_value)
-return formatValue(tierLimits[featureId]) // Tier-Standard als Fallback
+// ❌ FALSCH – dayjs.week() existiert nicht ohne Plugin:
+dayjs(date).week()
-// formatValue() konvertiert NULL zu "unlimited" (statt leer)
-if (val === null) return 'unlimited'
-
-// handleChange() entfernt Override wenn Wert = Tier-Standard
-if (parsedValue === tierLimit) {
- newChanges[featureId] = { action: 'remove' } // Redundanter Override
-}
-
-// Tier-Limits-Fallback wie TierLimitsPage
-limits[feature.id] = limitsMatrix.limits[key] ?? null // nicht default_limit!
+// ✅ RICHTIG – native ISO-Wochenberechnung:
+const w = (d => Math.ceil(((new Date(d.setDate(d.getDate()+4-(d.getDay()||7)))-
+ new Date(d.getFullYear(),0,1))/86400000+1)/7))(new Date(date))
```
-**Ergebnis:**
-- ✅ User sieht immer den aktuellen effektiven Wert
-- ✅ "unlimited" kann getippt und gespeichert werden (grün gefärbt)
-- ✅ Redundante Overrides werden automatisch entfernt
-- ✅ Selfhosted-Tier zeigt korrekt "unlimited" statt "0"
-
-### dayjs.week() – NIEMALS verwenden
-```javascript
-// ❌ Falsch:
-const week = dayjs(date).week()
-
-// ✅ Richtig (ISO 8601):
-const weekNum = (() => {
- const dt = new Date(date)
- dt.setHours(0,0,0,0)
- dt.setDate(dt.getDate()+4-(dt.getDay()||7))
- const y = new Date(dt.getFullYear(),0,1)
- return Math.ceil(((dt-y)/86400000+1)/7)
-})()
-```
-
-### session=Depends(require_auth) – Korrekte Platzierung
```python
-# ❌ Falsch (führt zu NameError oder ungeschütztem Endpoint):
-def endpoint(x_profile_id: Optional[str] = Header(default=None, session=Depends(require_auth))):
-
-# ✅ Richtig (separater Parameter):
-def endpoint(x_profile_id: Optional[str] = Header(default=None),
- session: dict = Depends(require_auth)):
+# PostgreSQL Boolean (nicht SQLite 0/1):
+WHERE active = true # ✅
+WHERE active = 1 # ❌
```
-### Recharts Bar fill=function – nicht unterstützt
-```jsx
-// ❌ Falsch:
- entry.color}/>
+## Design-System (Kurzreferenz)
+```css
+/* Farben */
+--accent: #1D9E75 --accent-dark: #085041 --danger: #D85A30
+--bg · --surface · --surface2 · --border · --text1 · --text2 · --text3
-// ✅ Richtig:
-
+/* Klassen */
+.card · .btn · .btn-primary · .btn-secondary · .btn-full
+.form-input · .form-label · .form-row · .spinner
+
+/* Abstände */
+Seiten-Padding: 16px · Card-Padding: 16-20px · Border-Radius: 12px/8px
+Bottom-Padding Mobile: 80px (Navigation)
```
-### PostgreSQL Boolean-Syntax
-```python
-# ❌ Falsch (SQLite-Syntax):
-cur.execute("SELECT * FROM ai_prompts WHERE active=1")
+> Vollständige CSS-Variablen und Komponenten-Muster: `frontend/src/app.css`
+> Responsive Layout-Spec: `.claude/docs/functional/RESPONSIVE_UI.md`
-# ✅ Richtig (PostgreSQL):
-cur.execute("SELECT * FROM ai_prompts WHERE active=true")
+## Dokumentations-Referenzen
+
+> **Für Claude Code:** Beim Arbeiten an einem Thema die entsprechende Datei lesen:
+
+| Thema | Datei |
+|-------|-------|
+| Backend-Architektur, Router, DB-Zugriff | `.claude/docs/architecture/BACKEND.md` |
+| Frontend-Architektur, api.js, Komponenten | `.claude/docs/architecture/FRONTEND.md` |
+| Coding Rules (Pflichtregeln) | `.claude/docs/rules/CODING_RULES.md` |
+| Lessons Learned (Fehler vermeiden) | `.claude/docs/rules/LESSONS_LEARNED.md` |
+| Feature Backlog (Übersicht) | `.claude/docs/BACKLOG.md` |
+| Membership-System (v9c, technisch) | `.claude/docs/technical/MEMBERSHIP_SYSTEM.md` |
+| Trainingstypen + HF (v9d, fachlich) | `.claude/docs/functional/TRAINING_TYPES.md` |
+| KI-Prompt Flexibilisierung (v9f, fachlich) | `.claude/docs/functional/AI_PROMPTS.md` |
+| Responsive UI (fachlich) | `.claude/docs/functional/RESPONSIVE_UI.md` |
+
+## Jinkendo App-Familie
```
-
-### RealDictCursor für dict-like row access
-```python
-# ❌ Falsch:
-cur = conn.cursor()
-cur.execute("SELECT COUNT(*) FROM weight_log")
-count = cur.fetchone()[0] # Tuple index
-
-# ✅ Richtig:
-cur = get_cursor(conn) # Returns RealDictCursor
-cur.execute("SELECT COUNT(*) as count FROM weight_log")
-count = cur.fetchone()['count'] # Dict key
-```
-
-## v9b Migration – Lessons Learned
-
-### PostgreSQL Migration (SQLite → PostgreSQL)
-**Problem:** Docker Build hing 30+ Minuten bei `apt-get install postgresql-client`
-**Lösung:** Alle apt-get dependencies entfernt, reine Python-Lösung mit psycopg2-binary
-
-**Problem:** Leere date-Strings (`''`) führten zu PostgreSQL-Fehlern
-**Lösung:** Migration-Script konvertiert leere Strings zu NULL für DATE-Spalten
-
-**Problem:** Boolean-Felder (SQLite INTEGER 0/1 vs PostgreSQL BOOLEAN)
-**Lösung:** Migration konvertiert automatisch, Backend nutzt `active=true` statt `active=1`
-
-### API Endpoint Consistency (März 2026)
-**Problem:** 11 kritische Endpoint-Mismatches zwischen Frontend und Backend gefunden
-**Gelöst:**
-- AI-Endpoints konsistent: `/api/insights/run/{slug}`, `/api/insights/pipeline`
-- Password-Reset: `/api/auth/forgot-password`, `/api/auth/reset-password`
-- Admin-Endpoints: `/permissions`, `/email`, `/pin` Sub-Routes
-- Export: JSON + ZIP Endpoints hinzugefügt
-- Prompt-Bearbeitung: PUT-Endpoint für Admins
-
-**Tool:** Vollständiger Audit via Explore-Agent empfohlen bei größeren Änderungen
-
-## Export/Import Spezifikation (v9c)
-
-### ZIP-Export Struktur
-```
-mitai-export-{name}-{YYYY-MM-DD}.zip
-├── README.txt ← Erklärung des Formats + Versionsnummer
-├── profile.json ← Profildaten (ohne Passwort-Hash)
-├── data/
-│ ├── weight.csv ← Gewichtsverlauf
-│ ├── circumferences.csv ← Umfänge (8 Messpunkte)
-│ ├── caliper.csv ← Caliper-Messungen
-│ ├── nutrition.csv ← Ernährungsdaten
-│ └── activity.csv ← Aktivitäten
-├── insights/
-│ └── ai_insights.json ← KI-Auswertungen (alle gespeicherten)
-└── photos/
- ├── {date}_{id}.jpg ← Progress-Fotos
- └── ...
-```
-
-### CSV Format (alle Dateien)
-```
-- Trennzeichen: Semikolon (;) – Excel/LibreOffice kompatibel
-- Encoding: UTF-8 mit BOM (für Windows Excel)
-- Datumsformat: YYYY-MM-DD
-- Dezimaltrennzeichen: Punkt (.)
-- Erste Zeile: Header
-- Nullwerte: leer (nicht "null" oder "NULL")
-```
-
-### weight.csv Spalten
-```
-id;date;weight;note;source;created
-```
-
-### circumferences.csv Spalten
-```
-id;date;waist;hip;chest;neck;upper_arm;thigh;calf;forearm;note;created
-```
-
-### caliper.csv Spalten
-```
-id;date;chest;abdomen;thigh;tricep;subscapular;suprailiac;midaxillary;method;bf_percent;note;created
-```
-
-### nutrition.csv Spalten
-```
-id;date;meal_name;kcal;protein;fat;carbs;fiber;note;source;created
-```
-
-### activity.csv Spalten
-```
-id;date;name;type;duration_min;kcal;heart_rate_avg;heart_rate_max;distance_km;note;source;created
-```
-
-### profile.json Struktur
-```json
-{
- "export_version": "2",
- "export_date": "2026-03-18",
- "app": "Mitai Jinkendo",
- "profile": {
- "name": "Lars",
- "email": "lars@stommer.com",
- "sex": "m",
- "height": 178,
- "birth_year": 1980,
- "goal_weight": 82,
- "goal_bf_pct": 14,
- "avatar_color": "#1D9E75",
- "auth_type": "password",
- "session_days": 30,
- "ai_enabled": true,
- "tier": "selfhosted"
- },
- "stats": {
- "weight_entries": 150,
- "nutrition_entries": 300,
- "activity_entries": 45,
- "photos": 12
- }
-}
-```
-
-### ai_insights.json Struktur
-```json
-[
- {
- "id": "uuid",
- "scope": "gesamt",
- "created": "2026-03-18T10:00:00",
- "result": "KI-Analyse Text..."
- }
-]
-```
-
-### README.txt Inhalt
-```
-Mitai Jinkendo – Datenexport
-Version: 2
-Exportiert am: YYYY-MM-DD
-Profil: {name}
-
-Inhalt:
-- profile.json: Profildaten und Einstellungen
-- data/*.csv: Messdaten (Semikolon-getrennt, UTF-8)
-- insights/: KI-Auswertungen (JSON)
-- photos/: Progress-Fotos (JPEG)
-
-Import:
-Dieser Export kann in Mitai Jinkendo unter
-Einstellungen → Import → "Mitai Backup importieren"
-wieder eingespielt werden.
-
-Format-Version 2 (ab v9b):
-Alle CSV-Dateien sind UTF-8 mit BOM kodiert.
-Trennzeichen: Semikolon (;)
-Datumsformat: YYYY-MM-DD
-```
-
-### Import-Funktion (zu implementieren)
-**Endpoint:** `POST /api/import/zip`
-**Verhalten:**
-- Akzeptiert ZIP-Datei (multipart/form-data)
-- Erkennt export_version aus profile.json
-- Importiert nur fehlende Einträge (kein Duplikat)
-- Fotos werden nicht überschrieben falls bereits vorhanden
-- Gibt Zusammenfassung zurück: wie viele Einträge je Kategorie importiert
-- Bei Fehler: vollständiger Rollback (alle oder nichts)
-
-**Duplikat-Erkennung:**
-```python
-# INSERT ... ON CONFLICT (profile_id, date) DO NOTHING
-# weight: UNIQUE (profile_id, date)
-# nutrition: UNIQUE (profile_id, date, meal_name)
-# activity: UNIQUE (profile_id, date, name)
-# caliper: UNIQUE (profile_id, date)
-# circumferences: UNIQUE (profile_id, date)
-```
-
-**Frontend:** Neuer Button in SettingsPage:
-```
-[ZIP exportieren] [JSON exportieren] [Backup importieren]
+mitai.jinkendo.de → Körper-Tracker (diese App)
+miken.jinkendo.de → Meditation (眉間)
+ikigai.jinkendo.de → Lebenssinn (生き甲斐)
+shinkan.jinkendo.de → Kampfsport (真観)
```
diff --git a/backend/apply_v9c_migration.py b/backend/apply_v9c_migration.py
index a639b75..78a9fca 100644
--- a/backend/apply_v9c_migration.py
+++ b/backend/apply_v9c_migration.py
@@ -60,6 +60,9 @@ def apply_migration():
if not migration_needed(conn):
print("[v9c Migration] Already applied, skipping.")
conn.close()
+
+ # Even if main migration is done, check cleanup
+ apply_cleanup_migration()
return
print("[v9c Migration] Applying subscription system migration...")
@@ -128,10 +131,123 @@ def apply_migration():
cur.close()
conn.close()
+ # After successful migration, apply cleanup
+ apply_cleanup_migration()
+
except Exception as e:
print(f"[v9c Migration] ❌ Error: {e}")
raise
+def cleanup_features_needed(conn):
+ """Check if feature cleanup migration is needed."""
+ cur = conn.cursor()
+
+ # Check if old export features still exist
+ cur.execute("""
+ SELECT COUNT(*) as count FROM features
+ WHERE id IN ('export_csv', 'export_json', 'export_zip')
+ """)
+ old_exports = cur.fetchone()['count']
+
+ # Check if csv_import needs to be renamed
+ cur.execute("""
+ SELECT COUNT(*) as count FROM features
+ WHERE id = 'csv_import'
+ """)
+ old_import = cur.fetchone()['count']
+
+ cur.close()
+
+ # Cleanup needed if old features exist
+ return old_exports > 0 or old_import > 0
+
+
+def apply_cleanup_migration():
+ """Apply v9c feature cleanup migration."""
+ print("[v9c Cleanup] Checking if cleanup migration is needed...")
+
+ try:
+ conn = get_db_connection()
+
+ if not cleanup_features_needed(conn):
+ print("[v9c Cleanup] Already applied, skipping.")
+ conn.close()
+ return
+
+ print("[v9c Cleanup] Applying feature consolidation...")
+
+ # Show BEFORE state
+ cur = conn.cursor()
+ cur.execute("SELECT id, name FROM features ORDER BY category, id")
+ features_before = [f"{r['id']} ({r['name']})" for r in cur.fetchall()]
+ print(f"[v9c Cleanup] Features BEFORE: {len(features_before)} features")
+ for f in features_before:
+ print(f" - {f}")
+ cur.close()
+
+ # Read cleanup migration SQL
+ cleanup_path = os.path.join(
+ os.path.dirname(__file__),
+ "migrations",
+ "v9c_cleanup_features.sql"
+ )
+
+ if not os.path.exists(cleanup_path):
+ print(f"[v9c Cleanup] ⚠️ Cleanup migration file not found: {cleanup_path}")
+ conn.close()
+ return
+
+ with open(cleanup_path, 'r', encoding='utf-8') as f:
+ cleanup_sql = f.read()
+
+ # Execute cleanup migration
+ cur = conn.cursor()
+ cur.execute(cleanup_sql)
+ conn.commit()
+ cur.close()
+
+ # Show AFTER state
+ cur = conn.cursor()
+ cur.execute("SELECT id, name, category FROM features ORDER BY category, id")
+ features_after = cur.fetchall()
+ print(f"[v9c Cleanup] Features AFTER: {len(features_after)} features")
+
+ # Group by category
+ categories = {}
+ for f in features_after:
+ cat = f['category'] or 'other'
+ if cat not in categories:
+ categories[cat] = []
+ categories[cat].append(f"{f['id']} ({f['name']})")
+
+ for cat, feats in sorted(categories.items()):
+ print(f" {cat.upper()}:")
+ for f in feats:
+ print(f" - {f}")
+
+ # Verify tier_limits updated
+ cur.execute("""
+ SELECT tier_id, feature_id, limit_value
+ FROM tier_limits
+ WHERE feature_id IN ('data_export', 'data_import')
+ ORDER BY tier_id, feature_id
+ """)
+ limits = cur.fetchall()
+ print(f"[v9c Cleanup] Tier limits for data_export/data_import:")
+ for lim in limits:
+ limit_str = 'unlimited' if lim['limit_value'] is None else lim['limit_value']
+ print(f" {lim['tier_id']}.{lim['feature_id']} = {limit_str}")
+
+ cur.close()
+ conn.close()
+
+ print("[v9c Cleanup] ✅ Feature cleanup completed successfully!")
+
+ except Exception as e:
+ print(f"[v9c Cleanup] ❌ Error: {e}")
+ raise
+
+
if __name__ == "__main__":
apply_migration()
diff --git a/backend/migrations/check_features.sql b/backend/migrations/check_features.sql
new file mode 100644
index 0000000..70f10fe
--- /dev/null
+++ b/backend/migrations/check_features.sql
@@ -0,0 +1,50 @@
+-- ============================================================================
+-- Feature Check Script - Diagnose vor/nach Migration
+-- ============================================================================
+-- Usage: psql -U mitai_dev -d mitai_dev -f check_features.sql
+-- ============================================================================
+
+\echo '=== CURRENT FEATURES ==='
+SELECT id, name, category, limit_type, reset_period, default_limit, active
+FROM features
+ORDER BY category, id;
+
+\echo ''
+\echo '=== TIER LIMITS MATRIX ==='
+SELECT
+ f.id as feature,
+ f.category,
+ MAX(CASE WHEN tl.tier_id = 'free' THEN COALESCE(tl.limit_value::text, '∞') END) as free,
+ MAX(CASE WHEN tl.tier_id = 'basic' THEN COALESCE(tl.limit_value::text, '∞') END) as basic,
+ MAX(CASE WHEN tl.tier_id = 'premium' THEN COALESCE(tl.limit_value::text, '∞') END) as premium,
+ MAX(CASE WHEN tl.tier_id = 'selfhosted' THEN COALESCE(tl.limit_value::text, '∞') END) as selfhosted
+FROM features f
+LEFT JOIN tier_limits tl ON f.id = tl.feature_id
+GROUP BY f.id, f.category
+ORDER BY f.category, f.id;
+
+\echo ''
+\echo '=== FEATURE COUNT BY CATEGORY ==='
+SELECT category, COUNT(*) as count
+FROM features
+WHERE active = true
+GROUP BY category
+ORDER BY category;
+
+\echo ''
+\echo '=== ORPHANED TIER LIMITS (feature not exists) ==='
+SELECT tl.tier_id, tl.feature_id, tl.limit_value
+FROM tier_limits tl
+LEFT JOIN features f ON tl.feature_id = f.id
+WHERE f.id IS NULL;
+
+\echo ''
+\echo '=== USER FEATURE USAGE (current usage tracking) ==='
+SELECT
+ p.name as user,
+ ufu.feature_id,
+ ufu.usage_count,
+ ufu.reset_at
+FROM user_feature_usage ufu
+JOIN profiles p ON ufu.profile_id = p.id
+ORDER BY p.name, ufu.feature_id;
diff --git a/backend/migrations/v9c_cleanup_features.sql b/backend/migrations/v9c_cleanup_features.sql
new file mode 100644
index 0000000..9acfbde
--- /dev/null
+++ b/backend/migrations/v9c_cleanup_features.sql
@@ -0,0 +1,141 @@
+-- ============================================================================
+-- v9c Cleanup: Feature-Konsolidierung
+-- ============================================================================
+-- Created: 2026-03-20
+-- Purpose: Konsolidiere Export-Features (export_csv/json/zip → data_export)
+-- und Import-Features (csv_import → data_import)
+--
+-- Idempotent: Kann mehrfach ausgeführt werden
+--
+-- Lessons Learned:
+-- "Ein Feature für Export, nicht drei (csv/json/zip)"
+-- ============================================================================
+
+-- ============================================================================
+-- 1. Rename csv_import to data_import
+-- ============================================================================
+UPDATE features
+SET
+ id = 'data_import',
+ name = 'Daten importieren',
+ description = 'CSV-Import (FDDB, Apple Health) + ZIP-Backup-Import'
+WHERE id = 'csv_import';
+
+-- Update tier_limits references
+UPDATE tier_limits
+SET feature_id = 'data_import'
+WHERE feature_id = 'csv_import';
+
+-- Update user_feature_restrictions references
+UPDATE user_feature_restrictions
+SET feature_id = 'data_import'
+WHERE feature_id = 'csv_import';
+
+-- Update user_feature_usage references
+UPDATE user_feature_usage
+SET feature_id = 'data_import'
+WHERE feature_id = 'csv_import';
+
+-- ============================================================================
+-- 2. Remove old export_csv/json/zip features
+-- ============================================================================
+
+-- Remove tier_limits for old features
+DELETE FROM tier_limits
+WHERE feature_id IN ('export_csv', 'export_json', 'export_zip');
+
+-- Remove user restrictions for old features
+DELETE FROM user_feature_restrictions
+WHERE feature_id IN ('export_csv', 'export_json', 'export_zip');
+
+-- Remove usage tracking for old features
+DELETE FROM user_feature_usage
+WHERE feature_id IN ('export_csv', 'export_json', 'export_zip');
+
+-- Remove old feature definitions
+DELETE FROM features
+WHERE id IN ('export_csv', 'export_json', 'export_zip');
+
+-- ============================================================================
+-- 3. Ensure data_export exists and is properly configured
+-- ============================================================================
+INSERT INTO features (id, name, description, category, limit_type, reset_period, default_limit, active)
+VALUES ('data_export', 'Daten exportieren', 'CSV/JSON/ZIP Export', 'export', 'count', 'monthly', 0, true)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ description = EXCLUDED.description,
+ category = EXCLUDED.category,
+ limit_type = EXCLUDED.limit_type,
+ reset_period = EXCLUDED.reset_period;
+
+-- ============================================================================
+-- 4. Ensure data_import exists and is properly configured
+-- ============================================================================
+INSERT INTO features (id, name, description, category, limit_type, reset_period, default_limit, active)
+VALUES ('data_import', 'Daten importieren', 'CSV-Import (FDDB, Apple Health) + ZIP-Backup-Import', 'import', 'count', 'monthly', 0, true)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ description = EXCLUDED.description,
+ category = EXCLUDED.category,
+ limit_type = EXCLUDED.limit_type,
+ reset_period = EXCLUDED.reset_period;
+
+-- ============================================================================
+-- 5. Update tier_limits for data_export (consolidate from old features)
+-- ============================================================================
+
+-- FREE tier: no export
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('free', 'data_export', 0)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- BASIC tier: 5 exports/month
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('basic', 'data_export', 5)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- PREMIUM tier: unlimited
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('premium', 'data_export', NULL)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- SELFHOSTED tier: unlimited
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('selfhosted', 'data_export', NULL)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- ============================================================================
+-- 6. Update tier_limits for data_import
+-- ============================================================================
+
+-- FREE tier: no import
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('free', 'data_import', 0)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- BASIC tier: 3 imports/month
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('basic', 'data_import', 3)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- PREMIUM tier: unlimited
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('premium', 'data_import', NULL)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- SELFHOSTED tier: unlimited
+INSERT INTO tier_limits (tier_id, feature_id, limit_value)
+VALUES ('selfhosted', 'data_import', NULL)
+ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value;
+
+-- ============================================================================
+-- Cleanup complete
+-- ============================================================================
+-- Final feature list:
+-- Data: weight_entries, circumference_entries, caliper_entries,
+-- nutrition_entries, activity_entries, photos
+-- AI: ai_calls, ai_pipeline
+-- Export/Import: data_export, data_import
+--
+-- Total: 10 features (down from 13)
+-- ============================================================================