- .gitignore: .claude/docs, rules, commands tracken; settings.local weiter ignorieren - DOCUMENTATION.md: verbindliche Ablage functional/technical/working/issues - .claude/README.md: Agent-Einstieg; GITEA_ISSUES_INDEX aus MCP (Stand 2026-04-08) - Arbeitspapiere von docs/ nach .claude/docs/working/ verschoben - docs/MEMBERSHIP_SYSTEM.md als Stub; kanonisch technical/MEMBERSHIP_SYSTEM.md - CLAUDE.md Pflichtlektüre und Links angepasst; docs/README.md vereinfacht Made-with: Cursor
11 KiB
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_accessinbackend/auth.py). Siehe.claude/docs/architecture/FEATURE_ENFORCEMENT.md. - Umgesetzt:
GET /api/app/widgets/catalogliefert pro Widgetallowed(ausrequires_featureim Katalog +check_feature_access).GET/PUT /api/app/dashboard-layoutwendetapply_entitlements_to_layout_dictan (nicht erlaubte Einträge:enabled: false; Standard-Layout ebenfalls bereinigt). Implementierung:backend/dashboard_widget_entitlements.py. - Optional pro Katalogzeile:
requires_feature(features.id) inwidget_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 ausfeatures.id) oder ein Cluster-Key ergänzt werden, der auf eine oder mehrerecheck_feature_access-Abfragen abgebildet wird – Details bei Implementierung festlegen. - Neue Widget-Doku: Wenn ein Widget an ein Feature hängt, in
widget_catalog-descriptionund in dieser Anleitung vermerken.
Verweis: Verbindliche Regel auf Projektebene: .claude/rules/ARCHITECTURE.md § 9.
1. Datenfluss (kurz)
backend/widget_catalog.py–WIDGET_CATALOG: erlaubte Widget-IDs, Reihenfolge, Titel/Beschreibung für API und Default-Layout.backend/dashboard_layout_schema.py–DashboardLayoutPayload: jede Zeile hatid,enabled, optionalconfig. IDs müssen inALLOWED_WIDGET_IDSsein (aus dem Katalog abgeleitet).backend/dashboard_widget_config.py–validate_widget_entry_config: nur Widgets inWIDGETS_ALLOWING_CONFIGdürfen nicht-leereconfighaben; Keys werden streng validiert (unbekannte Keys → Fehler).- Frontend –
ensurePilotLabWidgetsRegistered()infrontend/src/widgetSystem/registerPilotLabWidgets.js: verbindet jede Katalog-ID mit einer React-Komponente und mapptctx.layoutEntry.configauf Props. - 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
WIDGETS_ALLOWING_CONFIGinbackend/dashboard_widget_config.pyum die neuewidget_idergänzen.- In
validate_widget_entry_configeinen eigenen Zweig oder Aufruf einer Hilfsfunktion hinzufügen (siehe bestehende Muster unten). MAX_WIDGET_CONFIG_JSON_BYTES(3072): keine großen Blobs inconfig.- 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).
- Unbekannte Keys ablehnen (wie bei
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.
- 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:
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 inCHART_DAYS_WIDGET_IDS(Set oben in der Datei) + bestehendes Label/aria-label-Pattern für die Zeitraum-Zeile erweitern (siehebody_overview,nutrition_detail_charts). - Strukturierte Config (Listen, mehrere Booleans): Eigenes Editor-Komponenten-File nach Vorbild
KpiBoardConfigEditor.jsx/QuickCaptureConfigEditor.jsxeinbinden undsetLayout+normalizeLayoutForEditorwie 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.allowedje Widget (Auth +X-Profile-Idwie 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 aufenabled: falsegesetzt).
6. Nach getaner Arbeit
pytestfürdashboard_widget_configundwidget_catalog/dashboard_layout_schema.npm run build.MODULE_VERSIONS["app_dashboard"]inbackend/version.pyanheben.
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 |