diff --git a/docs/superpowers/specs/2026-09-13-settings-design.md b/docs/superpowers/specs/2026-09-13-settings-design.md new file mode 100644 index 0000000..55ec9bd --- /dev/null +++ b/docs/superpowers/specs/2026-09-13-settings-design.md @@ -0,0 +1,155 @@ +# JARVIS Einstellungen - Design + +**Datum:** 2026-09-13 +**Status:** Approved, bereit fuer Implementierungsplan + +## Kontext + +JARVIS hat aktuell keine editierbaren Identitaets-/Branding-Werte. Der +Assistenten-Name ("JARVIS") und die Einleitung im System-Prompt +("Du bist JARVIS, ein KI-Assistent fuer Business-Automatisierung.") stehen +fest im Code bzw. als `CLAUDE_SYSTEM_PROMPT`-Env-Var-Default (`main.py:58-61`) +- letztere wird im Deployment nirgends gesetzt, ist also faktisch immer der +hartcodierte Default. Es gibt weder eine Backend-Tabelle noch eine +Frontend-Ansicht, um das zu aendern. Feature-Wunsch +(`.claude/JARVIS_FEATURES.md`, Punkt 9): ein Einstellungen-Menuepunkt mit +Werten wie E-Mailadresse, Firmenname, Name des Assistenten. + +Dies ist Feature 2 von 3 einer priorisierten Roadmap (Feature 1, +Bestellungen erfassen via Nextcloud Deck, ist fertig implementiert und +deployt). + +## Scope-Entscheidungen (im Brainstorming geklaert) + +- **E-Mailadresse ist ein reiner Anzeige-/Branding-Wert.** Das technische + Postfach (`EMAIL_USER`/`EMAIL_PASSWORD`, IMAP/SMTP-Zugangsdaten fuer die + echten Mail-Tools) bleibt unveraendert ein Deployment-Secret in `.env` - + diese Einstellung dient nur dazu, dass Claude bei Bedarf eine + Kontakt-Adresse nennen kann, nicht um das tatsaechliche Postfach zu wechseln. +- **Speicherort: neue Postgres-Tabelle**, nicht `.env`. Aenderungen wirken + sofort auf den naechsten Chat-Request, kein Deploy/Neustart noetig. +- **Assistenten-Name wirkt auch im Frontend** (Seitentitel, Chat-Header/ + Platzhalter), nicht nur im Chat-Verhalten selbst. +- **Aenderung nur ueber die Einstellungen-Seite**, kein zusaetzliches + Chat-Tool (anders als z.B. `remember_fact`) - Einstellungen werden selten + geaendert, ein dediziertes Formular reicht. +- **Bewusste Luecke:** Der Login-Screen (`Login.tsx`, `

JARVIS

