Commit Graph
5 Commits
Author SHA1 Message Date
ScarriffleandClaude Opus 5 156c740330 Halo-Server, Stufe 1: Benutzer, Anmeldung, Einrichtung
Der Anfang eines eigenen Medienservers — C#, .NET 10, SQLite, im Container für
amd64 und arm64. Stufe 1 von fünf: er spielt noch keinen Film ab, aber ein
Fremder kann ihn in Betrieb nehmen, und genau daran scheitern solche Projekte
sonst als Erstes.

**Was läuft, gegen den laufenden Container gemessen** — nicht gegen einen
Prüfstand: Gesundheitsfrage, Einrichtungsassistent, zweiter Einrichtungsversuch
abgelehnt (409), Anmeldung, falsches Passwort abgelehnt (401), und der ganze
Weg der Code-Anmeldung vom Bildschirm bis zum Token.

**Drei Entscheidungen, die beim Lesen Fragen aufwerfen:**

*Zeitpunkte liegen als Unix-Millisekunden in der Datenbank.* SQLite hat keinen
Zeittyp; EF legt einen `DateTimeOffset` sonst als Text mitsamt Zonenversatz ab,
und dann steht `…T12:00+02:00` neben `…T11:00Z` — derselbe Augenblick, als Text
falsch herum sortiert. EF weigert sich deshalb, `ExpiresAt > jetzt` überhaupt zu
übersetzen; das war der erste echte Fehlschlag und ist jetzt eine
Typumwandlung für das ganze Modell statt je Eigenschaft.

*Token sind keine JWT.* Der Server schlägt sie nach, statt sie zu prüfen — nur
so lässt sich ein verlorenes Telefon sofort aussperren. In der Datenbank steht
allein der Fingerabdruck.

*Der Code am Fernseher holt nie selbst ein Token ab.* Dafür gibt es den
Abholschein, den nur das anfragende Gerät kennt. Ohne ihn wären sechsstellige
Codes ein Türschloss mit einer Million Schlüsseln — in Minuten durchprobiert.
Dass ein erratener Schein nichts bekommt, ist die wichtigste der 55 Prüfungen.

**Zwei Fehler, die erst der echte Container zeigte:**

`EnsureExists` legte nur an und prüfte nicht, ob sich schreiben lässt — bei
einem vorhandenen, aber gesperrten Verzeichnis meldete es Erfolg, und der
Server starb zehn Zeilen später an „unable to open database file". Diese
Meldung nennt weder das Verzeichnis noch verrät sie, dass es um Dateirechte
geht. Jetzt wird tatsächlich geschrieben, und die Meldung nennt Variable,
Pfad, die eigene Kennung und den nächsten Schritt.

Und: Docker legt den Einhängepunkt eines frischen benannten Volumes als `root`
an, wenn das Verzeichnis im Image fehlt. Das traf jeden, der ohne Bind Mount
startet. Die drei Verzeichnisse werden jetzt im Image angelegt und übereignet.

**Die Lizenz ist eine Bauvorschrift, keine Fussnote.** GPL-2.0-only verträgt
sich nicht mit Apache-2.0 — dessen Patentklausel gilt unter Abschnitt 6 als
„weitere Einschränkung". Darum steht in `LICENSES.md`, dass vor jedem
`PackageReference` die Lizenz zu prüfen ist, und darum ist das Testwerk NUnit
(MIT) und nicht xUnit (Apache-2.0). Alle vier Pakete sind geprüft MIT.

Die Apps stehen unter MPL-2.0 statt GPL, weil GPL und App Store sich nicht
vertragen — Apple entfernte 2011 VLC aus dem Store, nachdem ein Rechteinhaber
sich auf Abschnitt 6 berufen hatte.

