2809821c9b0ce05a13fa6ccce586be84d07cf036
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2809821c9b |
Vier Fehler, und bei zweien log der Kommentar daneben
**Der Einstellungsknopf lag hinter der Abspielleiste.** Gemessen: `.pl`
steht auf Stufe 150, `#chrome` auf gar keiner, und die kleine Leiste spannte
über die ganze Breite — genau über die unteren 72 Bildpunkte der
Seitenleiste, wo „Einstellungen" klebt. Weil der Spieler bei jedem Wechsel
der Adresse minimiert, stand sie zuverlässig im Weg, sobald man dorthin
wollte.
Die Leiste beginnt jetzt hinter der Seitenleiste. Nicht über die Stufen
gelöst, und das ist der Punkt: `#chrome` höher zu setzen hiesse, die
Seitenleiste läge auch über dem grossen Spieler — es ist dieselbe Hülle.
Dass diese Leiste Platz wegnimmt statt sich darüberzulegen, stand für
Inhalt, Hinweise und Auswahlleiste ohnehin schon da; die Seitenleiste war
die vergessene vierte. Gemessen, 1400 Bildpunkte breit: Leiste ab 240,
Knopf bei 0, keine Überschneidung, und ein Treffertest in der Knopfmitte
trifft den Knopf.
**Ein Klick auf eine Kategorie kam nie an.** Dort sprang der Code in die
Mediathek und suchte danach `.filterzeile select`, um den Wert zu setzen.
Die Filterzeile hat kein `<select>` — `auswahl()` baut einen Knopf, mit
Absicht und begründet. Der Zweig lief also nie, und man landete in der
ungefilterten Mediathek. Der Kommentar daneben behauptete das Gegenteil.
Das Genre steht jetzt in der Adresse: `#/m/{id}/mediathek/Horror`. Das
löst zugleich das zweite Problem — die Mediathek wird unter ihrem Weg
gemerkt, und stünde das Genre nur im Kopf, zeigte ein zweiter Besuch die
Liste des ersten. Geprüft: Klick setzt den Weg und die Pille, Neuladen
behält beides, Zurück führt auf die Kategorien, Vorwärts wieder hinein.
**Zurückgezogene Einladungen blieben für immer stehen.** Der Knopfblock
steckte ganz in `if (!e.revokedAt)`, eine erledigte Zeile hatte also gar
keinen Knopf. Die Serverbegründung — eine Zeile wegzuwerfen hiesse,
hinterher nicht mehr zu wissen, dass es den Link je gab — bleibt richtig,
sie gilt nur nicht ewig: eine Liste, aus der nichts verschwindet, wird
unlesbar, und dann sieht man die eine Zeile nicht mehr, um die es ging.
Deshalb kein umgedeutetes DELETE, sondern `?purge=true`, und das greift nur
bei einem Link, der ohnehin keine Tür mehr öffnet. Ein gültiger wird mit
einem deutschen Satz abgewiesen.
**Poster in der falschen Sprache.** „365 Tage" stand mit polnischem Plakat
da. Kein fehlender Anbieter: für den Schriftzug gab es die sprachgewichtete
Wahl längst, fürs Plakat wurde das blanke `poster_path` der Hauptantwort
genommen — bei TMDB das Bild der Originalsprache, wenn in der gewünschten
keines eingetragen ist. Die sortierte Liste lag im selben Abruf schon vor
und wurde ausschliesslich für den Schriftzug benutzt.
Plakat und Hintergrund nehmen jetzt dieselbe Rangfolge: eingestellte
Sprache, dann sprachlos, dann Englisch. Sprachlos vor Englisch ist keine
Verlegenheit — ein Bild ohne Sprachkennung trägt keinen Text, und ein
Plakat ohne Titel ist für einen deutschen Bestand besser als dasselbe mit
englischem. Im Bilderdialog stehen die Kandidaten jetzt ebenso sortiert.
Bestehende Titel ändern sich beim nächsten Auffrischen; von Hand gesetzte
Plakate bleiben unangetastet.
676 Tests grün, davon zehn neue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
cdea8e3b0a |
Die Höhe des Aufmachers ist jetzt einstellbar
Vier Stufen unter Einstellungen → Verwaltung → Übersicht: niedrig, mittel, hoch, sehr hoch. Mittel ist, was gestern fest verdrahtet war. **Keine Pixelzahl.** Eine einzustellen hiesse, sie an jedem Bildschirm neu einzustellen — der Aufmacher misst sich an der Fensterhöhe, nicht an einer festen Grösse. Gemessen bei 800 Bildpunkten Fensterhöhe: 496, 576, 656, 736. Oben ist Schluss, bevor von der ersten Plakatreihe nichts mehr übrig bleibt; sie soll zeigen, dass unten noch etwas kommt. Am Telefon bleibt es flacher, dort wäre auch das weg. **Sie reist mit der Systemauskunft mit**, nicht in einer eigenen Abfrage. Die holt der Client ohnehin als Erstes, noch vor der Anmeldung; käme die Höhe später, stünde der Aufmacher einen Augenblick in der Vorgabe und spränge dann — genau das, was eine einstellbare Höhe vermeiden soll. Gesetzt wird sie als Marke am `<html>`, das Regelwerk hängt vier Werte daran. Serverweit und nur für Admins, dieselbe Linie wie bei den Wiedergaberegeln: eine Entscheidung des Gastgebers, nicht des Gastes. Unbekanntes in der Spalte wird zur Vorgabe statt zu einem Fehler — eine Startseite, die an einem alten Eintrag scheitert, wäre die schlechtere Antwort. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d5e21d663f |
Einladen, statt sich für jemanden ein Passwort auszudenken
Ein Konto von Hand anzulegen heisst: sich für einen anderen ein Passwort ausdenken und es ihm durchsagen — über denselben Kanal, über den auch ein Link gegangen wäre. Jetzt gibt es den Link. Wer ihn öffnet, wählt Namen und Passwort selbst und ist danach angemeldet. Gültigkeit und Anzahl wählst du beim Erstellen: einen Tag bis einen Monat, einmal bis unbegrenzt. **Eine Rolle wählst du nicht** — wer eingeladen wird, ist gewöhnlicher Benutzer. Ein Feld, über das ein Link Adminrechte verschenken könnte, gibt es gar nicht, und damit auch keinen Weg, es versehentlich zu setzen. **Der Link steht genau einmal da.** Gespeichert ist nur sein Fingerabdruck, wie beim Gerätetoken; aus der Liste lässt sich keiner mehr bauen. Eine verlorene Einladung wird deshalb nicht wiedergefunden, sondern neu gemacht. Und gebaut wird die Adresse im Browser: der Server weiss nicht, unter welchem Namen er von aussen erreichbar ist, und hinter einem Reverse Proxy rät er falsch. **Das Rennen beim Einlösen.** Zwei Leute, die gleichzeitig denselben einmaligen Link öffnen, läsen beide „null benutzt" und schrieben beide „eins" — und hätten beide ein Konto. Der Zähler geht deshalb mit einer Bedingung hoch, die die Gültigkeit noch einmal prüft; wer dabei keine Zeile trifft, war der Zweite. Das Ganze in einer Transaktion, damit ein Tippfehler im Namen den Link nicht verbrennt. Beides steht als Test da — zwei gleichzeitige Einlösungen ergeben genau ein Konto. **Angelegt wird, angemeldet wird danach gewöhnlich.** Derselbe Zweischritt wie beim Einrichten, und der Grund ist nicht Bequemlichkeit: die Anmeldung setzt den Sitzungskeks, und ohne den lädt kein Plakat — Bilder weisen sich über ihn aus, nicht über das Token. Gemessen beim Durchspielen: nach dem Einlösen kommt ein Bild mit 200 zurück. **Die Weiche liegt im Hochfahren, nicht im Router.** Der Router läuft nie, wenn niemand angemeldet ist — ein Ausgeloggter mit Einladungslink sähe sonst die Anmeldekarte und landete nach dem Anmelden auf der Startseite, ohne je erfahren zu haben, wozu er eingeladen war. Nach dem Einrichten, damit ein nagelneuer Server zuerst seinen Administrator bekommt. Wer schon angemeldet ist, bekommt kein Formular, sondern den Hinweis und zwei Wege. Und die Gründe einer Ablehnung sind verschieden benannt: abgelaufen, zurückgezogen, schon benutzt. Verschwiegener wäre ein einheitliches „gilt nicht" — und dann fragt der Eingeladene nach, und der Gastgeber rät mit. Nebenbei korrigiert: die Erkundung meinte, dem Benutzernamen fehle ein eindeutiger Index. Er hat einen, seit der ersten Migration, sogar mit `NOCASE`. Das Namensrennen war also nie offen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
02978e482c |
Die Knöpfe bleiben, die Inhaltsangabe klappt auf
Ich hatte „die Beschreibung nur beim Hovern" gelesen und daraufhin Beschreibung *und* Knöpfe ausgeblendet. Die Knöpfe gehören immer dahin: in Ruhe steht oben, was läuft und was man damit tun kann. Die Inhaltsangabe dagegen steht erst da, wenn man es will. Zwei Sekunden auf dem Titel stehenbleiben, dann fährt er hoch und darunter klappt sie auf; wer nur vorbeifährt, sieht nichts. Gibt es keine, passiert gar nichts — der Absatz fällt dann ganz aus dem Fluss. **Angefasst wird am Titel, losgelassen am ganzen Aufmacher.** Das ist die ganze Vorsicht an dieser Stelle. Beim Aufklappen wächst der Block nach oben, der Titel rückt um 77 Bildpunkte weg und steht nicht mehr unter dem Zeiger. Hinge das Zuklappen an seinem `:hover`, wäre das die erste Hälfte einer Schaukel. Der Aufmacher selbst kann das nicht: gemessen, offen wie zu, 476 px hoch ab 48 — auf den Bildpunkt derselbe Kasten. **Die Höhe wird gemessen, nicht geraten.** Nach `auto` lässt sich nicht überblenden, und eine feste Obergrenze wäre bei einer einzeiligen Angabe zu drei Vierteln Leerlauf. Gemessen wird bei jedem Folienwechsel und bei jeder Fensteränderung; `scrollHeight` ginge nicht, der meldet bei einem `-webkit-box` die ungeklammerte Höhe. **Der Rasterabstand wandert mit.** Gemessen: zugeklappt stehen zwischen Eckdaten und Knopfzeile 16 px — mit Inhaltsangabe wie ohne, und genau so viele wie vorher. Aufgeklappt sind es 12 über und 16 unter dem Text, und die Knopfzeile steht still (Unterkante 492 in beiden Zuständen). Nebenbei kommt das Abbruchsignal endlich an: `hero()` nahm eines entgegen, aber `main.ts` baute die Startseite ohne. Sie wird gemerkt — ihr Neun-Sekunden-Takt lief also weiter, während man längst woanders war. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
327e2e9195 |
Einstellungen hat jeder, Verwaltung nicht
Die Seite hiess „Verwaltung" und war für alle anderen gar nicht erreichbar — „Meine Sprache" lag dadurch hinter einer Tür, die den meisten verschlossen ist. Jetzt heisst sie **Einstellungen**, steht jedem offen und hat zwei beschriftete Gruppen: oben, was einem selbst gehört (Mein Konto, Geschaut), darunter — nur für Administratoren — **Verwaltung**. Die Überschrift ist hier die halbe Aussage: sie sagt ohne weiteren Satz, wem der untere Teil gehört. In der Hauptleiste steht der Eintrag jetzt unten, ohne Gruppenüberschrift. Eine Überschrift über einem einzigen Eintrag ist eine Überschrift über nichts, und am Fuss sucht man Einstellungen ohnehin. `#/profil` und `#/verwaltung/…` leiten weiter; ein Lesezeichen merkt davon nichts. **Ein kurzes Menü am Bild.** Erst meldete der Klick ab — ohne Rückfrage. Dann führte er auf die eigene Seite, und zum Abmelden musste man erst dorthin. Beides sind zwei Punkte; ein Menü mit zwei Punkten ist ehrlicher als eine Tür, hinter der eine zweite liegt. **Und das Profilbild lässt sich zuschneiden.** Ein Profilbild ist rund, was hochgeladen wird selten quadratisch — den Ausschnitt dem Browser zu überlassen hiesse: mittig, und das trifft bei einem Porträt den Hals. Der Dialog zeigt den runden Ausschnitt, man zieht und vergrössert, und hochgeladen wird genau das: ein Quadrat von 512 Pixeln. Gemessen, es kommt auch so an. **Dazu die Spielerleiste.** Beide Knopfgruppen durften schrumpfen, ihre Knöpfe aber nicht — wird es eng, steht der äusserste halb hinter dem Fensterrand. Rechts sind es nur Knöpfe, da gibt es nichts nachzugeben; links schon. Dazu seitlich `env(safe-area-inset-*)`, damit auf einem Gerät mit runden Ecken nichts darunter liegt. Gemessen: vorher riss es ab 420 Pixeln, jetzt bis 320 nicht mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d8a1633cf4 |
Das eigene Bild führte aus dem Server heraus
Ein Klick auf den Buchstabenkreis meldete ab. Keine Rückfrage, kein Menü, kein Weg zurück — hinter einer Geste, die überall sonst „mein Konto" heisst. Jetzt führt er auf `#/profil`; Abmelden steht dort als Knopf, mit Nachfrage. Auf der Seite: Bild wählen, tauschen, entfernen. Dazu „Meine Sprache" — **umgezogen, nicht kopiert.** Sie stand mitten in der Verwaltung, und die zeigt der Client nur Administratoren; der Endpunkt erlaubte sie längst jedem. Wer kein Admin war, konnte seine eigene Sprache nirgends ändern. Der Warnhinweis „weicht ab" bleibt in der Verwaltung, denn dort entsteht die Verwirrung — er verweist jetzt aufs Profil. **Vorher musste ein Fehler weg, der seit Monaten dasteht.** `ImageCache.StoreStreamAsync` baute den Schlüssel aus 32 Hexzeichen plus Dateiendung; `PfadFür` verlangt genau 64 Hexzeichen und nichts sonst. Es gab keinen Wert, den beide gelten liessen: **jeder Bildupload gab `null` zurück und endete in einem 502.** Kein Test fragte je danach — jetzt vier. Damit geht nebenbei auch „eigenes Bild hochladen" für Filme und Serien wieder. Weil der Schlüssel keine Endung mehr trägt, kommt der Medientyp beim Ausliefern aus den ersten Bytes der Datei. Vorher ging alles als `image/jpeg` hinaus — bei TMDB stimmte das, bei einem hochgeladenen PNG nicht. **Und die gemerkte Sitzung war eine Kopie, die nie nachzog.** Sie wurde bei der Anmeldung geschrieben und danach nie wieder. Wer sein Bild am Telefon setzt und den Browser am Schreibtisch öffnet, fand dort den alten Buchstaben — bis zum nächsten Anmelden. Gemessen und behoben: beim Hochfahren wird das Konto abgeglichen, und das Profil meldet Änderungen an die Kopfleiste, damit sie ohne Neuladen nachzieht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1e4a231d1b |
Elf Abschnitte in einer Spalte waren zehn zu viel
Die Verwaltung war eine Seite mit 1786 Zeilen. Wer das Protokoll sehen wollte, scrollte an Mediatheken, Prüfliste, Benutzern und Speicher vorbei — und jede neue Einstellung hätte sie länger gemacht. Jetzt hat sie eine eigene Leiste mit sieben Bereichen, jeder mit seiner Adresse: `#/verwaltung/protokolle` lässt sich ablegen, und ein Neuladen bleibt dort stehen. Die Leiste sieht aus wie die Hauptleiste daneben, damit sie als Navigation gelesen wird und nicht als Knopfreihe; am Telefon wird aus der Spalte ein Streifen zum Schieben. **Gebaut wird nur, was offen ist.** Das ist nicht nur sparsamer: das Abspielfenster fragt im Fünf-Sekunden-Takt nach, die Mediathekenliste beobachtet laufende Scans. Gemessen: nach dem Wechsel in einen anderen Bereich kommt zwölf Sekunden lang keine Anfrage mehr. `admin.ts` ist in acht Dateien zerlegt — eine je Bereich, dazu `teile.ts` für die gemeinsame Überschrift und `index.ts` für Leiste und Weiche. Zwei Dinge mussten dabei nachziehen: **Die Markierung in der Hauptleiste.** Sie verglich stur zwei Segmente; bei `#/verwaltung/metadaten` traf das auf keinen Eintrag, und „Verwaltung" verlor seinen Balken. Jetzt gilt auch ein Segment. **„5 Titel warten — ansehen".** Der Knopf sprang mit `scrollIntoView` zur Prüfliste. Die steht nicht mehr im selben Dokument — er wechselt jetzt den Bereich. Die Hervorhebung danach entfällt damit, und mit ihr ihr CSS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
960cca3dae |
Der Abschnitt verschwand alle drei Sekunden und kam wieder
`laden()` setzte `innerHTML = ''` und baute die Mediathekenliste neu — während eines Scans alle drei Sekunden. Der ganze Abschnitt flackerte sichtbar, und wer gerade auf einen Knopf zielte, klickte ins Leere. Schlimmer: jeder Neuaufbau startete über `verfolgen(false)` einen weiteren Takt. Nach einer Minute liefen zwanzig davon nebeneinander. Jetzt wird fortgeschrieben statt neu gezeichnet: eine Zeile je Mediathek, die bleibt, und nur Kopfzeile, Prüfungsknopf und Freigabeknopf ändern sich. Belegt im Browser — nach einem Auffrischen sind es dieselben Knoten, nur die Beschriftung ist eine andere. Dieselbe Behandlung für die Seitenleiste. Und weil Seitenleiste und Verwaltung getrennt nachfragten, zeigten sie zwei Momente — 95 hier, 84 dort, während im Hintergrund Doppelte zusammengelegt wurden. Die Verwaltung reicht ihren Stand jetzt weiter: ein Abruf, zwei Anzeigen. Dazu zwei Dinge, die beim Prüfen auffielen. Zurücksetzen und Entfernen prallen jetzt serverseitig mit 409 ab, solange ein Lauf läuft — die Oberfläche sperrt die Knöpfe, aber darauf darf sich der Server nicht verlassen; ein Aufruf von Hand während eines Scans liess den Lauf an einer DbUpdateConcurrencyException sterben. Und in der Fortschrittszeile steht beim Holen der Folgenmetadaten jetzt der Serienname: „Folge 2×13" allein sagt bei 3815 Folgen nichts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a8bb98c44c |
Umsortiert wird gezogen, nicht geklickt
Die Pfeile sind weg; eine Mediathek wird in der Seitenleiste an ihren Platz gezogen. Mit Pointer Events statt HTML5-Drag: letzteres kennt kein Telefon, zeichnet ein Geisterbild, das sich nicht gestalten lässt, und liefert während des Ziehens keine brauchbaren Koordinaten. Mit Schwelle von sechs Bildpunkten — sonst verschiebt jeder leicht wackelige Klick die Reihenfolge, und ein Klick ohne Bewegung führt weiterhin zur Mediathek, auch im Bearbeitungsmodus. Die weichenden Zeilen gleiten, die gezogene folgt dem Zeiger ohne Verzögerung; bei „Bewegung reduzieren" gleitet nichts. Die Pfeiltasten bleiben auf der fokussierten Zeile, nur ohne sichtbare Knöpfe. Ziehen ist mit der Tastatur nicht bedienbar, und eine Funktion, die nur mit der Maus erreichbar ist, ist für den, der keine benutzt, gar keine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a22fa9b58e |
Drei Zähler für dieselbe Sache, und keiner davon offensichtlich falsch
Während eines Scans stand in der Mediathek 20, in der Verwaltung 14 und in der Seitenleiste 0. Die Verwaltung frischte alle fünf Runden auf, die Seitenleiste erst am Ende des Laufs. Jetzt laufen beide auf demselben Takt, alle drei Runden. Dazu: während ein Lauf läuft, sind Scannen, Metadaten auffrischen, Zurücksetzen und Entfernen gesperrt — die ersten beiden prallen am Server ab und sind nur verwirrend, die anderen beiden löschen Zeilen, während der Lauf welche schreibt. Umbenennen und Prüfung bleiben bedienbar. Die Sperre richtet sich nach dem Zustand, den der Server meldet, nicht nach dem Klick: wer die Seite neu lädt, während ein Lauf läuft, findet sie ebenfalls gesperrt vor. Die Reihenfolge der Mediatheken wird jetzt in der Seitenleiste umsortiert, über „Seitenleiste bearbeiten" neben der Überschrift. Sie wirkt dort, also gehört der Griff dorthin — vorher schob man in den Einstellungen und musste danach nachsehen, ob es das war, was man wollte. Der Zuordnungsdialog zeigt die Herkunft: Ordner, Zahl der Dateien und ein paar Namen. Liegt eine Zuordnung völlig daneben, sagt der angezeigte Titel nichts mehr darüber, welche Datei gemeint ist. Und SQLite läuft jetzt im WAL-Modus. In der Vorgabe sperrt ein Schreiber die ganze Datei: solange der Scan schrieb, kam keine Leseanfrage durch. Genau so sah es aus — Bilddialog und Protokoll liefen während eines Laufs sporadisch ins Leere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ac04901ccc |
Neun gemeldete Fehler, und die Frage, welcher Build gerade läuft
Ein grosser Commit, weil die Punkte einander bedingen: ohne lesbare Fehlermeldung war die Serienerkennung nicht zu diagnostizieren, und ohne Build-Kennung nicht einmal festzustellen, ob überhaupt der gebaute Stand läuft. Zwei der neun Meldungen betrafen Funktionen, die seit Stunden im Quelltext stehen. Version und Build. Die Version steht fest; daneben eine Kennung aus sechs Zeichen, die sich bei jedem Docker-Bau ändert. Sie steht in der Verwaltung, unter /health und im Protokoll bei jedem Start. Damit ist "reden wir vom selben Build?" in zwei Sekunden beantwortet statt in einem halben Tag. Serien. Lag eine Datei direkt im Serienordner und trug vor der Folgennummer nichts, wurde der ganze Dateiname zum Serientitel: aus 95 Serien wurden 737, jede mit einer Folge, bei jedem Lauf neu. Dazu lief der Scan in Verweise hinein — derselbe Ordner unter zwei Namen ergab zwei Serien. Und weder neue noch umgezogene Dateien standen in der Pfadliste des Laufs, weshalb ein doppelt gesehener Pfad eine zweite Zeile mit gleichem (LibraryId, Path) anlegte und der Eindeutigkeitsindex den ganzen Speichervorgang scheitern liess. Jede Serienanlage wird jetzt im Tagesprotokoll begründet. Fehlermeldungen. "An error occurred while saving the entity changes" stand englisch in einer deutschen Oberfläche und nannte weder Ursache noch nächsten Schritt. Jetzt geht der vollständige Text ins Protokoll und ein deutscher Satz ohne Fehlercode in die Anzeige — auch für "Failed to fetch", das aus dem Browser kam und nie übersetzt wurde. Protokolle. Die Ansicht holte die ganze Datei und zeigte 800 Zeilen davon; bei zwei Megabyte brach die Verbindung ab. Gelesen wird jetzt serverseitig rückwärts, mit einem Knopf für mehr. Auswahlfelder. Die drei <select> zeichnete das Betriebssystem: ein helles Systemmenü mitten in der dunklen Oberfläche. Ersetzt durch ein eigenes Listenfeld mit Tastaturbedienung. Bildwahl. Drei Raster untereinander in einem scrollenden Dialog hiess zwei Scrollebenen übereinander. Jetzt ein Reiter je Bildtyp. Sprache. Die Serversprache stand in der Serverliste zwischen Name, Metadaten und Fassung — gesucht und nicht gefunden. Sie hat jetzt einen eigenen Abschnitt ganz oben. Dazu: ein Knopf, der den erfassten Inhalt einer Mediathek verwirft, ohne Name und Pfad mitzunehmen; ein Abschnitt Speicher mit den drei Pfaden, ihrer Belegung und dem Leeren des Bildspeichers; und die Korrektur einer Zuordnung frischt nur noch die Kachel auf statt die ganze Seite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
637e95fbb1 |
Die Verwaltung zeichnete sich endlos neu
Gemessen am laufenden Server: nach dem Scan lief eine Schleife mit zwei Abrufen je Runde, sichtbar als vier dauernd drehende Spinner. Der Weg dahin: verfolgen() lief bei jedem Aufbau der Mediathekenzeile, sah den Zustand finished, meldete das über geaendert(), und geaendert() war mediathekenNeuLaden — also neu zeichnen. Der Neuaufbau startete verfolgen() erneut, das wieder finished sah. Dass der Lauf längst vorbei war, machte keinen Unterschied; der Zustand bleibt im Speicher stehen. Zwei Dinge dagegen. verfolgen() meldet nur noch, wenn es selbst einen Lauf angestossen hat — beim Aufbau zeigt es den Stand an und schweigt. Und die Verwaltung frischt über zaehlerAuffrischen() nur noch die Seitenleiste auf statt die ganze Seite neu zu zeichnen. Dazu zwei Befunde aus dem Plan, die sich beim Nachsehen als falsch erwiesen: die Takte werden sehr wohl abbestellt, dafür gibt es seit je einen MutationObserver, und das Dashboard tauscht seinen Inhalt erst nach dem Abruf. Geblieben ist der Spinner in den Listen, der bei jedem Auffrischen erschien — der kommt jetzt nur noch beim ersten Aufbau. Dazu der Zwischenspeicher für Ansichten. Der Router hängt sie aus statt sie zu zerstören; beim Zurückkommen stehen sie sofort wieder da, mitsamt Bildlauf, offenem Reiter und gesetzten Filtern, und laden leise nach. Gemessen: null Abrufe und 4 von 4 Kacheln beim Zurückkommen aus der Verwaltung. Höchstens fünf Ansichten, Verfall nach fünf Minuten, weggeworfen bei allem, was den Bestand ändert. Detailseite und Verwaltung sind ausgenommen — beide zeigen Laufendes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8c395ac051 |
Die Oberfläche nach Plex' Vorbild — Hintergrund, Schriftzug, Besetzung
Nach den Bildern deiner Plex-Installation umgebaut. Übernommen ist der Aufbau, nicht die Farbe: Plex ist bernsteingelb, Flaro bleibt Orchidee. **Kopfleiste oben** mit Marke, Suche und Konto. Die Suche gehört dorthin und nicht in jede Mediathek: wer einen Titel sucht, weiss oft gar nicht, in welcher er liegt. Sie geht quer durch alles. **Seitenleiste mit Icons** — echte SVG, keine Zeichen aus einer Schriftart. Ein ▶ aus Unicode sieht auf jedem System anders aus, lässt sich nicht einfärben und springt in der Grundlinie. Die aktive Zeile bekommt einen schmalen Balken links statt eines leuchtenden Blocks; das liest sich in einer langen Liste ruhiger. **Die Kachel steht jetzt an einer Stelle** statt an fünfen. Sie trägt den Haken für Gesehenes in der Ecke — wie bei Plex, ein Blick über das Raster genügt —, die Zahl offener Folgen an derselben Stelle, den Fortschrittsbalken unten und beim Überfahren einen Abspielknopf. Folgen sind dreizeilig: Serie, Folgentitel, „S14 · F22". **Reihen haben Pfeile.** Zwanzig Kacheln passen nicht in den Bildschirm, und quer zu scrollen geht mit dem Rad nur mit Umschalttaste. Die Überschrift nennt die Mediathek beim Namen: „Zuletzt in Dokumentationen hinzugefügt". **Mediatheken haben drei Reiter** — Empfehlungen, Mediathek, Kategorien. Darunter eine Filterzeile mit Genre, Sortierung und der Anzahl. Die Kategorien sind farbige Kacheln; die Farbe kommt aus dem Namen, nicht aus einer festen Liste, die jedes Genre kennen müsste, das TMDB je erfindet. **Die Detailseite** hat jetzt den unscharfen Hintergrund über die ganze Seite, den **Schriftzug des Films** statt der Systemschrift, eine Zeile mit Jahr, Genres, Laufzeit, Altersfreigabe und Bewertung, runde Knöpfe für gesehen/Favorit/weglegen, die Medienangaben als Tabelle — Datei, Video, Ton, Untertitel — und zuletzt Besetzung & Stab mit runden Porträts. **Dazu am Server:** ein Name. Ohne ihn zeigten die Clients den Hostnamen, und die App schrieb „localhost" über die Anmeldung. Vorgabe ist der Rechnername des Containers. --- **Der Fehler, den das erste Bild zeigte:** die Lupe in der Suchleiste war dreihundert Bildpunkte gross und lag über der halben Seite. Ein rohes `<svg>` ohne Breite füllt seinen Container; die Regel galt nur für `.icon`. Jetzt für jedes. 368 Tests grün. Am Container nachgesehen: „Der Untergang" mit eigenem Schriftzug, FSK 12, Bewertung 7,9, Genres Drama/Historie/Kriegsfilm, beide Tonspuren in der Tabelle, 22 Porträts darunter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
230beb9664 |
Mehrere Mediatheken, jede für sich
Bisher gab es in der Seitenleiste „Filme" und „Serien" — zwei feste Zeilen, hinter denen alles zusammenfiel, was der Server kannte. Wer „Filme" und „Dokumentationen" getrennt angelegt hatte, sah sie wieder in einem Topf. Das nahm die Entscheidung zurück, sie zu trennen. Jetzt führt die Seitenleiste **jede Mediathek einzeln**, mit Namen und Zahl, wie bei Plex und Jellyfin. Die Startseite bekommt je Mediathek eine Reihe mit dem zuletzt Hinzugefügten; steht mehr drin, als die Reihe zeigt, führt „Alle zeigen" dorthin. Die Mediathekenseite trägt den Namen im Titel, sucht innerhalb der Mediathek und lässt sich sortieren. **Am Server dafür:** `?sort=` auf `/movies` und `/series` — `title`, `added`, `year`. Der Titel immer als letzter Vergleich, sonst hätten zwei Filme aus demselben Jahr keine feste Reihenfolge und Seite 2 zeigte denselben Film noch einmal oder gar nicht. Die Liste der Bibliotheken nennt je Zeile einen Zählwert. „Dokumentationen · 0" sagt mehr als ein Eintrag, hinter dem sich nach dem Klick nichts findet — da ist der Unterschied zwischen „noch nicht gescannt" und „falscher Pfad" wenigstens sichtbar. `null` heisst „nicht gezählt", nicht „leer". Umbenennen und Entfernen, beides nur für Administratoren. **Umbenennen, mehr nicht:** die Art zu ändern hiesse, Filme in Serien zu verwandeln; den Pfad zu ändern hiesse, einen Bestand gegen einen anderen zu tauschen, während die Einträge stehenbleiben. Beides ist kein Bearbeiten, sondern eine neue Bibliothek. **Beim Entfernen bleiben die Mediendateien liegen.** Weg sind der Eintrag, die Zuordnungen und der Sehfortschritt aller Benutzer dazu; die Oberfläche sagt das vorher, und eine Prüfung hält fest, dass die Datei danach noch da ist. Ein Server, der beim Aufräumen in der Oberfläche fremde Filme löscht, ist einer, den man nicht noch einmal benutzt. --- **Der Fehler, den der Browser zeigte, und er betrifft jeden Betrieb:** nach einem Server-Update lief weiter die alte Oberfläche. Die Dateien unter `assets/` tragen eine Prüfsumme im Namen — ändert sich der Inhalt, ändert sich der Name. `index.html` trägt keine; sie nennt nur deren Namen. Lag sie im Zwischenspeicher, bekam der Browser weiter die alte App: nicht kaputt, sondern veraltet, und das fällt niemandem auf. Nachgemessen: geladen war `index-C0K3V8eg.js`, ausgeliefert wurde längst `index-DomHN4_b.js`. Jetzt zwei Lebensdauern — `no-cache` für die Seite, ein Jahr und `immutable` für die Bausteine. Mit Prüfung, und am Container nachgemessen. Der Beispielbestand bekommt zwei weitere Mediatheken, „Dokumentationen" und „Konzerte". Mit nur Filme und Serien liesse sich gar nicht prüfen, ob mehrere wirklich getrennt bleiben — zwei Bestände **derselben Art** sind der Fall, in dem es auffällt. 358 Tests grün. Am Container nachgewiesen: vier Mediatheken in der Seitenleiste mit Zählwerten, je eine Reihe auf der Startseite, und „Dokumentationen" zeigt zwei Titel statt sieben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
91eb934048 |
Die Weboberfläche — im Zuschnitt von Plex, vom Server selbst ausgeliefert
Einrichtung, Anmeldung, Mediathek, Detailseite mit Abspielen und Herunterladen, Zuordnung korrigieren, Verwaltung. Die Oberfläche aus der Jellyfin-Zeit ist dabei vollständig ersetzt: `types.ts`, `prefs.ts`, `contrast.ts`, `views/cards.ts`, `views/player.ts` und `views/settings.ts` sprachen Jellyfins Schnittstelle und hatten hier nichts mehr zu suchen. Seitenleiste links, waagerechte Reihen auf der Startseite, Plakate im Verhältnis 2:3, unscharfer Hintergrund hinter dem Kopf der Detailseite — das ist der Zuschnitt von Plex, weil er gefällt. **Die Farbe bleibt Flaros Orchidee**, nicht Plex' Orange; der Aufbau ist das Vorbild, nicht das Aussehen. Welche Bibliothek gezeigt wird, entscheidet die Seitenleiste. Eine zweite Umschaltung innerhalb der Seite wäre dieselbe Entscheidung an zwei Orten. --- **Der Zugang liegt jetzt auch in einem Cookie.** Ein `<img>`, ein `<video>` und ein Download-Link senden keine eigenen Kopfzeilen — eine App kann ihr Token in jede Anfrage legen, ein Browser nicht. Das Token stattdessen in die Adresse zu schreiben wäre der naheliegende Ausweg und stünde danach in jedem Zugriffsprotokoll, in jedem Verlauf und in jedem weitergegebenen Link. `HttpOnly`, damit kein eingeschlichenes Skript herankommt. `SameSite=Strict` gegen Anfragen von fremden Seiten. `Secure` nur über https — sonst liesse sich die Oberfläche im Heimnetz über http gar nicht benutzen. Die Apps benutzen weiterhin die Kopfzeile: ein zweiter Weg zum selben Token, kein zweites Verfahren. --- **Der Fehler, den erst der echte Container zeigte:** eine weisse Seite, ohne Hinweis worauf. Die Rückfallregel verlangt für jede Anfrage ohne eigenen Endpunkt einen angemeldeten Benutzer, und `/assets/index.js` hat keinen. Die statischen Dateien standen unter `UseAuthorization` — ein neuer Besucher bekam 401 auf genau das Skript, das ihm die Anmeldemaske zeichnen sollte. `/` kam durch, weil der SPA-Rückfall `AllowAnonymous` trägt; alles mit Dateiendung nicht. Jetzt liegen sie davor. Mit Prüfung. Beim Bau desselben Images der zweite: seit der Kontext die Projektwurzel ist, überschrieb `COPY server/` die `obj/`-Verzeichnisse aus dem Container mit denen vom Arbeitsrechner — die zeigen auf osx-arm64, gebaut wird linux-arm64, `dotnet publish` brach mit NETSDK1047 ab. Dafür gibt es jetzt eine `.dockerignore` in der Wurzel; die unter `server/` sucht Docker seit dem Umzug nicht mehr. --- Vier Prüfungen schlugen danach fehl, und zwar zu Recht: sie hiessen „ohne Anmeldung gibt es nichts", benutzten aber den Client, der nach dem Anmelden ein Cookie hält. Sie prüften also den Angemeldeten. Jetzt sagen sie ausdrücklich, dass sie ohne Zugang fragen. 313 Tests grün. Im Container nachgewiesen: Anmelden, Plakate, Abspielen, Staffeln und Folgen, Verwaltung — und ein Neuladen auf `/verwaltung`, das nicht in einem 404 endet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a7cf6c5def |
Halo heisst ab hier Flaro — die Marke stand im Weg
Der Upload zu App Store Connect scheiterte am Namen. „Halo" ist eine eingetragene Marke von Microsoft; im App Store heisst zwar keine App exakt so, aber reservierte und markenrechtlich belegte Namen tauchen in keiner Suche auf. Der einzige verlässliche Test war die Eingabe dort, und die ging daneben. **Der Name wurde diesmal vorher geprüft.** Zwanzig Kandidaten gegen den deutschen und amerikanischen App Store, auf exakte Treffer und auf ähnliche Namen. „Kavo" wäre dieselbe Wand gewesen — exakt belegt in beiden Stores, in Deutschland von KaVo Dental GmbH. „Flare" ist mit 42 ähnlichen Apps je Store der am dichtesten besetzte Name der ganzen Runde. **Flaro** ist in beiden frei und liest sich als eigenes Wort, nicht als Flare mit Tippfehler. Das bleibt eine Store-Suche und keine Markenrecherche — reservierte Namen ohne veröffentlichte App sieht sie nicht. Aber es ist mehr Prüfung, als beim letzten Mal stattfand. --- 2160 Textvorkommen in 273 Dateien, 49 Pfade. Betroffen ist diesmal auch der Server: `Flaro.Api`, `Flaro.Core`, `Flaro.Data`, `Flaro.Media`, `Flaro.Metadata`, `FlaroContext`, `FlaroPaths`, `flaro.db`, die Variablen `FLARO_DATA`, `FLARO_CACHE`, `FLARO_TRANSCODE`, `FLARO_WRITE_SIDECARS`, und das Image. **Vor dem Ersetzen geprüft**, ob „halo" irgendwo als Teil eines anderen Wortes steckt — und ob es im TMDB-Token vorkommt. Tat es nicht; ein blindes Ersetzen hätte den sonst zerlegt. `docs/superpowers/specs/` bleibt wie beim letzten Mal unangetastet: datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen. Das Server-Dokument hat stattdessen einen Nachtrag bekommen, damit es niemanden in die Irre führt. **Die Bundle-ID wandert mit**, zum zweiten Mal an einem Tag: `com.scarriffleservices.flaro`. Wer die vorige Fassung installiert hat, hat jetzt zwei Karteileichen auf dem Gerät — beide löschen, dann ist Ruhe. 308 Server-Tests grün, 368 im Swift-Paket, null Warnungen. iOS und tvOS bauen, das Image baut, und ein frischer Container läuft durch Einrichtung und Anmeldung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4b9a1e7b67 |
Flixy heißt ab hier Halo
Reine Umbenennung, kein Verhalten geändert — bewusst als eigener Commit, damit „umbenannt" und „umgebaut" im Nachhinein auseinanderzuhalten sind. 712 mal `Flixy`, 778 mal `flixy`, 7 mal `FLIXY` in 169 Dateien, dazu 25 Pfade. Ziele: `Halo`, `HaloTV`, `HaloTopShelf`, `HaloUITests`, `HaloKit` mit `HaloDesign`, `HaloDownloads`, `HaloPlayback` und `HaloProbe`. `MediaKit`, `JellyfinKit` und `DownloadWidget` tragen den Namen nicht und bleiben. `docs/superpowers/specs/` ist ausgenommen. Datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen; sie nachträglich umzuschreiben hiesse, die Vergangenheit zu glätten. **Die Bundle-ID wandert mit** — ausdrücklich so gewollt, mit bekanntem Preis: `com.scarriffleservices.halo` ist für iOS eine *andere* App. Die installierte Fassung bleibt daneben stehen, mit Downloads und Anmeldung in ihrem Container; Halo startet leer. Einmal neu anmelden, heruntergeladene Folgen einmal neu laden. Dasselbe gilt für die App-Gruppe, die sich TV-App und Top Shelf teilen. **Eine Folge davon, die beim Entscheiden noch nicht auf dem Tisch lag:** der Aufräumschritt aus dem vorigen Commit kommt an die alten Plex-Token nicht mehr heran. Der Schlüsselbund grenzt nach Team und Bundle-Kennung ab — was unter `…flixy` liegt, ist für `…halo` unerreichbar. Erledigt wird es trotzdem, nur anders: **die alte App löschen.** iOS räumt deren Schlüsselbund-Einträge beim Deinstallieren mit ab. Der Code bleibt stehen, weil er für jeden Stand vor dieser Umbenennung richtig ist und im Jahr einen Startvergleich kostet. Die Projektdatei ist aus `project.yml` neu erzeugt, der alte Ordner gelöscht statt umbenannt — sie steht ohnehin nicht im Repo. Das Verzeichnis des Repos selbst heißt weiter `Flixy`; das umzubenennen ist ein Handgriff ausserhalb des Projekts. 368 Tests grün. iOS und tvOS bauen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3af6c3112f |
Das Web ist jetzt ein Projekt
Schritt 2 des Plans, fertig. Aus 1700 Zeilen in einer Datei sind zwölf Module geworden: api, prefs, contrast, icons, util, types und die Ansichten für Anmeldung, Startseite, Mediathek, Einstellungen, Kacheln und Player. Alles typgeprüft, und die Prüfung läuft scharf — noUncheckedIndexedAccess inbegriffen. An einer Stelle hat der Compiler mich zu Recht aufgehalten: Object.fromEntries verliert die Typen jedes einzelnen Feldes, und geblieben wäre nur ein doppelter Cast, der die Prüfung stilllegt statt sie zu bestehen. Jetzt steht es ausgeschrieben da. hls.js wird erst beim Abspielen geladen. Es ist größer als der ganze Rest zusammen, und wer nur stöbert, braucht es nie: der Start fällt damit von 632 auf 39 Kilobyte. Geprüft mit einem erfundenen Medienserver dahinter: Seite kommt an, gebaute Dateien werden ausgeliefert, Anfragen gehen weiter, und die Startseite zeichnet Insel, Aufmacher und Reihen. Die einzelne Datei bleibt liegen, bis das Projekt sich bewährt hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |