Errungenschaften: Abzeichen für erreichte Meilensteine #15
Notifications
Total Time Spent: 21 minutes
florian.wilde
21 minutes
No due date set.
Dependencies
No dependencies set.
Reference: florian.wilde/flimmer#15
Reference in New Issue
Block a user
Worum es geht
Flimmer weiß eine Menge über das eigene Schauverhalten, sagt aber nichts dazu. Wer alle Spider-Man-Filme durch hat, bekommt keine Anerkennung — dabei ist genau das der Moment, in dem man kurz grinst und es jemandem erzählt.
Errungenschaften sollen still belohnen, nicht antreiben. Keine Serien-Streaks, die einen bestrafen, wenn man mal eine Woche nichts schaut. Keine Fortschrittsbalken, die drängeln. Ein Abzeichen taucht auf, wenn etwas erreicht ist — und bleibt dann.
Warum das Fundament schon steht
Alles Nötige liegt im Datenmodell, es wird nur nirgends ausgewertet:
Title.tmdbCollectionId— TMDB gruppiert Filmreihen selbstgetUniverse().progressgibt es schongetTotals()Title.genres,Title.releaseDateLibraryEntry.currentRunWatch.watchedAtLibraryEntry.ownedLibraryEntry.ratingFür „alle Spider-Man gesehen" braucht es also keine gepflegte Liste — TMDB liefert die Zugehörigkeit mit, und Flimmer speichert sie beim Import bereits.
Vorschlag für den Katalog
Symbole als handgezeichnete SVG im Signaturverlauf, wie das Logo — kein Emoji-Zoo, der auf jedem System anders aussieht. Die Emojis unten stehen nur als Platzhalter für die Bildidee.
Anfänge
Reihen und Universen — hier sitzt die Spider-Man-Idee
Ausdauer
Breite
Ordnung
Zu entscheiden: generisch oder mit Eigennamen?
Dein Beispiel „Spider Bro" ist der Kern der Frage. Zwei Wege:
a) Generisch, dynamisch benannt (Empfehlung als Basis). „Reihe komplett — Spider-Man". Funktioniert für jede Reihe, auch für die, an die niemand gedacht hat, und braucht keine Pflege.
b) Kuratierte Namen obendrauf. Eine Datendatei, die TMDB-Reihen-Kennungen auf Namen und Symbole abbildet:
{ "556": { "name": "Spider Bro", "symbol": "spinne" } }. Charmanter, aber Geschmackssache, nur für bekannte Reihen sinnvoll, und jemand muss sie pflegen.Vorschlag: beides — (a) als Grundlage, (b) als kleine JSON-Datei, die ohne Codeänderung wachsen kann. Fehlt ein Eintrag, greift der generische Name. Dann ist „Spider Bro" drin, ohne dass alles andere davon abhängt.
Umzusetzen
Datenmodell
Dazu
User.showAchievementsfür die öffentliche Profilseite.Auswertung
src/lib/achievements.tsmit einem Katalog aus Regeln; jede Regel bekommt einen vorbereiteten Datensatz und gibt zurück, welche Schlüssel erreicht sindOberfläche
Zwei Fallstricke
Der erste Lauf entriegelt alles auf einmal. Bestehende Konten haben Jahre an Daten — die erste Auswertung würde zwei Dutzend Abzeichen gleichzeitig vergeben und ebenso viele Benachrichtigungen verschicken. Der erste Durchlauf je Konto muss deshalb stumm sein; erst danach wird gemeldet.
Zeitzonen. „Zwischen 3 und 5 Uhr" und „an einem Kalendertag" müssen in der Zeitzone des Nutzers gerechnet werden, nicht in der des Servers. Bei den Monatsstatistiken hat genau das schon einmal für einen leeren Chart gesorgt.
Abnahme
Aufwand
Mittel. Das Datenmodell ist winzig, der Aufwand steckt im Regelkatalog und darin, ihn billig auswertbar zu halten: die meisten Bedingungen lassen sich aus wenigen Aggregaten beantworten, wenn man sie einmal gemeinsam holt statt je Regel einzeln zu fragen.
Sinnvoll wäre, mit einer kleinen Auswahl anzufangen — Anfänge, Reihen, Ausdauer — und den Rest nachzuziehen, wenn sich zeigt, was Spaß macht.
Angelegt auf ausdrücklichen Wunsch direkt in Gitea, ohne Verknüpfung zu einem Nebula-Task.
🤖 Claude Code hat mit der Umsetzung begonnen.
feat/15-errungenschaftenAchievement(Schlüssel, eingefrorener Name, Datum) samtUser.showAchievementssrc/lib/achievements.ts: ein gemeinsam geholter Datensatz, darauf der Regelkatalog — sonst fragt jede Regel einzeln die DatenbankZur offenen Frage im Issue (generische oder kuratierte Namen): Ich setze meinen Vorschlag um — generisch als Grundlage, dazu eine kleine Datendatei für Eigennamen wie „Spider Bro". Fehlt dort ein Eintrag, greift der generische Name. Sag Bescheid, wenn du das anders willst.
Den Katalog baue ich vollständig, nicht in Etappen: die Regeln sind Einzeiler, sobald der gemeinsame Datensatz steht — der Aufwand steckt in diesem, nicht in der Anzahl.
Zeiterfassung läuft.
✅ Umsetzung abgeschlossen — bereit zum Testen.
Was geändert wurde
Achievementsamt Migration, dazuUser.showAchievementssrc/lib/achievements.ts: ein gemeinsam geholter Datensatz, darauf 20 feste Regeln plus dynamische für Filmreihen und Universensrc/lib/achievement-names.ts: kuratierte Eigennamen wie „Spider Bro", abgesichert gegen falsche Kennungendocs/ANLEITUNG.mdmit eigenem Abschnitt und ScreenshotGeänderte Dateien
prisma/schema.prisma,prisma/migrations/20260902094830_errungenschaften/src/lib/achievements.ts,src/lib/achievement-names.ts,src/lib/sync.tssrc/components/AchievementBadge.tsx,src/components/Toasts.tsx,src/components/settings/Forms.tsxsrc/app/statistik/page.tsx,src/app/u/[slug]/page.tsx,src/app/einstellungen/page.tsxsrc/app/actions/library.ts,src/app/actions/settings.tstests/achievements.test.ts,docs/ANLEITUNG.md,scripts/screenshots.mjsZur offenen Frage: umgesetzt ist beides — generische Namen als Grundlage, kuratierte obendrauf.
Eine Absicherung, die ich für nötig hielt: Die TMDB-Collection-Kennungen habe ich aus dem Gedächtnis geschrieben und kann sie hier nicht prüfen. Jeder kuratierte Eintrag trägt deshalb ein Stück des erwarteten TMDB-Namens; passt er nicht, greift der generische Name. Eine falsche Kennung bleibt damit folgenlos, statt jemandem „Spider Bro" für Ocean's zu geben.
Ein Fehler, den ein Test gefunden hat: Die Regel „zwischen drei und fünf Uhr" las die Stunde aus der formatierten Zeichenkette. Auf Deutsch liefert
Intlaber „03 Uhr" —Number("03 Uhr")istNaN, das Abzeichen wäre nie vergeben worden.Geprüft
88 Tests grün (zehn neue), Typecheck sauber. Screenshot gegen die geseedete Instanz: 3 von 20 erreicht, Erreichtes farbig mit Datum, Offenes blass mit Bedingung.
So testest du es
git switch feat/15-errungenschaftennpm run db:migrate— es kommt eine neue Migrationnpm run dev, dann/statistiköffnen und ganz nach unten scrollen/u/<adresse>erscheinenAufgewendete Zeit: 00:22
Pull Request: #17
Nach Freigabe:
/issue-freigabe 15🚀 Freigegeben und nach
mainübernommen.6847d08Nach dem Merge auf
maingeprüft — zusammen mit #16, dessen Änderungen dieselben Dateien berühren: 93 Tests grün, Typecheck sauber, Bau erfolgreich. Kein Konflikt zwischen den beiden.⚠️ Beim Aktualisieren kommt eine neue Migration mit (
20260902094830_errungenschaften).Das Issue wurde durch das
Closes #15im Merge automatisch geschlossen; die Statuslabels sind geräumt.