55 Tests grün. Image baut für linux/amd64 und linux/arm64.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 12:13:47 +02:00
ScarriffleandClaude Opus 5 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>
2026-10-03 11:12:32 +02:00
ScarriffleandClaude Opus 5 2fcf6821c8 Plex ist raus — und nahm zwei stille Fehler mit
Vollständig entfernt: das Paket `PlexKit` samt 108 Tests, der
Web-Authenticator, die Anmeldung über plex.tv, die Serverauswahl und alles,
was daran hing. 43 Dateien gelöscht, 68 Stellen in 28 weiteren bereinigt.

**Zwei Dinge hätten beim blossen Streichen still das Falsche getan.**

`playbackUnavailableMessage` deckte im Else-Zweig zwei Lagen gleichzeitig ab
— „es ist Plex" und „niemand ist angemeldet". Wer die Serverart naiv
herausstreicht, bekommt als nicht angemeldeter Nutzer „Jellyfin-Wiedergabe
steht aus" statt „Server nicht erreichbar". Die Unterscheidung hängt jetzt
daran, ob überhaupt ein Server da ist.

Die Schlüsselbund-Einträge `plex.account` und `plex.server` wurden allein
über den Authenticator aus dem gelöschten Paket entfernt. Danach stünde
`plex.account` in keiner Zeile dieses Projekts mehr: wer sich nie abgemeldet
hat, trüge ein gültiges Token am fremden Konto bis in alle Ewigkeit mit sich,
ohne dass irgendein Weg es noch löschen könnte. `purgePlexLeftovers()` räumt
beide Token und fünf Voreinstellungen einmalig ab — und fasst nichts an, wo
nichts liegt, damit kein Jellyfin-Nutzer dafür seinen Mediathekenspeicher
verliert.

**Was Nutzer sehen.** Die Berechtigungsfrage fürs lokale Netz sagte wörtlich,
die App brauche es, „um deinen Plex-Server zu finden" — das stand so im
Systemdialog. Der Anmeldebildschirm hatte zwei Knöpfe, und der hervorgehobene
war der für Plex; der verbliebene erbt die Hervorhebung, denn ein Knopf, der
aussieht wie die zweite Wahl, lässt nach der ersten suchen. 14 verwaiste
Textbausteine sind weg, vier umformuliert.

Darunter einer, der schlicht nicht mehr stimmte: `signIn.privacy` versprach,
das Passwort erreiche diese App nie. Beim Jellyfin-Rückfall auf das Passwort
tut es das sehr wohl. Die Zusage, die weiterhin gilt, ist die über fremde
Dienste — und nur die steht jetzt da.

**Was ausdrücklich bleibt:** `MediaBackend`. Diese Abstraktion hat zwei
Server gleichzeitig getragen, und der Steckplatz, den Plex räumt, ist genau
der, auf den Halo kommt. `ServerKind` fällt dagegen weg — mit einem Fall ist
es ein Bool, das `backend != nil` schon beantwortet.

Nebenbei zog die README nach. Sie beschrieb noch einen „nativen Plex-Client"
mit zwei Playern und VLCKit, beides seit Längerem nicht mehr wahr.

368 Tests grün (von 476 minus die 108 aus PlexKitTests, kein anderer).
iOS und tvOS bauen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 11:10:20 +02:00
ScarriffleandClaude Opus 5 12bfc9e530 Abspielen am Fernseher — mit Apples Player und unseren Spuren
Schritt sieben. Wiedergabe, Spurwahl, Titelzeile, „Intro überspringen".

**Die schwierige Stelle war die Spurwahl.** Unsere Ton- und Untertitelwahl
geht als `audioStreamIndex` an Jellyfin und verlangt bei einem Transcode
einen **neuen Stream**. Apples eingebauter Spurwähler zeigt dagegen die
Auswahlgruppen des **laufenden** Streams — beim Transcode genau eine. Er
ist die richtige Oberfläche für die falsche Liste. Unsere Menüs sitzen
deshalb über `transportBarCustomMenuItems` daneben; `customInfoViewControllers`
wäre falsch, denn die Tonspur ist ein Bedienelement der Wiedergabe und kein
Begleitmaterial.

**Der Neuaufbau wird angesagt, nicht erklärt.** Jeder Eintrag, der Sekunden
mit schwarzem Bild kostet, trägt das als Untertitel — vorher, nicht
hinterher. Wer drückt, sieht den Haken sofort wandern, weil das Menü sich
schliesst und darin kein Platz für einen Kreisel ist. Das schwarze Bild
deckt eine Überlagerung in `contentOverlayView` zu, die hinter den
Bedienelementen liegt und keinen Fokus nimmt — `customOverlayViewController`
täte beides nicht und machte die Fernbedienung währenddessen tot. Und
solange umgebaut wird, sind alle Einträge gesperrt: sonst räumt `restart()`
eine Engine ab, die eine andere gerade baut.

**Die Regel dahinter ist jetzt prüfbar.** „Wann kostet ein Spurwechsel einen
Neuaufbau" lag bisher mitten im Vollzug von `selectAudioStream` und liess
sich nur an einem echten Server mit mehreren Tonspuren auslösen. Als
`TrackMenu` steht sie neben den übrigen Entscheidungsregeln, mit sieben
Tests — darunter der Fall, auf den der Kommentar dort seit jeher hinweist:
stimmt die Spurzahl nicht überein, ist die Zuordnung über die Reihenfolge
falsch, und es muss neu aufgebaut werden.

**Zwei stille Fehler nebenbei.** Die Tonsitzung stand hinter `#if os(iOS)`
und wäre auf tvOS nie gesetzt worden — verfügbar ist sie dort seit 9.0, man
hätte es nur daran gemerkt, dass sich die Wiedergabe gegen anderen Ton
nicht durchsetzt. Und `PlayerModel.downloads` ist jetzt optional statt
eines leeren Koordinators: ein leerer bräuchte einen `ModelContainer`, und
der ist das Einzige, wofür diese App SwiftData benutzt.

**Belegt statt vermutet.** Im Simulator lief die Wiedergabe gegen die
Attrappe, und die Titelzeile zeigte „Unbekannt". Statt an AVKit zu drehen,
hat ein Protokolleintrag die Frage beantwortet: `externalMetadata` wird
gesetzt, der Wert stimmt, und „Unbekannt" ist wörtlich das, was die
Attrappe für unbekannte Kennungen liefert. Geblieben ist davon nur die
Erkenntnis, dass die Beschriftung auf das Item warten muss — beim
Austausch des Players gibt es noch keines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-29 19:55:02 +02:00
GuidoandClaude Opus 5 1ed64bf166 Phase 0: Projektgerüst, Design-Tokens und Navigation
Legt das Fundament für den Plex-Client an:

- XcodeGen-Projekt nach dem Muster von Onyx (com.scarriffleservices,
  strict concurrency, Projektdatei nicht eingecheckt)
- FlixyKit mit vier Modulen: PlexKit, FlixyDesign, FlixyPlayback,
  FlixyDownloads. macOS steht als Plattform mit drin, damit die
  Logik-Tests ohne Simulator laufen.
- FlixyDesign: dunkle Farb-Tokens, Schriftskala, Poster- und
  Querformat-Kacheln, Aufmacher-Bild. Die Akzentfarbe fließt über die
  Umgebung und wird per WCAG-Rechnung lesbar gehalten.
- PlexKit: Kopfzeilen-Identität, Transport-Protokoll, Fehlerarten und
  die Medienmodelle samt nachsichtigem Decoder.
- FlixyPlayback: VideoPlayerEngine-Protokoll und PlaybackDecider — die
  eine Funktion, an der Phase 2 das Transcoding andockt.
- Ausfahrbare Seitenleiste auf dem iPhone, Split-View auf dem iPad.
- 45 Tests, alle grün.

VLCKit 4.0.0-a24 ist vorab geprüft: baut über SPM unter Swift 6 mit
strict concurrency. Die iOS-Slice ist mit 239 MB allerdings gewichtig,
deshalb bekommt sie in Phase 1 ein eigenes Target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:04:40 +02:00