docs(quick-260928-ujj): Design Mosaik uebernommen, Hintergrund pro Benutzer

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-28 22:23:45 +02:00
parent cb45d2663a
commit 105b66ed8d
5 changed files with 328 additions and 111 deletions
-71
View File
@@ -1,71 +0,0 @@
---
context: default
phase: design-mosaik (keine GSD-Phase)
task: 4
total_tasks: 7
status: in_progress
last_updated: 2026-09-28T16:05:48+02:00
---
# BLOCKING CONSTRAINTS — Read Before Anything Else
- [ ] CONSTRAINT: Dashboard-Reiter NICHT in die Seitenleiste — User hat das am 28.09. ausdrücklich abgelehnt ("gefallen mir so gar nicht"). Reiter bleiben in der Kopfzeile; nicht erneut vorschlagen.
- [ ] CONSTRAINT: Design-Arbeit läuft im Klon `~/projects/tessera-design` (Zweig `design/mosaik`), NICHT auf tessera-ctl main — der User wollte zurück können. Übertragung ins echte Projekt erst nach seiner Freigabe.
**Do not proceed until all boxes are checked.**
## Critical Anti-Patterns
| Pattern | Description | Severity | Prevention Mechanism |
|---------|-------------|----------|---------------------|
| Grösse ändern mit preventCollision | react-grid-layout 2.2.3 verwirft beim Resize w UND h, sobald die neue Grösse irgendeinen Nachbarn berührt — Maus-Wackeln nach unten blockiert das Schmalerziehen | advisory | Constraint `RESIZE_AXIS_FALLBACK` in `dashboard-grid.tsx` (Klon) behalten und bei Übertragung nach main mitnehmen; Bug steckt auch in live 1.4.0 |
| Dunkelmodus vortäuschen | `document.documentElement.classList.add('dark')` ändert next-themes `resolvedTheme` nicht → Hintergründe bleiben hell | advisory | Im Browser über den Theme-Knopf der Kopfzeile umschalten |
<current_state>
Design-Umbau „Mosaik" (Fluent 2 / GitHub-Stil) im Klon `~/projects/tessera-design`, Zweig `design/mosaik`, Stand `8440db8`, Arbeitsbaum sauber. Zweiter Stack läuft: web http://localhost:3100 (im Netz 192.168.200.241:3100), api 3101, eigene DB-Kopie. Vergleichsstand (tessera-ctl main, 3fc33e3) läuft auf Port 3000. Warten auf Rückmeldung des Users zu Runde 3b.
</current_state>
<completed_work>
- Runde 1 (25.09.): Tokens, dunkle App-Leiste, Fluent-Seitenleiste, Anmeldeseite mit Mosaik, Dashboard-Hintergründe (localStorage)
- Runde 2 (26.09.): Modulkacheln grau/Gelb nur aktiv, Begrüssung, Befehlsleiste, Kalender/Favoriten/Notiz ruhiger, leerer Zustand
- Runde 3 (28.09., 8532b63): Favoriten schmaler (Resize-Achsen-Fallback), Ansicht zentriert, Kalender einzeilig + „Heute/Morgen" + defaultH 16, „Blüte" dunkel weg (Ersatz Nebel), Symbol-Chip in Akzentfarbe, Hover-Anheben, gestaffeltes Einblenden
- Runde 3b (28.09., 8440db8): Reiter zurück in die Kopfzeile, Begrüssung zweizeilig unten in der Seitenleiste über „Einklappen"
- Tests: web 602 grün (dashboard/layout/portal), tsc sauber, Biome ohne neue Warnungen
</completed_work>
<remaining_work>
- Rückmeldung des Users zu Runde 3b (Begrüssung in der Seitenleiste, lebendigere Kacheln)
- Nach Freigabe: Design sauber geplant ins echte Projekt übertragen (GSD quick/Phase), Hintergrundwahl pro Benutzer in der DB statt localStorage, CHANGELOG „Unveröffentlicht"
- RESIZE_AXIS_FALLBACK auch für main/live (bei der Übertragung oder vorab als Einzelkorrektur, falls der User das will)
</remaining_work>
<decisions_made>
- Reiter bleiben in der Kopfzeile (User-Ablehnung der Seitenleisten-Variante)
- Begrüssung unten in der Seitenleiste, eingeklappt ausgeblendet
- „Blüte" im Dunkelmodus nicht angeboten (Farbstufen), Ersatz „Nebel"
- Ansicht zentriert belegte Spalten; Bearbeitungsmodus volle Breite
- Separater Klon statt main, damit Rückweg offen bleibt
</decisions_made>
<blockers>
- Keine technischen. Wartet auf User-Rückmeldung.
</blockers>
## Required Reading (in order)
1. Memory `project_design_mosaik.md` — Verlauf aller Runden und User-Wünsche
2. `git -C ~/projects/tessera-design log --oneline 3fc33e3..design/mosaik` — alle Design-Commits
3. `~/projects/tessera-design/.design-shots/r3/` — aktuelle Bildschirmfotos
## Infrastructure State
- tessera-design Stack (web 3100 / api 3101 / db): läuft; nach Code-Änderung `docker compose up -d --build web` im Klon (~2 min)
- tessera-ctl Stack (Port 3000): läuft, Stand 3fc33e3
- Login lokal: admin / admin123
- Zweig `design/mosaik` existiert NUR im Klon (origin = lokales tessera-ctl, nicht gepusht, nicht in Gitea)
<context>
Der User fand Tessera zu trist; Vorbild Microsoft 365/Fluent + GitHub, kein „KI-Stil". Ab Runde 2 keine bunten Farben: Gelb (persönliche Akzentfarbe) oder Grau. Er ist mit dem Design grundsätzlich zufrieden („ansonsten gefällt mir das design gut") und wollte „mehr Leben in den Widgets". Gespräch auf Deutsch, duzen, ganze Sätze, keine Fachbegriffe.
</context>
<next_action>
Start with: User fragen, wie ihm Runde 3b auf http://localhost:3100 gefällt; bei Freigabe die Übertragung von `design/mosaik` nach tessera-ctl main planen.
</next_action>
-37
View File
@@ -1,37 +0,0 @@
{
"version": "1.0",
"timestamp": "2026-09-28T16:05:48+02:00",
"phase": null,
"phase_name": "Design-Umbau Mosaik (kein GSD-Phase, Design-Spielwiese)",
"phase_dir": "~/projects/tessera-design (Klon, Zweig design/mosaik)",
"plan": null,
"task": null,
"total_tasks": null,
"status": "paused",
"completed_tasks": [
{"id": 1, "name": "Runde 1: Fluent-2/GitHub-Look (Tokens, App-Leiste, Seitenleiste, Anmeldeseite, Dashboard-Hintergruende)", "status": "done", "commit": "3c62a1a..76520b2"},
{"id": 2, "name": "Runde 2: Modulsymbole grau/gelb, Begruessung, Befehlsleiste, Kalender/Favoriten/Notiz ruhiger, leerer Zustand", "status": "done", "commit": "2281120,b06a2d1"},
{"id": 3, "name": "Runde 3: Resize-Achsen-Fallback (Favoriten schmaler), Zentrierung, Kalender einzeilig + defaultH 16, Bluete dunkel weg, Symbol-Chip/Hover/Einblenden", "status": "done", "commit": "8532b63"},
{"id": 4, "name": "Runde 3b: Reiter zurueck in Kopfzeile (Seitenleisten-Reiter abgelehnt), Begruessung unten in Seitenleiste", "status": "done", "commit": "8440db8"}
],
"remaining_tasks": [
{"id": 5, "name": "Rueckmeldung des Users zu Runde 3b abwarten (Begruessung in Seitenleiste, lebendigere Kacheln)", "status": "not_started"},
{"id": 6, "name": "Design ins echte Projekt (tessera-ctl main) uebertragen - sauber geplant (gsd-quick/Phase), getestet; Hintergrundwahl dann pro Benutzer in DB statt localStorage", "status": "not_started"},
{"id": 7, "name": "RESIZE_AXIS_FALLBACK auch fuer main/live mitnehmen (Bug: preventCollision verwirft w UND h bei Maus-Wackeln)", "status": "not_started"}
],
"blockers": [],
"async_jobs": [],
"human_actions_pending": [
{"action": "User schaut http://localhost:3100 (bzw. 192.168.200.241:3100) an und gibt Rueckmeldung zu Runde 3b", "context": "Freigabe der Design-Richtung vor Uebertragung ins echte Projekt", "blocking": true}
],
"decisions": [
{"decision": "Dashboard-Reiter bleiben in der Kopfzeile", "rationale": "User: Reiter in der Seitenleiste 'gefallen mir so gar nicht' - nicht wieder vorschlagen", "phase": "design"},
{"decision": "Begruessung zweizeilig unten in der Seitenleiste ueber 'Einklappen', eingeklappt ausgeblendet", "rationale": "User will sie behalten, aber Reiter+Begruessung ueber den Kacheln war zu viel Platz", "phase": "design"},
{"decision": "Hintergrund 'Bluete' im Dunkelmodus nicht angeboten, Ersatz 'mist'", "rationale": "sichtbare Farbstufen (8 Bit) auch mit Korn", "phase": "design"},
{"decision": "Ansicht zentriert belegte Spalten, Bearbeitungsmodus volle Breite", "rationale": "leerer Streifen rechts auf breiten Schirmen; im Bearbeiten muessen freie Spalten erreichbar sein", "phase": "design"},
{"decision": "Design-Arbeit im separaten Klon statt auf main", "rationale": "User wollte 'danach wieder zurueck koennen'", "phase": "design"}
],
"uncommitted_files": [],
"next_action": "Rueckmeldung des Users zu http://localhost:3100 einholen; bei Freigabe Uebertragung von design/mosaik nach tessera-ctl main planen (inkl. RESIZE_AXIS_FALLBACK, Hintergrund pro Benutzer in DB).",
"context_notes": "Klon ~/projects/tessera-design hat origin = lokales tessera-ctl, Zweig design/mosaik ist NICHT nach Gitea gepusht und nur im Klon vorhanden (13 Commits ab 3fc33e3). Stack dort: docker compose (web 3100, api 3101), nach Aenderung 'docker compose up -d --build web' (~2 min). Login admin/admin123. Bildschirmfotos in .design-shots/ (gitignored). tessera-ctl main = origin/main = 3fc33e3, sauber; 1.4.0 live."
}
+4 -3
View File
@@ -7,7 +7,7 @@ status: verified
stopped_at: "22.09.2026: 1.3.0 freigegeben; danach quick-260922-hk4 — Bilderrahmen-Bilder liegen jetzt im Dateibereich (user-files) statt in der Datenbank, Umzug laeuft automatisch beim Start, Selbstheilung aus der alten data-Spalte eingebaut; im Browser nachgewiesen. NAECHSTER SCHRITT, vom Nutzer noch nicht bestaetigt: (1) einmaliges Aufraeumen, damit ein Modul seine Dashboard-Kachel selbst mitbringt (heute sieben Hartkodierungen je Kachel; Katalog zeigt auch Kacheln gesperrter Module; gesperrte Kachel bleibt leer statt zu erklaeren) — das Geruest WIDGET_MODULE_MAP existiert und ist leer; (2) danach das Proxmox-Modul (PVE/PBS/PMG) und seine Kachel. Offen beim Nutzer: Live-Server auf 1.3.0 ziehen, neuen Client per Browser installieren."
last_updated: "2026-09-23T15:30:00.000Z"
last_activity: 2026-09-23
last_activity_desc: Quick 260925-bow — Was-ist-neu-Fenster nach Versionswechsel; 1.4.0 am 25.09. freigegeben und live
last_activity_desc: Quick 260928-ujj — Design Mosaik uebernommen, Hintergrund pro Benutzer in der DB; Freigabe 1.5.0
state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2
progress:
total_phases: 18
@@ -31,7 +31,7 @@ See: .planning/PROJECT.md (updated 2026-07-17)
Phase: 18 (desktop-client-fertigstellen) — COMPLETE (2026-09-17, Verifikation passed, Windows-Bedienprobe bestanden)
Plan: 6 of 6
Status: Alle 18 Phasen abgeschlossen; Version 1.2.0 freigegeben. Kein laufender Meilenstein. Nach 1.2.0 auf main (Beta): Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse, Update in der App (signiert), Versionszeile auf der Setup-Seite — alles verifiziert und auf VM/CI nachgewiesen
Last activity: 2026-09-22 - Quick 260922-ge2: XFrame-Ausschnitt waehlen und einpassen, Zoom, Nur anzeigen (Browser-Befund Rahmenhoehe behoben); davor Desktop: Download-Knoepfe in der App oeffnen jetzt den System-Browser (fast, 747a4d4); davor Quick 260922-frg: Tray-Update-Eintrag nennt den Grund einer fehlgeschlagenen Pruefung (HTTP 401 durch Passwortschutz am Proxy vor alpha), Klick prueft erneut, Pruefung alle 4 h; davor Kosmetik am Bilderrahmen (fast, 8b45a28): „1 Stunde“ statt „60 Minuten“, Bildanzahl in der Einstellungs-Kopfzeile; am 21.09. davor Quick 260921-pi9 und 260921-qd3: die zwei bestellten Dashboard-Widgets „Bilderrahmen“ und „XFrame“ gebaut, im Browser nachgewiesen, gepusht
Last activity: 2026-09-28 - Quick 260928-ujj: Design Mosaik aus dem Klon uebernommen (inkl. Resize-Fix), Dashboard-Hintergrund pro Benutzer in der Datenbank; Freigabe als 1.5.0
Progress: [██████████] 99%
@@ -476,6 +476,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
| 260924-i8v | **Proxmox-Kachel fuers Dashboard.** Modul-Kachel ueber den Weg aus 260922-m1h (Typ `proxmox` in packages/shared + Modulbindung, API-Freigabeliste, Registry, Katalog), nur fuer Benutzer mit Modulzugriff. Kompakter Gesundheitsbalken + Zusammenfassung in Worten, Serverliste nach Dringlichkeit mit je einer Kennzahl (Gaeste/Auslastung, aelteste Sicherung, eingehende Mails, unbekannt nie 0), Links auf /modules/proxmox (nicht im Bearbeitungsmodus), liest jede Minute den Zwischenstand (pausiert bei verborgenem Tab, loest NIE eine Abfrage aus), Titel + Serverauswahl an der Kachel und unter Einstellungen > Dashboard, Groessenstufen per Container-Query. Gemeinsame Teile nach `components/proxmox/` verschoben. Browser: Katalog, Kachel hell/dunkel, schmale Stufe (nur Punkte+Namen). web 864, api 1370 gruen. | 2026-09-24 | a906c67,92bf130,a217d60,377b6e3,586da44,602a45c | [260924-i8v-proxmox-kachel-fuers-dashboard](./quick/260924-i8v-proxmox-kachel-fuers-dashboard/) |
| 260924-m4n | **Flackernden Test entschaerft, alte Bildspalte entfernt.** (1) `tenant-selector.test.tsx`: Ursache war das Laden der Bausteine INNERHALB des ersten Tests (zaehlte in dessen 5-s-Grenze) -> Import vorab, Doppelfall getrennt, dasselbe in zwei weiteren Marktplatz-Tests; langsamster Web-Test jetzt < 2 s (mit 2 Kernen 1,3 s); act()-Warnungen der Proxmox-Kachel weg. (2) DashboardImage Stufe 2: Migration `20260924120000_dashboard_image_drop_data` mit Schutz (bricht ab, wenn noch Zeilen ohne `storagePath`; Zeilenschutz fuer die Pruefung abgeschaltet, sonst saehe sie still 0), `storagePath` NOT NULL, `data` weg, `system_read_policy` weg, Bootstrap-Umzug + `forSystem()` entfernt, Upload legt Zeile gleich mit Pfad an. Vorbedingung alpha geprueft (0 von 3 ohne Pfad); Live nicht pruefbar. Rueckweg bei Abbruch in `docs/anleitung-betrieb.md` Kap. 4. Browser/API: Bilder laden, Upload+Anzeige+Loeschen ok. api 1364, web 865 gruen. | 2026-09-24 | b10734f,dd54ec5 | [260924-m4n-flackernden-test-entschaerfen-und-dashbo](./quick/260924-m4n-flackernden-test-entschaerfen-und-dashbo/) |
| 260925-bow | **Was-ist-neu-Fenster nach Versionswechsel.** Spalte `User.lastSeenReleaseVersion` (Migration 20260925120000), Versionsnummer allein aus der API (`GET /users/me/release-notice`, Semver-Funktionen in packages/shared), Fenster im Portal-Rahmen einmal nach Versionswechsel, gemerkt erst beim Schliessen (`POST`), nur freigegebene Versionen (`dev` nie), hoechstens 3 Versionen + Hinweis auf aeltere + Link /changelog; neue Konten bekommen die laufende Version eingetragen; vorhandene ohne Stand sehen nur die aktuelle. Changelog-Text bleibt serverseitig. Browser: 1.3.0 -> Fenster 1.4.0, Verstanden merkt 1.4.0, kein zweites Mal; 1.0.0 -> 1.4.0/1.3.1/1.3.0 + „2 aelteren Versionen“; Link-Kontrast nachgebessert. api 1435, web 924 gruen. | 2026-09-25 | 59db32a,187fb76,5ae9aaa,b3b7b5d | [260925-bow-was-ist-neu-fenster-beim-ersten-anmelden](./quick/260925-bow-was-ist-neu-fenster-beim-ersten-anmelden/) |
| 260928-ujj | **Design Mosaik uebernommen + Hintergrund pro Benutzer.** Merge design/mosaik (76d17fe, inkl. RESIZE_AXIS_FALLBACK), Spalte `User.dashboardBackground` JSONB (Migration 20260928120000), `PATCH /users/me/dashboard-background` mit `parseDashboardBackground` aus packages/shared (Preset-Liste, imageId nur UUID), Web liest aus Sitzung, alte localStorage-Wahl einmalig uebernommen. Browser: Duenen gewaehlt, DB-Zeile gesetzt, nach localStorage-Loeschen weiter sichtbar. api 1462, web 952 gruen; Freigabe als 1.5.0. | 2026-09-28 | 9fa0a3f,0aaa152,cb45d26 | [260928-ujj-design-mosaik-uebernehmen-und-als-1-5-0-](./quick/260928-ujj-design-mosaik-uebernehmen-und-als-1-5-0-/) |
## Deferred Items
@@ -521,4 +522,4 @@ Last session: 2026-09-22T13:40:00Z
Resumed: 2026-09-21 (abends) ueber /gsd-resume-work; seitdem Bilderrahmen, XFrame (inkl. Ausschnitt), Desktop-Korrekturen, Freigabe 1.3.0, Bilder in den Dateibereich.
Stopped at: hk4 fertig und nachgewiesen. Dem Nutzer vorgelegt: erst das Aufraeumen (Modul bringt seine Kachel selbst mit), dann Proxmox-Modul + Kachel — Antwort steht aus.
Resume file: None
Last activity: 2026-09-22 - Quick 260922-hk4: Bilderrahmen-Bilder im Dateibereich, Selbstheilung aus der alten Spalte
Last activity: 2026-09-28 - Quick 260928-ujj: Design Mosaik aus dem Klon uebernommen (inkl. Resize-Fix), Dashboard-Hintergrund pro Benutzer in der Datenbank; Freigabe als 1.5.0
@@ -0,0 +1,193 @@
---
phase: quick-260928-ujj
plan: 01
quick_id: 260928-ujj
type: execute
wave: 1
depends_on: []
autonomous: true
requirements: [QUICK-260928-ujj]
files_modified:
- apps/web/** (Merge design/mosaik, 115 Dateien, nur apps/web)
- packages/shared/src/index.ts
- apps/api/prisma/schema.prisma
- apps/api/prisma/migrations/20260928120000_user_dashboard_background/migration.sql (neu)
- apps/api/src/auth/auth.service.ts
- apps/api/src/auth/auth.service.spec.ts
- apps/api/src/user/user.controller.ts
- apps/api/src/user/user.controller.spec.ts
- apps/web/src/lib/auth-actions.ts
- apps/web/src/lib/stores/auth-store.ts
- apps/web/src/components/layout/header.tsx
- apps/web/src/components/layout/header.test.tsx
- apps/web/src/lib/dashboard-background.ts
- apps/web/src/lib/dashboard-background.test.ts
- apps/web/src/components/dashboard/dashboard-background.tsx
- apps/web/src/components/dashboard/dashboard-background.test.tsx (neu)
- "apps/web/src/app/(portal)/page.tsx"
- "apps/web/src/app/(portal)/page.test.tsx"
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- docs/mandantentrennung-zugriffsklassifikation.md
- docs/anleitung-anwender.md
- docs/anleitung-entwicklung.md
- CHANGELOG.md
estimate:
tokens: 95000
raw_tokens: 95000
tasks: 3
confidence: low
must_haves:
truths:
- "main enthaelt das freigegebene Design Mosaik als Merge-Commit mit design/mosaik (76d17fe) als zweitem Elternteil; apps/web-Tests sind gruen"
- "Widgets lassen sich schmaler ziehen, auch wenn die Maus dabei leicht wackelt (RESIZE_AXIS_FALLBACK in dashboard-grid.tsx, Test in dashboard-grid.test.tsx gruen)"
- "Der gewaehlte Dashboard-Hintergrund steht pro Benutzer in der Datenbank (User.dashboardBackground) und kommt ueber dieselbe Anmelde-/Sitzungsantwort zurueck wie accentColor — im zweiten Browser erscheint derselbe Hintergrund"
- "PATCH /users/me/dashboard-background nimmt nur 'none', bekannte Preset-Kennungen oder eine UUID-Bildkennung an; alles andere ergibt 400 und schreibt nichts"
- "Eine bereits im localStorage gespeicherte Wahl wird einmalig in die Datenbank uebernommen und der alte Schluessel entfernt"
- "CHANGELOG.md 'Unveroeffentlicht' beschreibt das neue Aussehen (Neu/Geaendert) und den Resize-Fehler (Behoben); die Anwender-Anleitung beschreibt die Hintergrundwahl"
artifacts:
- path: apps/api/prisma/migrations/20260928120000_user_dashboard_background/migration.sql
provides: "Spalte User.dashboardBackground (JSONB, nullable)"
contains: "dashboardBackground"
- path: packages/shared/src/index.ts
provides: "DASHBOARD_BACKGROUND_PRESET_IDS, Typ DashboardBackground, parseDashboardBackground() — eine Pruefregel fuer API und Web"
contains: "parseDashboardBackground"
- path: apps/api/src/user/user.controller.ts
provides: "PATCH me/dashboard-background"
contains: "me/dashboard-background"
- path: apps/web/src/components/dashboard/dashboard-background.tsx
provides: "useDashboardBackground liest aus dem Auth-Store und speichert ueber die Server-Aktion"
key_links:
- from: apps/web/src/components/dashboard/dashboard-background.tsx
to: "PATCH /users/me/dashboard-background"
via: "updateDashboardBackgroundAction in apps/web/src/lib/auth-actions.ts"
pattern: "updateDashboardBackgroundAction"
- from: apps/api/src/auth/auth.service.ts
to: "User.dashboardBackground"
via: "select neben accentColor, Ausgabe durch parseDashboardBackground normalisiert"
pattern: "dashboardBackground: true"
- from: apps/web/src/components/layout/header.tsx
to: apps/web/src/lib/stores/auth-store.ts
via: "setUser-Abbildung uebernimmt dashboardBackground aus der Sitzung"
pattern: "dashboardBackground"
---
<objective>
Das vom Nutzer abgenommene Design „Mosaik“ (Zweig design/mosaik, nur apps/web) in main uebernehmen, die Hintergrundwahl des Dashboards von localStorage auf ein Datenbankfeld pro Benutzer umstellen (Muster accentColor) und CHANGELOG sowie Anleitungen fuer Version 1.5.0 vorbereiten.
Purpose: Das neue Aussehen samt Resize-Fix soll als 1.5.0 ausgeliefert werden; der Hintergrund soll dem Benutzer auf jedem Geraet folgen statt an einem Browser zu kleben.
Output: Merge-Commit, Migration + API-Weg + Web-Anbindung mit Tests, CHANGELOG-/Doku-Eintraege. Release (Tag, live-Zweig, Push) ist NICHT Teil dieses Plans — nicht pushen.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/STATE.md
@CLAUDE.md
@.planning/HANDOFF.json
Fakten (nicht neu herleiten):
- Zweig design/mosaik liegt lokal (Spitze 76d17fe, 15 Commits auf 3fc33e3), aendert nur apps/web (115 Dateien, keine package.json/Lockfile). Merge ist konfliktfrei. Die nicht committete Aenderung an .planning/HANDOFF.json beruehrt der Merge nicht — NICHT stagen, NICHT verwerfen.
- Hintergrund-Datentyp heute in apps/web/src/lib/dashboard-background.ts (Stand design/mosaik): kind 'none' | 'preset' (id aus mist, pebble, bloom, dunes, mosaic) | 'image' (imageId = Kennung eines Bilderrahmen-Bildes, DashboardImage.id ist uuid()). Speicherung per localStorage-Schluessel tessera.dashboardBackground.<userId>.
- Vorbild accentColor: schema.prisma Zeile ~45; Auswahl in apps/api/src/auth/auth.service.ts (~Zeile 320-345, select mit accentColor, speist die Sitzungsantwort); PATCH me/accent-color in apps/api/src/user/user.controller.ts (~Zeile 463-484, forTenant + where id currentUser.id, Inline-Body-Typ); Web: AuthUser in auth-actions.ts und stores/auth-store.ts, updateAccentColorAction in auth-actions.ts (~Zeile 220), setUser-Abbildung in components/layout/header.tsx (~Zeile 47-55).
- Vorbild Migration: apps/api/prisma/migrations/20260925120000_user_last_seen_release/migration.sql (deutscher Kopfkommentar, Hinweis auf auth_lookup_*-Funktionen mit fester Spaltenliste).
- NestJS-Routenreihenfolge: @Patch(':id') steht bei Zeile ~273; zweisegmentige Pfade wie me/accent-color werden davon nicht verschattet — der neue Pfad me/dashboard-background ist ebenfalls zweisegmentig.
- GET /dashboard/images/:id prueft den Besitz (dashboard-images.service.ts) — eine fremde Bildkennung liefert nur 404, deshalb reicht serverseitig die Formatpruefung.
- RLS-Inventar-Test apps/api/src/prisma/rls-access-inventory.spec.ts vergleicht Paare (Datei, Modell) gegen docs/mandantentrennung-zugriffsklassifikation.md; user.controller.ts + user existiert schon gebunden, also kein neues Paar — nur den Zeilentext fortschreiben.
- Lokale DB hat keinen Host-Port: Prisma vom Host ueber die Container-IP (172.19.x, docker inspect) mit tessera:tessera_dev.
- CHANGELOG.md wird vom Was-ist-neu-Fenster geparst: Ueberschriftenformat „## Unveröffentlicht“ / „### Neu|Geändert|Behoben“ exakt beibehalten.
</context>
<tasks>
<task type="auto">
<name>Task 1: Design Mosaik in main mergen</name>
<files>apps/web/** (aus design/mosaik)</files>
<action>Auf main (HEAD a8a910f) pruefen, dass design/mosaik auf 76d17fe steht und `git diff --name-only 3fc33e3 design/mosaik` ausschliesslich apps/web-Pfade zeigt. Dann `git merge --no-ff design/mosaik -m "feat(260928-ujj): Design Mosaik uebernehmen"` ausfuehren (Nachricht kurz, deutsch ohne Umlaute; im Rumpf eine Zeile, dass der Merge den Resize-Achsen-Fallback fuer schmaler gezogene Widgets mitbringt). .planning/HANDOFF.json bleibt unangetastet und ungestaged. Kein pnpm install noetig (keine Abhaengigkeitsaenderung). Danach Web-Tests und Typpruefung laufen lassen; schlaegt etwas fehl, das im Klon gruen war, Ursache beheben und als eigener Commit fix(260928-ujj) nachziehen — nicht in den Merge-Commit falten.</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl && git merge-base --is-ancestor 76d17fe HEAD && grep -q RESIZE_AXIS_FALLBACK apps/web/src/components/dashboard/dashboard-grid.tsx && pnpm --filter web test && pnpm --filter web type-check</automated>
</verify>
<done>Merge-Commit auf main mit 76d17fe als Elternteil; dashboard-grid.tsx enthaelt RESIZE_AXIS_FALLBACK; `pnpm --filter web test` und `pnpm --filter web type-check` gruen; HANDOFF.json weiterhin nur als lokale Aenderung vorhanden.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Dashboard-Hintergrund pro Benutzer in der Datenbank</name>
<files>packages/shared/src/index.ts, apps/api/prisma/schema.prisma, apps/api/prisma/migrations/20260928120000_user_dashboard_background/migration.sql, apps/api/src/auth/auth.service.ts, apps/api/src/auth/auth.service.spec.ts, apps/api/src/user/user.controller.ts, apps/api/src/user/user.controller.spec.ts, apps/web/src/lib/auth-actions.ts, apps/web/src/lib/stores/auth-store.ts, apps/web/src/components/layout/header.tsx, apps/web/src/components/layout/header.test.tsx, apps/web/src/lib/dashboard-background.ts, apps/web/src/lib/dashboard-background.test.ts, apps/web/src/components/dashboard/dashboard-background.tsx, apps/web/src/components/dashboard/dashboard-background.test.tsx, apps/web/src/app/(portal)/page.tsx, apps/web/src/app/(portal)/page.test.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json, docs/mandantentrennung-zugriffsklassifikation.md</files>
<behavior>
- API: PATCH me/dashboard-background mit background {kind:'none'} / {kind:'preset', id:'dunes'} / {kind:'image', imageId:<uuid>} schreibt genau das normalisierte Objekt (Zusatzschluessel entfernt) per forTenant mit where id = currentUser.id und liefert {success:true, dashboardBackground}
- API: unbekannte Preset-Kennung, unbekanntes kind, imageId kein UUID (z. B. 'img-1', mit Anfuehrungszeichen/Klammern, laenger als 36), background null/fehlend/kein Objekt/Array ergibt BadRequestException und kein update-Aufruf
- API: auth.service liefert dashboardBackground neben accentColor; gespeichertes gueltiges Objekt kommt normalisiert zurueck, NULL oder ungueltiger Inhalt kommt als null zurueck
- Web-Lib: takeLegacyDashboardBackground(userId) liefert eine gueltige alte localStorage-Wahl und entfernt den Schluessel; ungueltiger Wert ergibt null und entfernt ebenfalls; gesperrter Speicher ergibt null ohne Ausnahme
- Web-Lib: Preset-Kennungen in BACKGROUND_PRESETS sind deckungsgleich mit DASHBOARD_BACKGROUND_PRESET_IDS aus @tessera/shared
- Web-Hook: background kommt aus user.dashboardBackground im Auth-Store (null ergibt 'none'); choose() setzt den Store sofort und ruft updateDashboardBackgroundAction; bei Fehlschlag wird der vorige Wert zurueckgesetzt
- Web-Hook: ist der Server-Wert null und liegt eine alte localStorage-Wahl vor, wird sie genau einmal gespeichert; ist der Server-Wert gesetzt, passiert keine Uebernahme
</behavior>
<action>Zuerst die Tests aus dem behavior-Block schreiben (rot), dann umsetzen.
Gemeinsame Pruefregel: In packages/shared/src/index.ts DASHBOARD_BACKGROUND_PRESET_IDS (mist, pebble, bloom, dunes, mosaic als const-Tupel), Typ DashboardBackgroundPresetId, Typ DashboardBackground (drei Faelle wie im Web heute) und parseDashboardBackground(value: unknown): DashboardBackground | null ergaenzen. Die Funktion baut immer ein frisches Objekt nur aus den erlaubten Feldern; imageId muss eine UUID (8-4-4-4-12 Hex, Gross/Klein egal) sein — das haelt auch jede CSS-Einschleusung in den spaeteren url("...")-Stil fern. Deutscher Kommentar mit Verweis quick-260928-ujj im Stil der Datei.
API: In schema.prisma am User nach lastSeenReleaseVersion das Feld dashboardBackground Json? mit Kommentar (quick-260928-ujj, null = nie gewaehlt, sonst normalisiertes Objekt inkl. kind none). Migration 20260928120000_user_dashboard_background/migration.sql von Hand im Stil der Vorlage: deutscher Kopfkommentar (Zweck, NULL-Bedeutung, kein Backfill, auth_lookup_* unberuehrt) und ALTER TABLE "User" ADD COLUMN "dashboardBackground" JSONB. Danach `pnpm --filter api exec prisma generate`. Laeuft der lokale Stack, die Migration zusaetzlich per prisma migrate deploy gegen die Container-IP der db einspielen (tessera:tessera_dev, DB-Name aus .env/Compose) — kein Gate. In auth.service.ts im select neben accentColor dashboardBackground aufnehmen und in der Rueckgabe durch parseDashboardBackground normalisieren; auth.service.spec.ts Fixture/Erwartungen (~Zeile 500-540) ergaenzen. In user.controller.ts direkt nach updateAccentColor eine Methode updateDashboardBackground mit @Patch('me/dashboard-background'), Inline-Body-Typ mit background: unknown (wie accent-color, bewusst keine DTO-Klasse — die globale ValidationPipe mit whitelist wuerde verschachtelte Felder sonst nicht pruefen), parseDashboardBackground, bei null BadRequestException('Invalid dashboard background.'), sonst forTenant(...).user.update mit where id currentUser.id und data dashboardBackground; JSDoc mit Bedrohungsverweis T-ujj-01/02. Tests als neuer describe-Block „Dashboard-Hintergrund (quick-260928-ujj)“ in user.controller.spec.ts nach dem Muster des Was-ist-neu-Blocks. In docs/mandantentrennung-zugriffsklassifikation.md die Zeile zu apps/api/src/user/user.controller.ts fortschreiben: seit quick-260928-ujj schreibt der Selbstbedienungsweg PATCH me/dashboard-background dashboardBackground, ebenfalls forTenant mit where id currentUser.id, ohne Kennungsparameter.
Web: AuthUser in auth-actions.ts und stores/auth-store.ts um dashboardBackground?: DashboardBackground | null (Typ aus @tessera/shared) erweitern; updateDashboardBackgroundAction(background) als Server-Aktion nach dem Muster updateAccentColorAction (PATCH /users/me/dashboard-background, Body mit background). header.tsx setUser-Abbildung um dashboardBackground erweitern, header.test.tsx-Fixture nachziehen. apps/web/src/lib/dashboard-background.ts: Typ und Preset-Kennungen aus @tessera/shared beziehen (BackgroundPresetId als Alias behalten, falls genutzt), BACKGROUND_PRESETS/presetBackground bleiben; loadDashboardBackground und saveDashboardBackground ersetzen durch takeLegacyDashboardBackground(userId) (liest, prueft mit parseDashboardBackground, entfernt Schluessel); Kopfkommentar aktualisieren (Speicherung jetzt in der Datenbank, „Prototyp“ entfernen). components/dashboard/dashboard-background.tsx: useDashboardBackground liest user aus useAuthStore statt userId-Parameter; choose() wie im behavior-Block (optimistisch, bei Fehlschlag zuruecksetzen); einmalige Uebernahme der alten Wahl per useRef je Benutzerkennung. Aufrufstelle in app/(portal)/page.tsx anpassen (Kommentar „Prototyp mit localStorage“ ersetzen), page.test.tsx bei Bedarf nachziehen. Hook-Tests in neuer Datei components/dashboard/dashboard-background.test.tsx mit gemocktem @/lib/auth-actions. Hinweistext dashboard.background.hint in de.json auf „Gilt nur für Sie – auf jedem Gerät, auf dem Sie sich anmelden.“ und in en.json sinngemaess („Applies only to you – on every device you sign in on.“) aendern; bestehende Tests, die den alten Text pruefen, anpassen.
Commit(s): test(260928-ujj) fuer die roten Tests, feat(260928-ujj): Dashboard-Hintergrund pro Benutzer in der Datenbank — deutsch ohne Umlaute.</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/shared type-check && pnpm --filter api type-check && pnpm --filter api test && pnpm --filter web type-check && pnpm --filter web test && grep -q '"dashboardBackground" JSONB' apps/api/prisma/migrations/20260928120000_user_dashboard_background/migration.sql && ! grep -v '^\s*//' apps/web/src/lib/dashboard-background.ts | grep -q 'setItem'</automated>
</verify>
<done>Migration und Schemafeld vorhanden, Prisma-Client generiert; PATCH me/dashboard-background prueft und speichert, Sitzungsantwort liefert dashboardBackground; Web liest aus dem Store und speichert ueber die API, alte localStorage-Wahl wird einmalig uebernommen; api- und web-Tests inkl. rls-access-inventory.spec.ts gruen, Typpruefung aller drei Pakete sauber.</done>
</task>
<task type="auto">
<name>Task 3: CHANGELOG, Anleitungen, Abschlusspruefung</name>
<files>CHANGELOG.md, docs/anleitung-anwender.md, docs/anleitung-entwicklung.md</files>
<action>Vorher `git log --format='%h %s%n%b' 3fc33e3..design/mosaik` lesen, um alle sichtbaren Aenderungen zu erfassen. CHANGELOG.md, Abschnitt „## Unveröffentlicht“ (bestehenden Neu-Eintrag zum Was-ist-neu-Fenster behalten), in Alltagssprache, Sie-Form, ganze Saetze, Stil der bisherigen Eintraege, keine Fachbegriffe:
- „### Neu“: ein Eintrag zum waehlbaren Dashboard-Hintergrund (Knopf „Hintergrund“: keiner, ruhige Flaechen und Motive, eigenes Bild aus den Bilderrahmen-Bildern oder neu hochgeladen; gilt nur fuer Sie und folgt Ihnen auf jedes Geraet und in die Desktop-App; eine bisher im Browser gemerkte Wahl wird automatisch uebernommen).
- „### Geändert“ (neu anlegen, zwischen Neu und Behoben): drei bis vier Eintraege — (1) neues Aussehen: dunkle App-Leiste, neu gestaltete Seitenleiste mit Modul-Kacheln, deutschen Kategorienamen und der Begruessung unten, Akzentfarbe nur beim Modul im Fokus; (2) neue Anmeldeseite, geteilt mit dunklem Markenbereich und Farbmosaik; (3) Dashboard: Kacheln mittig ausgerichtet, Widgets mit gelbem Symbol-Feld, heben sich beim Darueberfahren leicht an und blenden beim Laden sanft ein, Begruessung/Befehlsleiste ueber den Kacheln, ruhigere Kalender-, Favoriten- und Notiz-Kacheln; (4) Kalender: Terminliste einzeilig mit „Heute“/„Morgen“ statt Datum. Eintraege aus den Commit-Rumpfen ergaenzen, die fuer Anwender sichtbar sind (z. B. mobile Schublade), nichts Internes.
- „### Behoben“ (neu anlegen): Widgets liessen sich manchmal nicht schmaler ziehen, wenn die Maus dabei leicht wackelte — jetzt klappt das zuverlaessig.
docs/anleitung-anwender.md knapp nachziehen: Abschnitt „Aufbau der Oberfläche“ (dunkle App-Leiste, Seitenleiste mit Modul-Kacheln und Begruessung unten — nur was sich wirklich geaendert hat, gegen den gemergten Code pruefen), Abschnitt „Anmeldung“ falls die Seite beschrieben ist, Abschnitt „Dashboard“ um einen Absatz **Hintergrund** (Knopf, Auswahl, pro Benutzer gespeichert, im dunklen Erscheinungsbild werden eigene Bilder abgedunkelt und „Blüte“ durch „Nebel“ ersetzt), Kalender-Zeile der Widget-Tabelle (einzeilige Terminliste, „Heute“/„Morgen“). docs/anleitung-entwicklung.md: kurzer Absatz neben der Stelle zu User.lastSeenReleaseVersion (~Zeile 669) zu User.dashboardBackground, PATCH /users/me/dashboard-background und parseDashboardBackground in @tessera/shared als einzige Pruefregel.
Abschlusspruefung: web- und api-Build, Biome auf allen in dieser Aufgabe und im Merge geaenderten ts/tsx-Dateien ohne Fehler (Fehler in unveraenderten Dateien sind ausser Umfang; Biome-Fehler in gemergten Dateien beheben als fix(260928-ujj)). Laeuft der lokale Stack: api und web mit --build neu starten und per Playwright MCP pruefen — Hintergrund waehlen, Seite neu laden, in einem zweiten Browserkontext (gleicher Benutzer) erscheint derselbe Hintergrund; nie per fetch aus der Seite messen. Kein Push, kein Tag. Commit docs(260928-ujj): CHANGELOG und Anleitungen fuer Design Mosaik.</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl && awk '/^## Unver/{f=1;next} /^## [0-9]/{f=0} f' CHANGELOG.md | grep -c '^### \(Neu\|Geändert\|Behoben\)$' | grep -q '^3$' && grep -q 'Hintergrund' docs/anleitung-anwender.md && grep -q 'dashboardBackground' docs/anleitung-entwicklung.md && pnpm exec biome check $(git diff --name-only --diff-filter=AM 3fc33e3 HEAD -- '*.ts' '*.tsx') && pnpm --filter api build && pnpm --filter web build</automated>
</verify>
<done>CHANGELOG „Unveröffentlicht“ hat Neu, Geändert und Behoben mit den beschriebenen Eintraegen; Anwender- und Entwickler-Anleitung beschreiben Hintergrundwahl und neues Aussehen; Biome ohne Fehler auf den geaenderten Dateien; api- und web-Build erfolgreich; alles committet, nichts gepusht.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Browser -> API PATCH /users/me/dashboard-background | Unvertrauter JSON-Body wird gespeichert und spaeter als CSS-Stil (url("...")) gerendert |
| DB -> Web (Sitzungsantwort) | Gespeicherter JSON-Wert fliesst in style-Attribut des Dashboard-Hintergrunds |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-ujj-01 | Tampering | user.controller.ts updateDashboardBackground / parseDashboardBackground | medium | mitigate | Allowlist fuer kind und Preset-Kennungen, imageId nur als UUID, frisch aufgebautes Objekt ohne Zusatzschluessel; ungueltig ergibt 400 ohne Schreibzugriff; Ausgabe in auth.service erneut durch parseDashboardBackground |
| T-ujj-02 | Elevation of Privilege | PATCH me/dashboard-background | medium | mitigate | Kein Kennungsparameter; forTenant(prisma, currentUser.tenantId).user.update mit where id = currentUser.id |
| T-ujj-03 | Information Disclosure | imageId eines fremden Bildes | low | accept | GET /dashboard/images/:id prueft Besitz; fremde Kennung ergibt nur ein fehlendes Bild beim eigenen Benutzer |
| T-ujj-04 | Denial of Service | uebergrosser Body | low | accept | Express-JSON-Grenze greift; gespeichert wird nur das normalisierte Kleinobjekt |
</threat_model>
<verification>
- `git merge-base --is-ancestor 76d17fe main` ist erfolgreich (Merge-Commit mit 76d17fe als zweitem Elternteil)
- `pnpm --filter web test`, `pnpm --filter api test` gruen; type-check fuer shared, api, web sauber
- Biome ohne Fehler auf geaenderten ts/tsx-Dateien; `pnpm --filter api build` und `pnpm --filter web build` erfolgreich
- Nichts gepusht, kein Tag; .planning/HANDOFF.json unveraendert als lokale Aenderung
</verification>
<success_criteria>
main traegt das Design Mosaik inklusive Resize-Fix, der Dashboard-Hintergrund wird pro Benutzer in der Datenbank gespeichert und geprueft, CHANGELOG und Anleitungen sind fuer 1.5.0 vorbereitet — bereit fuer die Freigabe durch den Orchestrator.
</success_criteria>
<output>
Create `.planning/quick/260928-ujj-design-mosaik-uebernehmen-und-als-1-5-0-/260928-ujj-SUMMARY.md` when done
</output>
@@ -0,0 +1,131 @@
---
phase: quick-260928-ujj
plan: 01
quick_id: 260928-ujj
status: complete
subsystem: web, api, shared, docs
tags: [design-mosaik, dashboard-background, prisma-migration, changelog, 1.5.0]
requires:
- design/mosaik (76d17fe)
provides:
- Design Mosaik auf main (Merge 9fa0a3f)
- User.dashboardBackground (JSONB) + PATCH /users/me/dashboard-background
- parseDashboardBackground / DASHBOARD_BACKGROUND_PRESET_IDS in @tessera/shared
- CHANGELOG "Unveröffentlicht" mit Neu/Geändert/Behoben fuer 1.5.0
affects:
- apps/web (115 Dateien aus dem Merge)
- apps/api user/auth
tech-stack:
added: []
patterns:
- "Gemeinsame Pruefregel in @tessera/shared, angewendet beim Schreiben (Controller) und Lesen (getMe)"
- "Hook liest Benutzerwahl aus dem Auth-Store und speichert optimistisch ueber Server-Aktion mit Ruecksetzen"
key-files:
created:
- apps/api/prisma/migrations/20260928120000_user_dashboard_background/migration.sql
- apps/web/src/components/dashboard/dashboard-background.test.tsx
modified:
- packages/shared/src/index.ts
- apps/api/prisma/schema.prisma
- apps/api/src/auth/auth.service.ts
- apps/api/src/auth/auth.service.spec.ts
- apps/api/src/user/user.controller.ts
- apps/api/src/user/user.controller.spec.ts
- apps/web/src/lib/auth-actions.ts
- apps/web/src/lib/stores/auth-store.ts
- apps/web/src/components/layout/header.tsx
- apps/web/src/components/layout/header.test.tsx
- apps/web/src/lib/dashboard-background.ts
- apps/web/src/lib/dashboard-background.test.ts
- apps/web/src/components/dashboard/dashboard-background.tsx
- apps/web/src/app/(portal)/page.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- docs/mandantentrennung-zugriffsklassifikation.md
- docs/anleitung-anwender.md
- docs/anleitung-entwicklung.md
- CHANGELOG.md
decisions:
- "Dashboard-Hintergrund als JSONB-Spalte User.dashboardBackground; null = nie gewaehlt, { kind: 'none' } = bewusst kein Hintergrund"
- "parseDashboardBackground in @tessera/shared ist die einzige Pruefregel (Allowlist kind/Preset, imageId nur UUID) fuer API-Schreiben, API-Lesen und Web-Altdatenuebernahme"
- "Alter localStorage-Schluessel wird immer einmal gelesen und entfernt, uebernommen nur bei Server-Wert null"
- "Biome: nur vom Merge neu eingebrachte Befunde behoben; vorbestehende Format-/Importbefunde (158 an der Basis) bleiben ausser Umfang"
metrics:
duration: 17min
completed: 2026-09-28
estimate:
tokens: 95000
tasks: 3
actuals:
tokens: 95700
tasks: 3
commits: 17
plan_head_before: a8a910fd29cf6f2e4c3b228e2a71b2af226f39da
plan_head_after: cb45d2663ac65954463e8c5a6bf859f73a555e86
---
# Quick 260928-ujj Plan 01: Design Mosaik uebernehmen und fuer 1.5.0 vorbereiten — Summary
Design „Mosaik“ per `--no-ff`-Merge (zweiter Elternteil 76d17fe) auf main übernommen. Der Dashboard-Hintergrund wird jetzt pro Benutzer in `User.dashboardBackground` (JSONB) gespeichert: `PATCH /users/me/dashboard-background` prüft mit dem gemeinsamen `parseDashboardBackground`, und der Wert kommt zusammen mit `accentColor` über `getMe` zurück. Die alte Wahl aus dem localStorage wird einmal übernommen. CHANGELOG und beide Anleitungen sind für 1.5.0 vorbereitet.
## Commits
| Task | Commit | Beschreibung |
|------|--------|--------------|
| 1 | 9fa0a3f | feat(260928-ujj): Design Mosaik uebernehmen (Merge, Eltern a8a910f + 76d17fe; bringt 12 Commits aus design/mosaik mit) |
| 2 (RED) | 76f6d87 | test(260928-ujj): rote Tests fuer Dashboard-Hintergrund in der Datenbank |
| 2 (GREEN) | 0aaa152 | feat(260928-ujj): Dashboard-Hintergrund pro Benutzer in der Datenbank |
| 3 (Biome) | 69d1730 | fix(260928-ujj): Biome-Formatierung der mit Design Mosaik eingebrachten Dateien |
| 3 | cb45d26 | docs(260928-ujj): CHANGELOG und Anleitungen fuer Design Mosaik |
`commits: 17` wurde gemessen mit `git rev-list --count a8a910f..HEAD`. Die Zahl enthält die 12 Commits aus design/mosaik, die der Merge mitbringt. Auf der ersten Elternlinie stehen 5 eigene Commits.
## Verifikation
- `git merge-base --is-ancestor 76d17fe HEAD`: erfolgreich. `RESIZE_AXIS_FALLBACK` steht in `dashboard-grid.tsx`.
- Web-Tests (vitest 4.1.9): 97 Dateien, **952 Tests grün**. Direkt nach dem Merge waren es 96 Dateien und 940 Tests.
- API-Tests (vitest 3.2.6): 85 Dateien, **1462 Tests grün**, einschließlich `rls-access-inventory.spec.ts`.
- tsc: shared, api und web sind sauber.
- Builds: `pnpm --filter api build` und `pnpm --filter web build` sind erfolgreich.
- Biome bringt **keine neuen Fehler**. In den seit 3fc33e3 geänderten ts/tsx-Dateien standen an der Basis 158 Fehler, jetzt sind es 156. Alle verbleibenden Befunde bestanden schon vorher (84 format, 72 organizeImports in 93 Dateien).
- Die Migration ist lokal eingespielt (`prisma migrate deploy` gegen 172.19.0.2). Die Spalte `dashboardBackground` hat den Typ jsonb.
- Lokaler Stack neu gebaut mit `docker compose up -d --build api web`. Die API meldet die Route `Mapped {/users/me/dashboard-background, PATCH}`. Ein Aufruf ohne Anmeldung ergibt 401.
- Nichts gepusht, kein Tag gesetzt, den live-Zweig nicht angefasst. `.planning/HANDOFF.json` ist weiter nur eine lokale Änderung und wurde nicht gestaged.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Das Biome-Gate des Plans kann auf den geänderten Dateien nicht fehlerfrei werden**
- **Found during:** Task 2 und Task 3
- **Issue:** Die Verify-Zeile `biome check $(git diff --name-only --diff-filter=AM 3fc33e3 HEAD ...)` endet mit Exit 1. Die 93 betroffenen Dateien hatten schon an der Basis 3fc33e3 zusammen 158 Format- und Importbefunde.
- **Fix:** Ich habe nur die Befunde behoben, die der Merge neu eingebracht hat (12 Dateien, nur Formatierung und Importreihenfolge, eigener Commit 69d1730), dazu alle Befunde in den von mir geschriebenen Zeilen und neuen Dateien. Den Rest ganzer Dateien habe ich nicht umformatiert. Die Vorgabe des Auftraggebers („no new errors“) ist erfüllt: 158 → 156.
- **Commit:** 69d1730
**2. [Rule 2 - Missing critical] Der alte localStorage-Schlüssel wird auch bei gesetztem Server-Wert aufgeräumt**
- **Found during:** Task 2
- **Issue:** Nach Plan hätte bei gesetztem Server-Wert keine Übernahme stattgefunden, der alte Schlüssel wäre dann aber für immer liegen geblieben.
- **Fix:** `takeLegacyDashboardBackground` wird je Benutzer einmal aufgerufen und entfernt den Schlüssel immer. Übernommen wird die alte Wahl nur, wenn der Server-Wert `null` ist. Ein Test deckt das ab.
**3. [Rule 1 - Doc] Die Anwender-Anleitung war schon vor dem Merge an drei Stellen veraltet**
- Die Seitenleiste zeigte bereits vorher keine Sprachumschaltung und keine Name/Rolle-Zeile mehr. Die Reiter standen seit 1.4.0 in der Kopfzeile. Der Stift-Schalter ist mit Mosaik zu „Bearbeiten“/„Fertig“ oben rechts geworden. Diese Stellen habe ich beim Nachziehen gegen den gemergten Code korrigiert.
### Nicht ausgeführt
- **Browser-Prüfung per Playwright MCP** (Hintergrund wählen, neu laden, zweiter Browserkontext): In dieser Ausführungsumgebung gab es kein Playwright-MCP-Werkzeug. Ersatzweise habe ich den lokalen Stack neu gebaut und geprüft, dass die Route gemappt ist, ohne Anmeldung 401 liefert und die Spalte in der DB existiert. Die Prüfung über zwei Geräte im Browser steht noch aus.
## Hinweise
- Eine Übernahme der alten Wahl, die fehlschlägt (z. B. wegen eines API-Ausfalls genau in dem Moment), geht verloren, weil der Schlüssel schon beim Lesen entfernt wird. Der Benutzer wählt dann einfach neu. Das ist bewusst so, damit die Übernahme garantiert nur einmal passiert.
- Alte Prototyp-Werte mit einer Bildkennung, die keine UUID ist, werden nicht übernommen. Echte Bilderrahmen-Kennungen sind immer UUIDs.
- Das Web liest `@tessera/shared` jetzt auch in `dashboard-background.ts` zur Laufzeit. `parseDashboardBackground` enthält nur löschbare Syntax (Regel aus dem Warnkommentar über `WIDGET_TYPES`).
## Threat Flags
Keine über das Threat-Register hinaus. T-ujj-01 und T-ujj-02 sind wie geplant umgesetzt: Allowlist und UUID-Prüfung beim Schreiben und Lesen, `forTenant` mit `where id = currentUser.id`, kein Kennungsparameter.
## Self-Check: PASSED
- FOUND: apps/api/prisma/migrations/20260928120000_user_dashboard_background/migration.sql
- FOUND: apps/web/src/components/dashboard/dashboard-background.test.tsx
- FOUND commits: 9fa0a3f, 76f6d87, 0aaa152, 69d1730, cb45d26