mitai-jinkendo/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md
Lars 141df021c1
All checks were successful
Deploy Development / deploy (push) Successful in 1m2s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
refactor: rename Dashboard-Lab-Widgets to Dashboard-Widgets and update related documentation
- Renamed references from "Dashboard-Lab-Widgets" to "Dashboard-Widgets" across documentation and codebase for consistency.
- Removed the deprecated Dashboard-Lab page and integrated its functionality into the new Dashboard-Widgets layout.
- Updated widget registration and configuration handling to reflect the new naming convention.
- Adjusted documentation in `.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` and other related files to ensure clarity on the updated structure.
- Bumped application version to reflect these changes.
2026-04-23 16:18:10 +02:00

164 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Dashboard-Widgets Anleitung für Coding-Agenten
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.
### 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 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.
---
## 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 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. |
| 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` (790) | 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 790`).
### 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 790.
### 3.4 Layout-Editor (`DashboardConfigurePage.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 `DashboardConfigurePage.jsx`) + 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` (Servertemplate für Editor/Reset; Feldname historisch).
- `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` |
| Dashboard-Widget-Registrierung | `frontend/src/widgetSystem/registerDashboardWidgets.js` |
| Layout-Editor (Nutzer) | `frontend/src/pages/DashboardConfigurePage.jsx` |