docs: Changelog, Release-Notes und DEVLOG für 0.3.0
This commit is contained in:
+54
-1
@@ -7,6 +7,58 @@ die Versionierung [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
## [Unveröffentlicht]
|
||||
|
||||
## [0.3.0] — 2026-09-01
|
||||
|
||||
Das Kernstück: Bibliothek, Status und Episodenfortschritt.
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
- **Bibliothek** als Posterraster mit Volltextsuche und Filtern nach Status,
|
||||
Typ, Genre, Streamingdienst und Tag; Sortierung nach zuletzt aktualisiert,
|
||||
hinzugefügt, Titel, Bewertung oder Erscheinungsjahr. Der gesamte Filterstand
|
||||
steht in der URL und ist damit teilbar.
|
||||
- **Fünf Status** je Eintrag (Schaue gerade, Pausiert, Abgeschlossen,
|
||||
Abgebrochen, Watchlist). Der Status ergibt sich beim Abhaken von selbst:
|
||||
die erste Episode startet einen Titel, die letzte schließt ihn ab.
|
||||
Pausiert und Abgebrochen bleiben unangetastet — wer bewusst pausiert hat,
|
||||
will das nicht durch eine Sichtung aufgehoben bekommen.
|
||||
- **Episodenfortschritt**: einzeln abhaken, ganze Staffel auf einmal,
|
||||
„bis hierhin gesehen" für den Einstieg mitten in einer Staffel, und ein
|
||||
**„+1 Episode"**-Knopf, der genau die nächste offene Episode nimmt.
|
||||
Häkchen reagieren sofort und werden zurückgedreht, wenn der Server
|
||||
widerspricht; beim Abhaken glimmt die Zeile einmal warm auf.
|
||||
- **„Als Nächstes dran"** mit Episodencode, Titel und Ausstrahlungsdatum;
|
||||
Fortschritt in Prozent auf Basis der echten Episodenzahl, dazu die
|
||||
verbleibende Laufzeit.
|
||||
- **Rewatch-Zählung**: ein neuer Durchlauf setzt den Fortschritt zurück, die
|
||||
Sichtungen des vorherigen bleiben für die Statistik erhalten. Bei Filmen
|
||||
zählt jeder Durchlauf einzeln.
|
||||
- **Rückgängig** für jede Sichtungsaktion — die Rückmeldung am unteren Rand
|
||||
nennt, was passiert ist, und nimmt es auf Klick zurück.
|
||||
- **Pro Eintrag**: Bewertung von 1 bis 10, Notizfeld, eigene Tags und das Feld
|
||||
„wo schaue ich das", das beim Aufnehmen einen Vorschlag aus der
|
||||
Streaming-Verfügbarkeit erhält und jederzeit überschreibbar ist.
|
||||
- **Papierkorb** statt hartem Löschen: entfernte Titel bleiben 30 Tage liegen
|
||||
und behalten dabei ihren Fortschritt. Wird ein Titel erneut aufgenommen,
|
||||
lebt derselbe Eintrag wieder auf.
|
||||
- **Titel selbst anlegen** für alles, was TMDB nicht kennt. Solche Einträge
|
||||
haben keine TMDB-ID und werden vom Abgleich nie angefasst.
|
||||
- **Seed mit Beispielbibliothek**: erfundener Katalog aus 8 Serien und
|
||||
10 Filmen samt lokal erzeugten Postern, dazu 18 Bibliothekseinträge und
|
||||
85 Sichtungen über die letzten Monate verteilt.
|
||||
- **21 Tests** decken Aufnehmen, Abhaken, Fortschritt, Rewatch, Papierkorb und
|
||||
den Zugriffsschutz zwischen Nutzern ab.
|
||||
|
||||
### Geändert
|
||||
|
||||
- Die Detailseite liegt jetzt unter `/titel/<id>` und hängt an der internen
|
||||
Titel-ID statt an der TMDB-ID — nur so bekommen manuell angelegte Titel
|
||||
dieselbe Seite. Treffer aus der Suche laufen über `/tmdb/<art>/<id>`, das
|
||||
den Titel holt und weiterleitet.
|
||||
- Die Zahl der Build-Worker ist auf vier begrenzt: mit einem Worker je Kern
|
||||
stürzte der Build auf einer 18-Kern-Maschine reproduzierbar ab, weil jeder
|
||||
Worker eine eigene Prisma-Engine hochzieht.
|
||||
|
||||
## [0.2.0] — 2026-09-01
|
||||
|
||||
Metadaten: Flimmer spricht mit TMDB.
|
||||
@@ -80,6 +132,7 @@ Erste lauffähige Etappe: Gerüst, Marke, Datenmodell und Anmeldung.
|
||||
dass weitere Sprachen als zusätzliche Datei dazukommen können.
|
||||
- **Seed-Skript** mit Demo-Konten sowie README und Design-Dokumentation.
|
||||
|
||||
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/flimmer/compare/v0.2.0...HEAD
|
||||
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/flimmer/compare/v0.3.0...HEAD
|
||||
[0.3.0]: https://git.wilde-server.de/florian.wilde/flimmer/releases/tag/v0.3.0
|
||||
[0.2.0]: https://git.wilde-server.de/florian.wilde/flimmer/releases/tag/v0.2.0
|
||||
[0.1.0]: https://git.wilde-server.de/florian.wilde/flimmer/releases/tag/v0.1.0
|
||||
|
||||
@@ -34,11 +34,27 @@ Alle Zeiten per `date`-Befehl erfasst (Europe/Berlin), nicht geschätzt.
|
||||
|
||||
---
|
||||
|
||||
## Etappe 3 — Kern-Tracking: Bibliothek, Status, Episodenfortschritt (v0.3.0)
|
||||
|
||||
| # | Start | Ende | Dauer | Arbeitsschritt |
|
||||
|---|---|---|---|---|
|
||||
| 3.1 | 19:01 | 19:06 | 5 min | Domänenlogik: Aufnehmen, Status, Sichtungen, Rewatch-Durchläufe, Fortschritt, nächste Episode |
|
||||
| 3.2 | 19:06 | 19:10 | 4 min | 14 Tests dazu; dabei ein Fehler im parallelen Testlauf gefunden (Testdateien räumten sich gegenseitig die Fixtures weg) |
|
||||
| 3.3 | 19:10 | 19:14 | 4 min | Server-Aktionen, Rückmeldungen mit Rückgängig, Bedienelemente (Status, Bewertung, Notizen, Tags, Episodenliste, „+1 Episode") |
|
||||
| 3.4 | 19:14 | 19:17 | 3 min | Bibliotheksseite mit Filtern und Facetten, Papierkorb, Umbau der Detailseite auf die interne Titel-ID |
|
||||
| 3.5 | 19:17 | 19:23 | 6 min | Beispieldaten: erfundener Katalog mit lokal erzeugten Postern, gelebte Bibliothek mit Sichtungshistorie; Klickweg im Browser geprüft (+1 Episode, Rückgängig) |
|
||||
| 3.6 | 19:23 | 19:25 | 2 min | Manuelles Anlegen ohne TMDB-Treffer; Build-Absturz durch zu viele Worker eingegrenzt und behoben |
|
||||
|
||||
**Etappe 3 gesamt: 24 min** (2026-09-01, 19:01 – 19:25)
|
||||
|
||||
---
|
||||
|
||||
## Gesamtübersicht
|
||||
|
||||
| Etappe | Version | Dauer |
|
||||
|---|---|---|
|
||||
| 1 — Gerüst, Branding, Docker, Schema, Auth | 0.1.0 | 38 min |
|
||||
| 2 — TMDB-Anbindung | 0.2.0 | 14 min |
|
||||
| 3 — Kern-Tracking | 0.3.0 | 24 min |
|
||||
|
||||
**Bisher gesamt: 52 min**
|
||||
**Bisher gesamt: 76 min**
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Flimmer 0.3.0
|
||||
|
||||
Das Kernstück: Bibliothek, Status und Episodenfortschritt.
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
- **Bibliothek** als Posterraster mit Volltextsuche und Filtern nach Status,
|
||||
Typ, Genre, Streamingdienst und Tag; Sortierung nach zuletzt aktualisiert,
|
||||
hinzugefügt, Titel, Bewertung oder Erscheinungsjahr. Der gesamte Filterstand
|
||||
steht in der URL und ist damit teilbar.
|
||||
- **Fünf Status** je Eintrag (Schaue gerade, Pausiert, Abgeschlossen,
|
||||
Abgebrochen, Watchlist). Der Status ergibt sich beim Abhaken von selbst:
|
||||
die erste Episode startet einen Titel, die letzte schließt ihn ab.
|
||||
Pausiert und Abgebrochen bleiben unangetastet — wer bewusst pausiert hat,
|
||||
will das nicht durch eine Sichtung aufgehoben bekommen.
|
||||
- **Episodenfortschritt**: einzeln abhaken, ganze Staffel auf einmal,
|
||||
„bis hierhin gesehen" für den Einstieg mitten in einer Staffel, und ein
|
||||
**„+1 Episode"**-Knopf, der genau die nächste offene Episode nimmt.
|
||||
Häkchen reagieren sofort und werden zurückgedreht, wenn der Server
|
||||
widerspricht; beim Abhaken glimmt die Zeile einmal warm auf.
|
||||
- **„Als Nächstes dran"** mit Episodencode, Titel und Ausstrahlungsdatum;
|
||||
Fortschritt in Prozent auf Basis der echten Episodenzahl, dazu die
|
||||
verbleibende Laufzeit.
|
||||
- **Rewatch-Zählung**: ein neuer Durchlauf setzt den Fortschritt zurück, die
|
||||
Sichtungen des vorherigen bleiben für die Statistik erhalten. Bei Filmen
|
||||
zählt jeder Durchlauf einzeln.
|
||||
- **Rückgängig** für jede Sichtungsaktion — die Rückmeldung am unteren Rand
|
||||
nennt, was passiert ist, und nimmt es auf Klick zurück.
|
||||
- **Pro Eintrag**: Bewertung von 1 bis 10, Notizfeld, eigene Tags und das Feld
|
||||
„wo schaue ich das", das beim Aufnehmen einen Vorschlag aus der
|
||||
Streaming-Verfügbarkeit erhält und jederzeit überschreibbar ist.
|
||||
- **Papierkorb** statt hartem Löschen: entfernte Titel bleiben 30 Tage liegen
|
||||
und behalten dabei ihren Fortschritt. Wird ein Titel erneut aufgenommen,
|
||||
lebt derselbe Eintrag wieder auf.
|
||||
- **Titel selbst anlegen** für alles, was TMDB nicht kennt. Solche Einträge
|
||||
haben keine TMDB-ID und werden vom Abgleich nie angefasst.
|
||||
- **Seed mit Beispielbibliothek**: erfundener Katalog aus 8 Serien und
|
||||
10 Filmen samt lokal erzeugten Postern, dazu 18 Bibliothekseinträge und
|
||||
85 Sichtungen über die letzten Monate verteilt.
|
||||
- **21 Tests** decken Aufnehmen, Abhaken, Fortschritt, Rewatch, Papierkorb und
|
||||
den Zugriffsschutz zwischen Nutzern ab.
|
||||
|
||||
### Geändert
|
||||
|
||||
- Die Detailseite liegt jetzt unter `/titel/<id>` und hängt an der internen
|
||||
Titel-ID statt an der TMDB-ID — nur so bekommen manuell angelegte Titel
|
||||
dieselbe Seite. Treffer aus der Suche laufen über `/tmdb/<art>/<id>`, das
|
||||
den Titel holt und weiterleitet.
|
||||
- Die Zahl der Build-Worker ist auf vier begrenzt: mit einem Worker je Kern
|
||||
stürzte der Build auf einer 18-Kern-Maschine reproduzierbar ab, weil jeder
|
||||
Worker eine eigene Prisma-Engine hochzieht.
|
||||
@@ -1,137 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
/**
|
||||
* Staffel- und Episodenliste.
|
||||
*
|
||||
* Staffeln sind eingeklappt, damit eine Serie mit zwanzig Staffeln die Seite
|
||||
* nicht sprengt; die erste reguläre Staffel steht offen. In Etappe 3 kommen
|
||||
* hier die Häkchen für den Fortschritt dazu.
|
||||
*/
|
||||
|
||||
export type EpisodeData = {
|
||||
id: string;
|
||||
episodeNumber: number;
|
||||
name: string;
|
||||
overview: string | null;
|
||||
airDate: string | null;
|
||||
runtime: number | null;
|
||||
};
|
||||
|
||||
export type SeasonData = {
|
||||
id: string;
|
||||
seasonNumber: number;
|
||||
name: string;
|
||||
overview: string | null;
|
||||
airDate: string | null;
|
||||
episodes: EpisodeData[];
|
||||
};
|
||||
|
||||
function formatDate(value: string | null): string | null {
|
||||
if (!value) return null;
|
||||
return new Date(value).toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export function SeasonList({ seasons }: { seasons: SeasonData[] }) {
|
||||
const firstRegular = seasons.find((season) => season.seasonNumber > 0) ?? seasons[0];
|
||||
const [open, setOpen] = useState<string[]>(firstRegular ? [firstRegular.id] : []);
|
||||
|
||||
function toggle(id: string) {
|
||||
setOpen((current) =>
|
||||
current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id],
|
||||
);
|
||||
}
|
||||
|
||||
if (seasons.length === 0) {
|
||||
return <p className="text-sm text-[var(--fl-faint)]">Zu dieser Serie liegen keine Staffeln vor.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{seasons.map((season) => {
|
||||
const isOpen = open.includes(season.id);
|
||||
const air = formatDate(season.airDate);
|
||||
|
||||
return (
|
||||
<section key={season.id} className="fl-card overflow-hidden">
|
||||
<h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggle(season.id)}
|
||||
aria-expanded={isOpen}
|
||||
className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-[var(--fl-surface-2)]"
|
||||
>
|
||||
<Chevron open={isOpen} />
|
||||
<span className="flex-1">
|
||||
<span className="font-medium">{season.name}</span>
|
||||
<span className="ml-2 text-sm text-[var(--fl-faint)]">
|
||||
{season.episodes.length}{" "}
|
||||
{season.episodes.length === 1 ? "Episode" : "Episoden"}
|
||||
{air ? ` · ab ${air}` : ""}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
{isOpen && (
|
||||
<ol className="border-t border-[var(--fl-border)]">
|
||||
{season.episodes.map((episode) => (
|
||||
<li
|
||||
key={episode.id}
|
||||
className="flex gap-3 border-b border-[var(--fl-border)] px-4 py-2.5 last:border-b-0"
|
||||
>
|
||||
<span className="w-14 shrink-0 pt-0.5 font-mono text-xs text-[var(--fl-faint)]">
|
||||
S{String(season.seasonNumber).padStart(2, "0")}E
|
||||
{String(episode.episodeNumber).padStart(2, "0")}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm font-medium">{episode.name}</span>
|
||||
{episode.overview && (
|
||||
<span className="mt-0.5 line-clamp-2 block text-xs text-[var(--fl-faint)]">
|
||||
{episode.overview}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="shrink-0 pt-0.5 text-right text-xs text-[var(--fl-faint)]">
|
||||
{formatDate(episode.airDate) ?? "—"}
|
||||
{episode.runtime ? <span className="block">{episode.runtime} min</span> : null}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
{season.episodes.length === 0 && (
|
||||
<li className="px-4 py-3 text-sm text-[var(--fl-faint)]">
|
||||
Für diese Staffel liegen noch keine Episoden vor.
|
||||
</li>
|
||||
)}
|
||||
</ol>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Chevron({ open }: { open: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={clsx(
|
||||
"h-4 w-4 shrink-0 text-[var(--fl-muted)] transition-transform duration-200",
|
||||
open && "rotate-90",
|
||||
)}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
aria-hidden
|
||||
>
|
||||
<path d="m9 6 6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user