Ziel: Ein neues Dashboard-Widget **end-to-end** korrekt einbinden (Backend-Katalog, Validierung, API-Layout, Frontend-Registrierung, optional Editor für `config` in **Übersicht anpassen**).
Kontext: Geschützte Endpoints `GET/PUT /api/app/...` (siehe `backend/routers/app_dashboard.py`). Layout liegt pro Profil in `profiles.dashboard_layout` (JSON). Nutzer-Oberfläche: `frontend/src/pages/DashboardConfigurePage.jsx` (Route z.B. `/settings/dashboard-layout`).
## 0. Architekturanforderung: Subscription / Feature-System vs. Widget-Katalog
### 0.1 Ist-Stand (Verifikation)
- **Bereits vorhanden:** Membership- und Feature-Modell (`features`, `tier_limits`, `user_feature_restrictions`, `check_feature_access` in `backend/auth.py`). Siehe `.claude/docs/architecture/FEATURE_ENFORCEMENT.md`.
- **Umgesetzt:** `GET /api/app/widgets/catalog` liefert pro Widget **`allowed`** (aus `requires_feature` im Katalog + `check_feature_access`). `GET/PUT /api/app/dashboard-layout` wendet **`apply_entitlements_to_layout_dict`** an (nicht erlaubte Einträge: `enabled: false`; Standard-Layout ebenfalls bereinigt). Implementierung: `backend/dashboard_widget_entitlements.py`.
- Optional pro Katalogzeile: **`requires_feature`** (`features.id`) in `widget_catalog.py`; fehlt der Key → Widget für alle authentifizierten Nutzer katalog-sichtbar (ohne zusätzliches Feature-Gate).
### 0.2 Soll: eine Wahrheit für „darf angezeigt werden“
- **Komplexität** (Module aus, Cluster, Stufen: z.B. Ernährung an, aber bestimmte Auswertungen nur in höherem Tier) gehört in die **Feature-/Subscription-Schicht** (inkl. späterer Feature-Cluster), nicht in einzelne React-Widgets.
- **Widgets** sollen das Ergebnis nur **abrufen** (z.B. `allowed` / sichtbar im Katalog), nicht die Tier-Logik duplizieren.
| **A1 – Zentrale Auflösung** | Backend ermittelt pro Profil (effektiver Tier + Restrictions), welche Widget-IDs **erlaubt** sind – idealerweise in **einer** Stelle (Erweiterung des Katalog-Endpoints oder dedizierter Entitlements-Teil der Response). Intern: `check_feature_access` und später ggf. Mapping Widget-ID → Feature-ID(n) / Cluster. |
| **A2 – Nutzer-Konfigurator** | Im Layout-Konfigurator (**Übersicht anpassen**): Widgets **ohne Berechtigung nicht anbieten** (ausgeblendet oder gar nicht in der Liste). Alle **erlaubten** Widgets bleiben wie heute wählbar. |
| **A3 – Layout-Persistenz** | `PUT /api/app/dashboard-layout`: Layout darf **keine** nicht erlaubten Widgets dauerhaft speichern – entweder **ablehnen** (422) oder **beim Speichern entfernen/deaktivieren** (Policy festlegen und dokumentieren). Verhindert „gespeichert, aber nie sichtbar“-Zombies. |
| **A4 – API-/Datenschutz** | Sichtbarkeit im UI reicht nicht: Endpoints, die **Inhalte** für gated Widgets liefern (Charts, KI, …), müssen weiterhin wie heute **eigenständig** über Features abgesichert sein (`check_feature_access`, 403). |
### 0.4 Katalog-Erweiterung (Vorbereitung ohne feste Tier-Namen)
- Tiers bleiben **in der DB konfigurierbar**; im Code keine Annahme „free vs. pro“.
- Pro Widget-Eintrag (oder separater Mapping-Layer) kann später **`required_feature_id`** (ein Key aus `features.id`) oder ein **Cluster-Key** ergänzt werden, der auf **eine oder mehrere**`check_feature_access`-Abfragen abgebildet wird – Details bei Implementierung festlegen.
- Neue Widget-Doku: Wenn ein Widget an ein Feature hängt, in `widget_catalog`-`description` und in dieser Anleitung vermerken.
**Verweis:** Verbindliche Regel auf Projektebene: `.claude/rules/ARCHITECTURE.md` § 9.
---
## 1. Datenfluss (kurz)
1.**`backend/widget_catalog.py`** –`WIDGET_CATALOG`: erlaubte Widget-IDs, Reihenfolge, Titel/Beschreibung für API und Default-Layout.
2.**`backend/dashboard_layout_schema.py`** –`DashboardLayoutPayload`: jede Zeile hat `id`, `enabled`, optional `config`. IDs müssen in `ALLOWED_WIDGET_IDS` sein (aus dem Katalog abgeleitet).
3.**`backend/dashboard_widget_config.py`** –`validate_widget_entry_config`: **nur** Widgets in `WIDGETS_ALLOWING_CONFIG` dürfen **nicht-leere**`config` haben; Keys werden streng validiert (unbekannte Keys → Fehler).
4.**Frontend**–`ensureDashboardWidgetsRegistered()` in `frontend/src/widgetSystem/registerDashboardWidgets.js`: verbindet jede Katalog-ID mit einer React-Komponente und mappt `ctx.layoutEntry.config` auf Props.
5.**Layout-Editor (Produkt)**–`frontend/src/pages/DashboardConfigurePage.jsx`: Umsortieren, Ein/Aus, Speichern; **zusätzliche** UI nur nötig, wenn das Widget konfigurierbare Felder braucht.
| B | `backend/widget_catalog.py` | Optional: ID zu `DEFAULT_LAB_WIDGET_IDS` hinzufügen, wenn es im Server-Standardlayout **aktiv** sein soll (Feld `lab_default_layout` in der Layout-API). |
| C | `frontend/src/components/dashboard-widgets/MyWidget.jsx` (oder Legacy-Widget unter `dashboard-widgets-legacy/`) | React-Komponente implementieren; typischerweise `refreshTick` aus `mapProps` nutzen, um Daten neu zu laden. |
| D | `frontend/src/widgetSystem/registerDashboardWidgets.js` | `import` + `registerDashboardWidget({ id, Component, mapProps })`–`id`**exakt** wie im Katalog. |
Neue komplexe Config: eigene `_validate_my_widget_config` schreiben, Keys als `frozenset` whitelisten, Typen prüfen, sinnvoll normalisieren/abrunden.
5.**Tests** in `backend/tests/test_dashboard_widget_config.py`: Happy-Path, ein ungültiger Wert, unbekannter Key, ggf. Größe/Limits.
### 3.2 Katalog-Beschreibung
In `widget_catalog.py` bei `description` die **konfigurierbaren Keys** kurz nennen (hilft Admin/API-Nutzern). Einheitliche Benennung mit dem Backend (z.B. `chart_days 7–90`).
### 3.3 Frontend: Props aus Layout
`registerDashboardWidget` erhält `mapProps(ctx)`:
- **`ctx.layoutEntry`**: `{ id, enabled, config? }`– hierher kommt die gespeicherte Konfiguration.
- **`ctx.refreshTick`** / **`ctx.requestRefresh()`**: Datenaktualisierung nach Aktionen.
Typische Zuordnung:
```javascript
mapProps: (ctx) => ({
refreshTick: ctx.refreshTick,
myOption: ctx.layoutEntry?.config?.my_option,
})
```
**Abgleich mit Chart-Zeitraum:** Für `chart_days` existiert `frontend/src/widgetSystem/bodyChartDays.js` (`BODY_CHART_DAYS_MIN/MAX`, `normalizeBodyChartDays`). Entweder in `mapProps` normalisieren (wie `body_overview`) oder rohen Wert durchreichen und in der Widget-Komponente normalisieren (wie `nutrition_detail_charts` / `TrendKcalWeightWidget`) –**beides** ist im Projekt vertreten; wichtig ist Konsistenz mit der Backend-Grenze 7–90.
- **Strukturierte Config (Listen, mehrere Booleans):** Eigenes Editor-Komponenten-File nach Vorbild `KpiBoardConfigEditor.jsx` / `QuickCaptureConfigEditor.jsx` einbinden und `setLayout` + `normalizeLayoutForEditor` wie bei den bestehenden Blöcken verwenden.
Nach Speichern ruft die Seite `api.putAppDashboardLayout(layout)` auf; das Backend validiert über `DashboardLayoutPayload` → `validate_widget_entry_config`.
---
## 4. Grenzen und Fehlerbilder
| Thema | Detail |
|--------|--------|
| Erlaubte IDs | Nur IDs aus `WIDGET_CATALOG`. `ALLOWED_WIDGET_IDS` wird daraus abgeleitet – nicht manuell duplizieren. |
| Doppelte IDs | Im Layout sind **keine** doppelten `widget.id` erlaubt (`DashboardLayoutPayload`). |