`) + bleibt statisch. Settings liegen hinter `X-Admin-Key` wie alle anderen + `/api/v1/*`-Routen und sind vor dem Login nicht abrufbar - Branding gilt + nur fuer den eingeloggten Bereich. + +## Datenmodell (`Claude outputs/migrations/007_settings.sql`) + +```sql +CREATE TABLE settings ( + key VARCHAR(100) PRIMARY KEY, + value TEXT NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +Reine Key-Value-Tabelle, keine Seed-Daten. Ein fester Katalog in `main.py` +definiert die gueltigen Keys und ihre Defaults: + +```python +SETTINGS_DEFAULTS = { + "assistant_name": "JARVIS", + "company_name": "MBO-Tech-IT", + "contact_email": "kontakt@mbo-tech-it.de", +} +``` + +Fehlt ein Key in der Tabelle, gilt der Default - die Tabelle kann also leer +bleiben, bis der Nutzer zum ersten Mal etwas aendert, und das Verhalten +bleibt exakt wie heute. + +## DB-Helper (analog `memory_facts`-Pattern aus main.py) + +- `get_setting(key: str) -> str` - `SELECT value FROM settings WHERE key = %s`, + faellt auf `SETTINGS_DEFAULTS[key]` zurueck, wenn keine Zeile existiert. +- `get_all_settings() -> dict` - liest alle vorhandenen Zeilen, merged sie + ueber `SETTINGS_DEFAULTS` (Defaults zuerst, dann DB-Werte drueber) und + gibt ein vollstaendiges Dict mit allen drei Keys zurueck. +- `set_setting(key: str, value: str)` - `INSERT ... ON CONFLICT (key) DO + UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP`. + +## REST-Endpoints + +``` +GET /api/v1/settings -> {"assistant_name": "...", "company_name": "...", "contact_email": "..."} +PUT /api/v1/settings Body: beliebige Teilmenge der drei Keys -> aktualisiertes vollstaendiges Dict +``` + +Beide hinter `dependencies=[Depends(require_admin_key)]`, wie alle +bestehenden `/api/v1/*`-Routen ausser `/health`. `PUT` validiert jeden +Key gegen `SETTINGS_DEFAULTS.keys()` - unbekannter Key -> `400`. + +## Einbindung in den Chat (`run_chat_completion`) + +Die bisher statische Identitaets-Zeile (`CLAUDE_SYSTEM_PROMPT`-Env-Var) +wird ersetzt durch einen dynamisch aus den Settings gebauten Text, der bei +**jedem** Chat-Aufruf frisch berechnet wird (gleiches Prinzip wie der +bestehende `_current_datetime_context()`): + +```python +async def _identity_system_prompt() -> str: + settings = await get_all_settings() + return ( + f"Du bist {settings['assistant_name']}, der KI-Assistent von " + f"{settings['company_name']}. Bei Fragen zur Erreichbarkeit kannst " + f"du auf {settings['contact_email']} verweisen." + ) +``` + +Die `CLAUDE_SYSTEM_PROMPT`-Env-Var und ihr Default-Text entfallen komplett +(im Deployment ohnehin nirgends gesetzt) - die Settings-Tabelle ist ab jetzt +die einzige Quelle fuer die Identitaets-Zeile. + +## Frontend + +- Neue Komponente `web/src/components/Settings.tsx`: Formular mit drei + Textfeldern (Assistentenname, Firmenname, Kontakt-E-Mail) + + "Speichern"-Button. Laedt aktuelle Werte per `GET /api/v1/settings` beim + Mounten (Pattern wie `Dashboard.tsx`), speichert per `PUT`. +- `App.tsx`: `View`-Type um `"settings"` erweitert, neuer Nav-Button + "Einstellungen". Laedt die Settings einmalig nach dem Login, setzt + `document.title` auf `assistant_name` und reicht den Namen als Prop an + `Chat.tsx` durch, das damit den bisher festen Platzhaltertext + ("Nachricht an JARVIS...") ersetzt. + +## Fehlerbehandlung + +- `GET /api/v1/settings` schlaegt fehl (DB nicht erreichbar): Frontend + zeigt eine Fehlermeldung im Settings-Formular (gleiches Muster wie + `CalendarWidget`/`EmailWidget`); `App.tsx` faellt beim Titel/Platzhalter + auf den hartcodierten Default ("JARVIS") zurueck, kein harter Fehler. +- `PUT` mit unbekanntem Key: `400`, Formular zeigt die Fehlermeldung an, + nichts wird gespeichert (auch nicht die gueltigen Keys aus demselben + Request - alles oder nichts, kein Teil-Update bei Validierungsfehlern). + +## Testing + +- Unit-Tests (`tests/test_settings.py`, gemockte DB wie bei bestehenden + Tool-Tests) fuer `get_setting`/`get_all_settings`/`set_setting` + (Default-Fallback, Merge-Verhalten, Upsert) und die REST-Endpoints + (Erfolg, unbekannter Key -> 400, fehlender Admin-Key -> 401). +- Test fuer `run_chat_completion`: `_identity_system_prompt()` wird + gemockt, Ergebnis muss im `system`-Parameter des Claude-Aufrufs landen + (gleiches Pattern wie der bestehende Datums-Kontext-Test). +- Frontend: kein neuer Komponenten-Test (Projekt-Konvention, siehe + `CalendarWidget`/`EmailWidget`/`OrdersWidget`), Verifikation ueber + `npm run build`. +- Manueller End-to-End-Test nach Deployment: Einstellungen-Seite oeffnen, + Assistentenname aendern und speichern, pruefen dass Seitentitel und + Chat-Platzhalter sich sofort aktualisieren, im Chat fragen "wie heisst + du?" und pruefen, dass die Antwort den neuen Namen nutzt. + +## Out of Scope (bewusst nicht Teil dieser Phase) + +- Aenderung der Einstellungen per Chat-Tool (siehe Scope-Entscheidungen). +- Aendern des technischen Postfachs (`EMAIL_USER`/`EMAIL_PASSWORD`) ueber + die UI - bleibt Deployment-Secret. +- Dynamisches Branding auf dem Login-Screen (siehe "bewusste Luecke" oben). +- Weitere Einstellungen ueber die drei genannten hinaus (die Tabelle ist + aber generisch genug, dass spaetere Erweiterung nur einen neuen Eintrag + in `SETTINGS_DEFAULTS` + ein Formularfeld braucht, keine Schema-Aenderung).