# Dashboard-Lab-Widgets – Anleitung für Coding-Agenten

Ziel: Ein neues Dashboard-Widget **end-to-end** korrekt einbinden (Backend-Katalog, Validierung, API-Layout, Frontend-Registrierung, optional Lab-Editor für `config`).  
Kontext: **Dashboard-Lab** unter geschützten Endpoints `GET/PUT /api/app/...` (siehe `backend/routers/app_dashboard.py`). Layout liegt pro Profil in `profiles.dashboard_layout` (JSON).

---

## 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.

### 0.3 Bindende Anforderungen (wenn Feature-Gating umgesetzt wird)

| Anforderung | Beschreibung |
|-------------|--------------|
| **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 Dashboard-Lab (und jedem späteren Layout-Konfigurator): 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** – `ensurePilotLabWidgetsRegistered()` in `frontend/src/widgetSystem/registerPilotLabWidgets.js`: verbindet jede Katalog-ID mit einer React-Komponente und mappt `ctx.layoutEntry.config` auf Props.
5. **Dashboard-Lab-UI** – `frontend/src/pages/DashboardLabPage.jsx`: Umsortieren, Ein/Aus, Speichern; **zusätzliche** UI nur nötig, wenn das Widget konfigurierbare Felder braucht.

---

## 2. Checkliste: neues Widget ohne Konfiguration

| Schritt | Datei | Aktion |
|--------|--------|--------|
| A | `backend/widget_catalog.py` | Neuen Eintrag `{ "id", "title", "description" }` in `WIDGET_CATALOG` einfügen (Reihenfolge = Default-Reihenfolge im Layout). Optional `"requires_feature": "<features.id>"` für Tarif-Gating (`dashboard_widget_entitlements`). |
| B | `backend/widget_catalog.py` | Optional: ID zu `DEFAULT_LAB_WIDGET_IDS` hinzufügen, wenn es im Standard-Lab **aktiv** sein soll. |
| C | `frontend/src/components/dashboard-widgets/MyWidget.jsx` (oder Pilot-Komponente) | React-Komponente implementieren; typischerweise `refreshTick` aus `mapProps` nutzen, um Daten neu zu laden. |
| D | `frontend/src/widgetSystem/registerPilotLabWidgets.js` | `import` + `registerDashboardWidget({ id, Component, mapProps })` – `id` **exakt** wie im Katalog. |
| E | `backend/tests/test_widget_catalog.py` | Läuft implizit mit; bei Strukturänderungen Katalog-Tests beachten. |
| F | `backend/version.py` | `MODULE_VERSIONS["app_dashboard"]` MINOR erhöhen und kurz kommentieren. |
| G | Build/Tests | `pytest` (z. B. `tests/test_dashboard_layout_schema.py`, `test_widget_catalog.py`); `npm run build` im `frontend`. |

**Nicht nötig:** `WIDGETS_ALLOWING_CONFIG` oder `validate_widget_entry_config`-Zweig, solange `config` immer `{}` bleibt.

**Wichtig:** Widget-IDs im Frontend-Registry **ohne** Registrierung führen im UI zu „Unbekanntes Widget“ (`dashboardWidgetRegistry.jsx`).

---

## 3. Checkliste: Widget mit konfigurierbaren Einstellungen (`config`)

### 3.1 Backend

1. **`WIDGETS_ALLOWING_CONFIG`** in `backend/dashboard_widget_config.py` um die neue `widget_id` ergänzen.
2. In **`validate_widget_entry_config`** einen eigenen Zweig oder Aufruf einer Hilfsfunktion hinzufügen (siehe bestehende Muster unten).
3. **`MAX_WIDGET_CONFIG_JSON_BYTES`** (3072): keine großen Blobs in `config`.
4. Regeln konsistent halten:
   - Unbekannte Keys **ablehnen** (wie bei `kpi_board`, `quick_capture`, `chart_days`-only).
   - Leeres Objekt `{}` erlauben, wenn alle Keys optional sind (Validator entscheidet).

**Referenz-Muster im Code:**

| Muster | Verwendung | Implementierung |
|--------|------------|-----------------|
| Nur `chart_days` (7–90) | Chart-Kacheln | `_validate_chart_days_only(raw, label="...")` |
| KPI-Kacheln | `tiles`-Liste, max. 9 | `_validate_kpi_board_config` |
| Booleans + Mindestens eines true | Schnelleingabe-Sichtbarkeit | `_validate_quick_capture_config` |

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.

### 3.4 Dashboard-Lab-Editor (`DashboardLabPage.jsx`)

Ohne UI-Änderung bleibt `config` beim Nutzer `{}` – konfigurierbare Widgets brauchen **Editor-Controls**:

- **Einfaches Zahlfeld `chart_days`:** Eintrag in `CHART_DAYS_WIDGET_IDS` (Set oben in der Datei) + bestehendes Label/`aria-label`-Pattern für die Zeitraum-Zeile erweitern (siehe `body_overview`, `nutrition_detail_charts`).
- **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`). |
| Max. Widgets | `widgets` max. 32 Einträge (`DashboardLayoutPayload`). |
| Config verboten | Widget **nicht** in `WIDGETS_ALLOWING_CONFIG` → jede nicht-leere `config` → Validierungsfehler beim Speichern. |
| Frontend ≠ Katalog | Komponente registriert, ID fehlt im Katalog → PUT schlägt fehl. |
| Katalog ohne Registry | GET Layout ok, Render zeigt „Unbekanntes Widget“. |

---

## 5. API zum Prüfen

- `GET /api/app/widgets/catalog` – Katalog inkl. `allowed` je Widget (Auth + `X-Profile-Id` wie andere App-Endpoints).
- `GET /api/app/dashboard-layout` – `layout` (effektiv, bereinigt), `custom`, `product_default_layout` (Übersichts-Standard), `lab_default_layout` (Dashboard-Lab-Standard).
- `PUT /api/app/dashboard-layout` – Body `{ "version": 1, "widgets": [ ... ] }` (unerlaubte Widgets werden auf `enabled: false` gesetzt).

---

## 6. Nach getaner Arbeit

- `pytest` für `dashboard_widget_config` und `widget_catalog` / `dashboard_layout_schema`.
- `npm run build`.
- `MODULE_VERSIONS["app_dashboard"]` in `backend/version.py` anheben.

---

## 7. Verwandte Dateien (Referenz)

| Zweck | Pfad |
|--------|------|
| Katalog | `backend/widget_catalog.py` |
| Config-Validierung | `backend/dashboard_widget_config.py` |
| Layout-Pydantic | `backend/dashboard_layout_schema.py` |
| HTTP | `backend/routers/app_dashboard.py` |
| Registry + Render | `frontend/src/widgetSystem/dashboardWidgetRegistry.jsx` |
| Pilot/Lab-Registrierung | `frontend/src/widgetSystem/registerPilotLabWidgets.js` |
| Lab-UI | `frontend/src/pages/DashboardLabPage.jsx` |
