14 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, requirements-completed, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | tech-stack | key-files | key-decisions | requirements-completed | duration | completed | status | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260923-lrr | 01 | api+web (favorites) |
|
|
|
|
|
|
|
|
45min | 2026-09-23 | complete |
Quick-Aufgabe 260923-lrr: Favoriten — eigenes Symbol hochladen, Zwischenspeicher nach Änderung erneuern (Summary)
Favoriten-Symbole bekommen eine versionierte Adresse (?v=<iconVersion>), ein hochgeladenes eigenes Symbol (PNG/JPEG/GIF/WebP/ICO/SVG, ≤512 KB) hat Vorrang vor der Logo-Adresse, und eine serverseitige Abrufprobe weist eine nicht ladbare Logo-Adresse beim Speichern mit einer deutschen Meldung ab statt sie still zu übernehmen.
Ausgangslage
Der Nutzer meldete: Eine eigene Logo-Adresse eintragen „bewirkt nichts“ (Verdacht: Cloudflare-Prüfung vor dem Bild), und selbst eine tatsächlich abrufbare neue Adresse erschien wegen eines 24-Stunden-Zwischenspeichers einen Tag lang nicht. Beide Ursachen wurden am Code bestätigt (siehe 260923-lrr-PLAN.md, Abschnitt „Ausgangslage“) und in diesem Lauf behoben; zusätzlich lässt sich jetzt ein eigenes Symbol hochladen.
Performance
- Dauer: ca. 45 Minuten
- Aufgaben: 2 von 2 geplanten Aufgaben ausgeführt (Aufgabe 3, Browser-Nachweis, ist ein separater Checkpoint und wird vom Orchestrator im Anschluss durchgeführt — siehe unten)
- Geänderte/neue Dateien: 19
Aufgaben-Commits
- Aufgabe 1: API — Symbol hochladen/entfernen, Vorrang, Versionszähler, Abrufprobe —
7704372(feat) - Aufgabe 2: Web — versionierte Symbol-Adresse, Datei-Auswahl, Entfernen-Knopf, Texte —
61f95c8(feat)
Beide Commits liegen direkt auf main (Orchestrator-Vorgabe für diesen Lauf: kein Worktree, git.allow_default_branch_commits: true in .planning/config.json).
Accomplishments
- Zwischenspeicher-Fehler behoben:
GET /favorites/:id/iconsendet jetztCache-Control: private, max-age=86400(stattpublic); die Bildadresse im Widget trägt?v=<iconVersion>, das bei jeder Änderung der Symbolquelle um eins steigt — ein geändertes Symbol erscheint jetzt ohne Neuladen der Seite. - Cloudflare-Fall behoben: Eine neue, explizit eingetragene Logo-Adresse wird beim Speichern einmal serverseitig abgerufen (
assertIconUrlLoadable, 4 s Zeitgrenze über den vorhandenenIconDiscoveryService); scheitert der Abruf, antwortet die API mit 422 und einer deutschen Meldung im Formular — nichts wird gespeichert. Keine Umgehung von Bot-Sperren. - Eigenes Symbol hochladen: Neue Felder
FavoriteLink.uploadedIconMime/iconVersion(Migration20260923160000_favorite_icon_upload), Ablage unteruser-files/favorite-icons/<userId>/<id>.<ext>(Dateiname immer servergeneriert, Musterdashboard-images.service.ts), Erkennung von PNG/JPEG/GIF/WebP/ICO/SVG an den Bytes (favorite-icon-files.ts). Vorrang voriconUrlbeim Ausliefern. Hochladen und Entfernen im Bearbeitungs- und Hinzufügen-Formular des Widgets. - T-LRR-07 zusätzlich geschlossen (im Plan als Restrisiko mit Disposition „accept“ eingetragen, siehe unten): Löscht ein Nutzer ein ganzes Widget oder einen Dashboard-Reiter, entfernt die Datenbank-Kaskade (
onDelete: Cascade) die betroffenenFavoriteLink-Zeilen, ohne den Favoriten-Dienst zu durchlaufen — dessen Datei-Aufräumung griff dort bisher nicht.DashboardService.removeWidget/deleteDashboardmerken sich jetzt vor der Kaskade, welche Favoriten ein hochgeladenes Symbol tragen, und entfernen deren Dateien danach best effort (nie blockierend für das Löschen selbst).
Dateien erstellt/geändert
API:
apps/api/prisma/schema.prisma—FavoriteLink.uploadedIconMime/iconVersionapps/api/prisma/migrations/20260923160000_favorite_icon_upload/migration.sql— neue Migration, lokal angewendet (siehe Verifikation)apps/api/src/favorites/favorite-icon-files.ts(neu) — Erkennung, Pfadbildung, best-effort Dateientfernungapps/api/src/favorites/favorite-icon-files.spec.ts(neu) — 15 Testsapps/api/src/favorites/favorites.service.ts—uploadIcon/removeUploadedIcon, Abrufprobe, Vorrang ingetIconBytesapps/api/src/favorites/favorites.service.spec.ts— 49 Tests (24 neu)apps/api/src/favorites/favorites.controller.ts—POST/DELETE /favorites/:id/icon,Cache-Control: privateapps/api/src/favorites/favorites.controller.spec.ts(neu) — 7 Testsapps/api/src/dashboard/dashboard.service.ts— T-LRR-07-Aufräumung inremoveWidget/deleteDashboardapps/api/src/dashboard/dashboard.service.spec.ts— 66 Tests (5 neu)docs/anleitung-betrieb.md—user-files/favorite-icons/ergänzt
Web:
apps/web/src/lib/favorites-api.ts—FavoriteRequestError,uploadFavoriteIcon/removeFavoriteIcon,FAVORITE_ICON_MAX_BYTESapps/web/src/lib/favorites-api.test.ts(neu) — 10 Testsapps/web/src/components/dashboard/widgets/favorites-widget.tsx— versionierte Symbol-Adresse, Datei-Auswahl, Entfernen-Knopf, Fehlermeldung im Formularapps/web/src/components/dashboard/widgets/favorites-widget.test.tsx— 27 Tests (10 neu, 1 bestehender Test an?v=0angepasst)apps/web/src/messages/de.json/en.json— neun neue Texte unterwidgets.favoritesCHANGELOG.md,docs/anleitung-anwender.md— ergänzt
Entscheidungen
- iconVersion statt updatedAt:
updatedAtscheidet als Versionsquelle aus, weil Umsortieren oder eine Titeländerung sonst jedes Symbol neu laden ließen.iconVersionsteigt gezielt nur bei einer Änderung der Symbolquelle. - Vorrang und Rückfall: Ein hochgeladenes Symbol hat Vorrang vor
iconUrl. Fehlt die Datei trotz gesetztem Typ (praktisch nur bei einer manuellen Änderung am Dateisystem denkbar), protokolliert der Dienst eine Warnung und fällt auficonUrlzurück, statt 404 zu werfen — das entspricht dem im Plan festgelegten Verhalten. - Abrufprobe nur bei Änderung: Die Probe läuft nur, wenn eine neue oder gegenüber der Zeile abweichende
iconUrlübergeben wird — nicht bei jedem Speichern. Vermeidet unnötige Netzwerkaufrufe beim bloßen Ändern von Titel oder Reihenfolge.
Abweichungen vom Plan
Vom Orchestrator angeordnete Zusatzanforderung (kein Regelabweichungsfund, sondern expliziter Auftrag)
1. T-LRR-07 geschlossen — Datei-Leichen nach Kaskadenlöschung eines Widgets/Reiters
- Gefunden während: vor Aufgabe 1, auf ausdrückliche Anweisung des Orchestrators (Constraint „closes the plan's accepted gap T-LRR-07“)
- Befund:
DashboardService.removeWidget(einzelnes Widget) undDashboardService.deleteDashboard(ganzer Reiter) löschenWidgetInstance-Zeilen;FavoriteLink.widgetIdträgtonDelete: Cascade, wodurch die Datenbank die zugehörigen Favoriten-Zeilen mitlöscht, OHNEFavoritesService.remove()zu durchlaufen — dessen Datei-Aufräumung griff dort also nicht. Der Plan hatte dies als Restrisiko T-LRR-07 mit Disposition „accept“ eingetragen (Dateien bleiben liegen, sind ohne Zeile nie abrufbar, höchstens 512 KB je Favorit). - Fix:
favorite-icon-files.tsbekam eine zusätzliche, nie werfende FunktionremoveFavoriteIconFileBestEffort(userId, id, mime).DashboardService.removeWidget/deleteDashboardlesen VOR der Löschung die betroffenen Favoriten mit gesetztemuploadedIconMime(ein reiner Lesezugriff, außerhalb der Löschtransaktion) und entfernen NACH erfolgreicher Löschung deren Dateien best effort — ein Dateifehler wird protokolliert und geschluckt, er kann das Löschen des Widgets/Reiters nie verhindern oder zurücknehmen (Muster T-HK4-04). - Dateien geändert:
apps/api/src/favorites/favorite-icon-files.ts,apps/api/src/dashboard/dashboard.service.ts,apps/api/src/dashboard/dashboard.service.spec.ts - Tests: 5 neue Tests in
dashboard.service.spec.ts(Datei wird entfernt, fehlende Datei wird geschluckt, Favoriten ohne Symbol lösen keinen Dateizugriff aus — für beide Methoden je Fall bzw. anteilig) - Verifikation:
pnpm --filter @tessera/api exec vitest run src/dashboard src/favoritesgrün (218 Tests) - Commit:
7704372(Teil des Aufgabe-1-Commits, da API-seitig und eng anfavorite-icon-files.tsgekoppelt)
Restrisiko nach diesem Fix: Das Löschen eines EINZELNEN Favoriten über DELETE /favorites/:id sowie die beiden neuen Wege raumen die Datei immer auf. Ein denkbarer Rest bleibt nur, wenn ein Dateisystemfehler ausgerechnet beim best-effort-Entfernen auftritt (protokolliert, nie blockierend) — dasselbe Restrisiko, das die Bilderrahmen-Funktion (dashboard-images.service.ts, T-HK4-04) für ihre Bilder ebenfalls bewusst trägt.
Gesamt: 1 Zusatzanforderung umgesetzt (kein Regel-1/2/3-Fund im eigentlichen Sinn, da vom Orchestrator vorgegeben statt während der Ausführung entdeckt — inhaltlich entspricht die Umsetzung Regel 2, fehlende sicherheits-/korrektheitsrelevante Funktionalität). Auswirkung auf den Plan: Kein Scope Creep über die Orchestrator-Vorgabe hinaus; alle übrigen Aufgaben wurden wie im Plan spezifiziert umgesetzt.
Verifikation (durchgeführt)
pnpm --filter @tessera/api exec prisma generate # OK
pnpm --filter @tessera/api exec vitest run src/favorites src/dashboard # 218/218 grün
pnpm --filter @tessera/api exec tsc --noEmit # sauber
pnpm exec biome lint apps/api/src/favorites apps/api/src/dashboard # keine Befunde
pnpm --filter @tessera/web exec vitest run src/components/dashboard \
src/lib/favorites-api.test.ts src/messages # 276/276 grün
pnpm --filter @tessera/web exec tsc --noEmit # sauber
pnpm exec biome lint apps/web/src/lib/favorites-api.ts \
apps/web/src/lib/favorites-api.test.ts \
apps/web/src/components/dashboard/widgets/favorites-widget.test.tsx # keine Befunde
biome lint favorites-widget.tsx # 3 Befunde (alle vorbestehend, wie vom Plan erlaubt)
grep -rc "as unknown as" apps/api/src # ≤ 29 (Plan-Obergrenze eingehalten)
Migration 20260923160000_favorite_icon_upload wurde lokal über die Container-IP der db mit prisma migrate deploy angewendet (Vorgabe des Orchestrators: tessera:tessera_dev, kein Host-Port).
Bekannte Stubs
Keine — jede neu geschriebene Funktion ist mit echten Daten verdrahtet, keine Platzhalter.
Aufgabe 3 — Browser-Nachweis (noch offen, Orchestrator)
Aufgabe 3 des Plans (checkpoint:human-verify, gate="blocking") ist ein separater Verifikationsschritt am lokalen Docker-Stack (Symbol hochladen/entfernen, Cache-Bust im Browser, Abrufprobe, Dateigrößen-/Typgrenzen, Dateiaufräumung nach Löschen) und wird laut Auftrag NICHT von diesem Ausführungslauf durchgeführt — das übernimmt der Orchestrator im Anschluss per Playwright MCP. Diese SUMMARY dokumentiert ausschließlich die abgeschlossenen Aufgaben 1 und 2.
Nächste Schritte
- Orchestrator: Aufgabe 3 (Browser-Nachweis) durchführen, siehe
260923-lrr-PLAN.md. - Kein Blocker für Aufgabe 3 aus Sicht der API/Web-Implementierung — alle automatisierten Prüfungen sind grün, die Migration ist lokal bereits angewendet.
Quick-Aufgabe: 260923-lrr Abgeschlossen (Aufgaben 1–2): 2026-09-23
Self-Check: PASSED
Alle in dieser Summary genannten neuen Dateien sowie beide Commits (7704372, 61f95c8) wurden gegen das Repository geprüft und gefunden.