All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Introduced a centralized media archive (`/media`) with lifecycle management, including soft delete and recovery options. - Enhanced media upload functionality to support multiple files and automatic type inference. - Updated documentation to reflect the new media architecture and inline media linking specifications. - Version bump to 0.8.59 to accommodate changes in media handling and database schema. Co-authored-by: Cursor <cursoragent@cursor.com>
488 lines
16 KiB
Markdown
488 lines
16 KiB
Markdown
# Architektur-Regeln – Mitai Jinkendo
|
||
|
||
> **PFLICHTLEKTÜRE für Claude Code vor jeder Implementierung.**
|
||
> Diese Regeln sind verbindlich und dürfen nicht ohne explizite
|
||
> Genehmigung des Nutzers abgeändert werden.
|
||
>
|
||
> **Dokumentationsablage:** siehe **`DOCUMENTATION.md`** (gleicher Ordner) – fachlich/technisch/working/issues.
|
||
|
||
---
|
||
|
||
## 1. Router-Architektur
|
||
|
||
### 1.1 Ein Modul = Ein Router
|
||
Jedes fachliche Modul hat genau eine Router-Datei in `backend/routers/`.
|
||
|
||
```
|
||
backend/routers/
|
||
├── auth.py # Authentifizierung
|
||
├── profiles.py # Nutzerprofile
|
||
├── weight.py # Gewichts-Tracking
|
||
├── sleep.py # Schlaf-Modul
|
||
├── training_types.py # Trainingstypen + HF
|
||
└── ... # je neues Modul = neue Datei
|
||
```
|
||
|
||
**Regeln:**
|
||
- Kein Endpoint darf außerhalb seines thematischen Routers definiert werden
|
||
- Neue Module immer als neue Router-Datei anlegen, nie in bestehende einfügen
|
||
- Router in `main.py` registrieren: `app.include_router(modul.router, prefix="/api")`
|
||
- Router-Datei-Name = Modul-Name in `version.py` MODULE_VERSIONS
|
||
|
||
### 1.2 API-First Prinzip
|
||
Jede Funktion ist zuerst als API-Endpoint implementiert – die UI nutzt ausschließlich
|
||
diese Endpoints über `api.js`. Keine Business-Logik im Frontend.
|
||
|
||
```python
|
||
# ✅ Richtig: Logik im Backend-Endpoint
|
||
@router.get("/sleep/stats")
|
||
def get_sleep_stats(session=Depends(require_auth)):
|
||
# Berechnung hier
|
||
return {"avg_duration": ..., "sleep_debt": ...}
|
||
|
||
# ❌ Falsch: Berechnung im Frontend
|
||
const sleepDebt = entries.reduce((sum, e) => sum + (goal - e.duration), 0)
|
||
```
|
||
|
||
### 1.3 Einheitliche Fehlerbehandlung
|
||
```python
|
||
# ✅ Immer dieses Format:
|
||
raise HTTPException(status_code=404, detail="Eintrag nicht gefunden")
|
||
# Response: {"detail": "Eintrag nicht gefunden"}
|
||
|
||
# ❌ Nie eigene Formate:
|
||
return {"error": "not found"}
|
||
return {"message": "Fehler", "success": False}
|
||
```
|
||
|
||
### 1.4 Mandanten & Zugriffsschicht (Shinkan / ACCESS_LAYER)
|
||
|
||
**Verbindlicher Rahmen:** `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`
|
||
**Medien-Assets (Archiv, Papierkorb, Promotion, Copyright, externer Speicher, geplante Inline-Verlinkung §11):** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`
|
||
**Session-Handover (Ist + nächste Schritte):** `docs/HANDOVER.md`
|
||
**Fortlaufendes Inventar:** `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`
|
||
|
||
**Definition of Done für neue oder geänderte geschützte APIs**, sobald Daten **Verein**, **Sichtbarkeit** oder **mandantenbezogene Listen** betreffen:
|
||
|
||
1. Request-Kontext: `Depends(get_tenant_context)` (oder dokumentierte Ausnahme mit Kommentar `# ACCESS_LAYER exempt:` + Audit).
|
||
2. Lesen: gleiche Sichtbarkeitslogik wie vergleichbare Bibliotheks-/Planungsartefakte (`library_content_visibility_sql`, `exercise_visible_to_profile` / zentrale Helfer — nicht ad-hoc „alles aus der Tabelle“).
|
||
3. Schreiben: `assert_valid_governance_visibility` wo `visibility` / `club_id` gesetzt oder geändert wird.
|
||
4. Audit-Tabelle und bei Bedarf die EXEMPT-Liste im Script `backend/scripts/check_access_layer_hints.py` aktualisieren.
|
||
5. Optional vor Commit: `python backend/scripts/check_access_layer_hints.py` (mit `ACCESS_LAYER_STRICT=1` schlägt bei neuen Verstößen fehl).
|
||
|
||
Router ohne Vereinsbezug (z. B. globale Kataloge, Auth-Login) bleiben bewusst ohne `get_tenant_context`; sie stehen im Script auf der **EXEMPT**-Liste.
|
||
|
||
---
|
||
|
||
## 2. Versionskontrollsystem
|
||
|
||
### 2.1 Versionierungsschema
|
||
**Semantic Versioning: `MAJOR.MINOR.PATCH`**
|
||
|
||
| Typ | Wann | Beispiel |
|
||
|-----|------|---------|
|
||
| MAJOR | Breaking Change, DB-Migration inkompatibel | 9.0.0 → 10.0.0 |
|
||
| MINOR | Neues Feature, neues Modul | 9.2.0 → 9.3.0 |
|
||
| PATCH | Bugfix, kleine Änderung, Refactor | 9.3.0 → 9.3.1 |
|
||
|
||
### 2.2 Versions-Dateien
|
||
|
||
**Backend: `backend/version.py`**
|
||
```python
|
||
APP_VERSION = "9.3.0"
|
||
BUILD_DATE = "2026-03-22"
|
||
|
||
MODULE_VERSIONS = {
|
||
"auth": "1.2.0",
|
||
"profiles": "1.1.0",
|
||
"weight": "1.0.3",
|
||
"circumference": "1.0.1",
|
||
"caliper": "1.0.1",
|
||
"activity": "1.1.0",
|
||
"nutrition": "1.0.2",
|
||
"photos": "1.0.0",
|
||
"insights": "1.3.0",
|
||
"prompts": "1.1.0",
|
||
"admin": "1.2.0",
|
||
"stats": "1.0.1",
|
||
"exportdata": "1.1.0",
|
||
"importdata": "1.0.0",
|
||
"membership": "2.1.0",
|
||
}
|
||
|
||
CHANGELOG = [
|
||
{
|
||
"version": "9.3.0",
|
||
"date": "2026-03-22",
|
||
"changes": [
|
||
"Feature: Sleep Module (sleep_log, JSONB-Segmente)",
|
||
"Feature: Vitalwerte-Seite in Navigation",
|
||
"Feature: Trainingstypen-Kategorisierung",
|
||
]
|
||
},
|
||
{
|
||
"version": "9.2.1",
|
||
"date": "2026-03-20",
|
||
"changes": [
|
||
"Fix: Feature-Enforcement Rollback",
|
||
"Fix: Erholungsstatus-Gewichtung korrigiert",
|
||
]
|
||
},
|
||
]
|
||
```
|
||
|
||
**Frontend: `frontend/src/version.js`**
|
||
```javascript
|
||
export const APP_VERSION = "9.3.0"
|
||
export const BUILD_DATE = "2026-03-22"
|
||
|
||
export const PAGE_VERSIONS = {
|
||
Dashboard: "1.3.0",
|
||
LoginScreen: "1.1.0",
|
||
WeightPage: "1.0.3",
|
||
ActivityPage: "1.2.0",
|
||
NutritionPage: "1.1.0",
|
||
AnalysisPage: "1.3.0",
|
||
SettingsPage: "1.4.0",
|
||
AdminPanel: "1.2.0",
|
||
SubscriptionPage: "1.0.0",
|
||
// Neue Seiten hier eintragen
|
||
}
|
||
```
|
||
|
||
### 2.3 Versions-Endpoint
|
||
|
||
**`GET /api/version`** – öffentlich (kein Auth erforderlich)
|
||
|
||
```json
|
||
{
|
||
"app_version": "9.3.0",
|
||
"build_date": "2026-03-22",
|
||
"backend_version": "9.3.0",
|
||
"modules": {
|
||
"auth": "1.2.0",
|
||
"sleep": "1.0.0"
|
||
},
|
||
"db_schema_version": "20260322",
|
||
"environment": "production"
|
||
}
|
||
```
|
||
|
||
Dieser Endpoint wird in `backend/routers/version.py` implementiert und liest
|
||
direkt aus `version.py`.
|
||
|
||
### 2.4 Versions-Anzeige in der App
|
||
|
||
**Settings-Seite – Versions-Panel:**
|
||
```
|
||
System-Versionen
|
||
─────────────────────────────────────
|
||
App (gesamt) 9.3.0
|
||
Backend 9.3.0 ✓ erreichbar
|
||
Frontend 9.3.0 ✓ geladen
|
||
DB-Schema 20260322
|
||
Umgebung production
|
||
─────────────────────────────────────
|
||
Module
|
||
auth 1.2.0
|
||
sleep 1.0.0
|
||
membership 2.1.0
|
||
[alle Module...]
|
||
─────────────────────────────────────
|
||
[Changelog] [Cache leeren]
|
||
```
|
||
|
||
Frontend ruft beim Laden der Settings-Seite `/api/version` ab und vergleicht
|
||
mit der eigenen `APP_VERSION` aus `version.js`. Bei Abweichung: Warnung anzeigen.
|
||
|
||
### 2.5 Pflicht-Regel: Versions-Bump bei jedem Commit
|
||
|
||
**Jede Code-Änderung erfordert:**
|
||
1. Versions-Bump in `backend/version.py` (APP_VERSION + betroffenes MODULE_VERSION)
|
||
2. Versions-Bump in `frontend/src/version.js` (APP_VERSION + betroffene PAGE_VERSION)
|
||
3. Changelog-Eintrag in `backend/version.py` CHANGELOG
|
||
|
||
**Claude Code prüft das im `/deploy` Command automatisch.**
|
||
|
||
Kein Commit ohne Versions-Bump – keine Ausnahme.
|
||
|
||
### 2.6 DB-Schema-Version
|
||
|
||
Format: `YYYYMMDD` (Datum der letzten Migration)
|
||
|
||
Gespeichert in `backend/version.py`:
|
||
```python
|
||
DB_SCHEMA_VERSION = "20260322"
|
||
```
|
||
|
||
Bei jeder Schema-Änderung (ALTER TABLE, neue Tabelle) → DB_SCHEMA_VERSION aktualisieren.
|
||
|
||
---
|
||
|
||
## 3. Datenbankregeln
|
||
|
||
### 3.1 Pflichtfelder für neue Tabellen
|
||
```sql
|
||
-- Jede neue Tabelle braucht:
|
||
id SERIAL PRIMARY KEY,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW()
|
||
```
|
||
|
||
### 3.2 Source-Tracking bei Import-Daten
|
||
Tabellen die Daten aus externen Quellen empfangen brauchen:
|
||
```sql
|
||
source VARCHAR(50) DEFAULT 'manual'
|
||
-- Werte u. a.: 'manual' | 'apple_health' | 'garmin' | 'withings' | 'csv'
|
||
```
|
||
Importe über den **Universal CSV**-Pfad setzen `source = 'csv'`, sofern die Tabelle ein `source`-Feld hat; CHECK-Constraints und Migrationen müssen diesen Wert erlauben.
|
||
|
||
**Agent-Pflicht bei neuen Import-Zielen oder Executor-Änderungen:** `.claude/docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`
|
||
|
||
Manuelle Einträge (`source = 'manual'`) haben IMMER Vorrang bei Reimport:
|
||
```sql
|
||
-- Reimport überschreibt nur nicht-manuelle Einträge:
|
||
INSERT INTO sleep_log (...) ON CONFLICT (profile_id, date)
|
||
DO UPDATE SET ... WHERE sleep_log.source != 'manual'
|
||
```
|
||
|
||
### 3.3 Profile-ID Isolation
|
||
Jede Tabelle mit Nutzerdaten hat `profile_id` als Foreign Key.
|
||
Kein Endpoint gibt Daten eines anderen Profils zurück.
|
||
Profile-ID kommt IMMER aus der Session, nie aus Request-Parametern.
|
||
|
||
### 3.4 Boolean-Werte
|
||
```sql
|
||
-- PostgreSQL Boolean (nicht SQLite 0/1):
|
||
WHERE active = true ✓
|
||
WHERE active = 1 ✗
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Frontend-Regeln
|
||
|
||
### 4.1 Alle API-Calls über api.js
|
||
```javascript
|
||
// ✅ Richtig:
|
||
import { api } from '../utils/api'
|
||
const data = await api.listSleep()
|
||
|
||
// ❌ Falsch:
|
||
const r = await fetch('/api/sleep')
|
||
```
|
||
|
||
### 4.2 Neue Seite = Eintrag in PAGE_VERSIONS
|
||
Jede neue Seite in `frontend/src/version.js` registrieren.
|
||
|
||
### 4.3 CSS-Variablen statt Hardcoded-Farben
|
||
```javascript
|
||
// ✅ Richtig:
|
||
style={{color: 'var(--accent)'}}
|
||
|
||
// ❌ Falsch:
|
||
style={{color: '#1D9E75'}}
|
||
```
|
||
|
||
### 4.4 Fehlerbehandlung in allen async Funktionen
|
||
```javascript
|
||
try {
|
||
const data = await api.meinEndpoint()
|
||
setData(data)
|
||
} catch(e) {
|
||
setError(e.message)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Git & Deployment-Regeln
|
||
|
||
### 5.1 Nie direkt auf main pushen
|
||
Immer über Pull Request in Gitea: develop → main.
|
||
develop Branch niemals löschen.
|
||
|
||
### 5.2 Commit-Message Format
|
||
```
|
||
feat: neues Feature oder Modul
|
||
fix: Bugfix
|
||
refactor: Umbau ohne Funktionsänderung
|
||
docs: Dokumentation
|
||
version: Versions-Bump
|
||
ci: CI/CD Änderungen
|
||
chore: Maintenance
|
||
```
|
||
|
||
### 5.3 Versions-Bump im Commit
|
||
```
|
||
feat: Sleep Module v1.0.0
|
||
|
||
- sleep_log Tabelle mit JSONB-Segmenten
|
||
- Import aus Apple Health CSV
|
||
- Korrelationen Schlaf <-> Ruhepuls
|
||
|
||
version: 9.3.0 (backend + frontend)
|
||
module: sleep 1.0.0
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Dokumentations-Regeln
|
||
|
||
### 6.1 Neue Module dokumentieren
|
||
Bei jedem neuen Modul:
|
||
1. Fachliche Spec: `.claude/docs/functional/MODUL_NAME.md`
|
||
2. Technische Spec: `.claude/docs/technical/MODUL_NAME.md`
|
||
3. Nach Fertigstellung: `.claude/library/` aktualisieren
|
||
|
||
### 6.2 CLAUDE.md aktuell halten
|
||
Nach größeren Änderungen CLAUDE.md Versions-Tabelle aktualisieren.
|
||
|
||
### 6.3 Lessons Learned dokumentieren
|
||
Jeder Rollback oder schwerer Bug → Eintrag in `.claude/rules/LESSONS_LEARNED.md`
|
||
|
||
---
|
||
|
||
## Zusammenfassung: Checkliste vor jedem Commit
|
||
|
||
```
|
||
[ ] Versions-Bump in backend/version.py (APP_VERSION + MODULE)
|
||
[ ] Versions-Bump in frontend/src/version.js (APP_VERSION + PAGE)
|
||
[ ] Changelog-Eintrag in backend/version.py
|
||
[ ] DB_SCHEMA_VERSION aktualisiert (wenn Schema geändert)
|
||
[ ] Neues Modul in PAGE_VERSIONS / MODULE_VERSIONS eingetragen
|
||
[ ] Auth auf alle neuen Endpoints (require_auth)
|
||
[ ] Fehlerformat einheitlich (HTTPException mit detail)
|
||
[ ] Neue Tabellen haben created_at + updated_at
|
||
[ ] Import-Tabellen haben source-Feld
|
||
[ ] api.js für alle Frontend API-Calls
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Prod-Schutz & Dev-Zugriff
|
||
|
||
### 7.1 Absoluter Prod-Schutz
|
||
Claude Code darf auf dem Prod-System (mitai.jinkendo.de) NIEMALS:
|
||
- Container neustarten (`docker restart mitai-*`)
|
||
- Schreibend in Container ausführen (`docker exec mitai-api ...`)
|
||
- Dateien direkt ändern (`/home/lars/docker/bodytrack/`)
|
||
- Prod-Datenbank schreiben (nur SELECT erlaubt)
|
||
|
||
**Prod-Änderungen ausschließlich über:**
|
||
```
|
||
git push origin develop → Gitea PR → Merge → deploy-prod.yml
|
||
```
|
||
|
||
### 7.2 Dev-System – voller Zugriff erlaubt
|
||
Claude Code darf auf dem Dev-System (dev.mitai.jinkendo.de):
|
||
- Container neustarten (`docker restart dev-mitai-*`)
|
||
- Logs lesen und filtern
|
||
- DB lesen und schreiben (für Tests)
|
||
- Container neu bauen
|
||
|
||
### 7.3 Test-Umgebung
|
||
- API-Tests: gegen http://dev.mitai.jinkendo.de
|
||
- Playwright-Tests: gegen https://dev.mitai.jinkendo.de
|
||
- Screenshots: in `screenshots/` Ordner (in .gitignore)
|
||
- Test-Credentials: in Umgebungsvariablen (TEST_EMAIL, TEST_PASSWORD)
|
||
- NIEMALS Test-Credentials in Code committen
|
||
|
||
### 7.4 Erkennungsmerkmale Prod vs. Dev
|
||
```
|
||
Prod-Container: mitai-api, mitai-ui, mitai-db-prod
|
||
Dev-Container: dev-mitai-api, dev-mitai-ui, dev-mitai-postgres
|
||
|
||
Prod-Ports: 8002 (Backend), 3002 (Frontend)
|
||
Dev-Ports: 8099 (Backend), 3099 (Frontend)
|
||
|
||
Prod-URL: mitai.jinkendo.de
|
||
Dev-URL: dev.mitai.jinkendo.de
|
||
```
|
||
|
||
---
|
||
|
||
## 8. CSV-Import vs. Data Layer (Issue #53)
|
||
|
||
### 8.1 Leitlinie: Wo Interpretation stattfindet
|
||
|
||
| Schicht | Erlaubt | Nicht Sinn der Schicht |
|
||
|--------|---------|-------------------------|
|
||
| **Import (Ingest)** | Zuordnung CSV→Speicherfeld, **Typ-/Einheits-Konvertierung** (`type_conversions`), Duplikat-/Constraint-Logik | Fachliche **Interpretation**, Aggregation von „Bedeutung“, Metriken für Auswertung |
|
||
| **Data Layer (Issue #53, Layer 1+)** | Daten lesen, aufbereiten, ableiten, für Charts/KI/Prompts bereitstellen | — |
|
||
|
||
Verbindlich: **Semantik und Auswertung** nicht dauerhaft im Import verstecken; neue Features werden an dieser Grenze geprüft.
|
||
|
||
**Detail & Zielbild (Multi-Layer, Single Source of Truth):** `docs/issues/issue-53-phase-0c-multi-layer-architecture.md`
|
||
|
||
**Umsetzung Schlaf-Import (Refactoring, Offen):** Gitea http://192.168.2.144:3000/Lars/mitai-jinkendo/issues/69
|
||
|
||
### 8.2 Ist-Einordnung Import-Pfade (Übergang)
|
||
|
||
Bis sukzessive auf das Zielbild umgestellt ist, gilt:
|
||
|
||
| Pfad | Einordnung |
|
||
|------|----------------|
|
||
| Universal-CSV (`csv_parser`, `routers/csv_import.py`, Executor für u. a. Gewicht/Ernährung/Blutdruck/Aktivität/Vitals) | **Zielrichtung:** Mapping + Typkonvertierung |
|
||
| Apple-Schlaf-Aggregat (`csv_parser/sleep_apple_import.py`, `import_mode: apple_sleep_aggregate`) | **Legacy-Adapter** (quellenspezifische Aufbereitung) – Austausch gegen mapping-nah + Layer 1 geplant |
|
||
| Dedizierte Import-Endpoints (z. B. `/api/activity/import-csv`, Vitals Apple) | **Legacy/Parallel** – neue Quellen bevorzugt über Universal-Pfad + Vorlagen |
|
||
|
||
Änderungen an Import-Pfaden: Legacy nur erweitern mit **expliziter** Issue-/Review-Begründung; kein neues „wir rechnen Auswertung beim Insert“ ohne Data-Layer-Bezug.
|
||
|
||
---
|
||
|
||
## 9. Test-Regeln
|
||
|
||
### 9.1 Tests schreiben ist Pflicht
|
||
Jedes neue Feature bekommt mindestens einen Playwright-Test in
|
||
`tests/dev-smoke-test.spec.js`.
|
||
|
||
### 9.2 Reihenfolge: Test vor Commit
|
||
```
|
||
Implementieren → Tests schreiben → Tests grün → Committen
|
||
NIEMALS: Implementieren → Committen → Tests später
|
||
```
|
||
|
||
### 9.3 Claude Code schreibt Tests selbst
|
||
Nach jeder Implementierung:
|
||
1. Passende Tests in dev-smoke-test.spec.js ergänzen
|
||
2. `npx playwright test` ausführen
|
||
3. Fehler korrigieren bis alle Tests grün
|
||
4. Erst dann committen
|
||
|
||
### 9.4 Test-Kategorien
|
||
```javascript
|
||
// UI-Test (Playwright)
|
||
test('FEATURE: Beschreibung', async ({ page }) => { ... })
|
||
|
||
// API-Test (Playwright request)
|
||
test('API: Endpoint', async ({ request }) => { ... })
|
||
```
|
||
|
||
### 9.5 Screenshots bei Fehlern
|
||
Fehlgeschlagene Tests erzeugen automatisch Screenshots in:
|
||
`test-results/TESTNAME/test-failed-1.png`
|
||
→ Immer ansehen bevor Code geändert wird
|
||
|
||
### 9.6 Prod nie testen
|
||
Tests laufen IMMER gegen dev.mitai.jinkendo.de
|
||
NIEMALS gegen mitai.jinkendo.de
|
||
|
||
---
|
||
|
||
## 10. Dashboard-Lab-Widgets und Feature-System
|
||
|
||
**Kontext:** Dashboard-Widgets (`backend/widget_catalog.py`, Lab unter `/api/app/...`) und das **Subscription-/Feature-Modell** (`features`, `tier_limits`, `check_feature_access` in `backend/auth.py`) sind **getrennte Schichten**, müssen aber bei tariffrelevanten Widgets **verknüpft** werden.
|
||
|
||
**Bindend:**
|
||
|
||
1. **Keine fest codierten Tier-Namen** für Widget-Rechte – Tiers und Limits kommen aus der DB.
|
||
2. **Komplexität** (Module aus, Unter-Stufen, KI vs. Standard) liegt in der **Feature-/Subscription-Logik**, nicht verteilt in Widget-Komponenten.
|
||
3. **Nutzer-Konfigurator** (z. B. Dashboard-Lab): Widgets **ohne** passende Berechtigung **nicht anzeigen**; alle erlaubten Widgets bleiben verfügbar.
|
||
4. **Backend** liefert die effektive Erlaubnis (z. B. über erweiterten Katalog oder Entitlements), und **validiert beim Speichern** des Layouts, dass keine unerlaubten Widget-IDs persistiert werden (Policy: ablehnen oder strippen – einheitlich halten).
|
||
5. **Daten/API:** Zusätzlich zur UI-Filterung müssen die **inhaltsliefernden Endpoints** weiterhin über `check_feature_access` geschützt sein (kein Leck über direkte API-Aufrufe).
|
||
|
||
**Detail-Doku (Checklisten, Dateipfade):** `.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` § 0.
|