Files
Flaro/project.yml
T
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

326 lines
13 KiB
YAML

name: Halo
options:
bundleIdPrefix: com.scarriffleservices
deploymentTarget:
iOS: "26.0"
macOS: "26.0"
tvOS: "26.0"
createIntermediateGroups: true
groupSortPosition: top
developmentLanguage: en
settings:
base:
DEVELOPMENT_TEAM: PP34X97WS3
SWIFT_VERSION: "6.0"
MARKETING_VERSION: "0.1.0"
CURRENT_PROJECT_VERSION: "1"
SWIFT_STRICT_CONCURRENCY: complete
packages:
HaloKit:
path: Packages/HaloKit
targets:
Halo:
type: application
platform: iOS
# Ausdrücklich aufgeschrieben, statt Xcode raten zu lassen.
#
# Xcode legt für jedes Ziel von selbst ein Schema an — aber im
# Benutzerordner, und `xcodegen generate` räumt den mit weg. Danach
# findet `xcodebuild` kein Schema mehr und kann die Pakete nicht
# auflösen. Hier steht es im Projekt und überlebt jedes Neuerzeugen.
scheme:
testTargets:
- HaloUITests
sources:
# Das Icon ist ein Bündel, also technisch ein Ordner. Ohne diesen
# Ausschluss läuft XcodeGen hinein und trägt icon.json und play.svg
# einzeln ein — dann fehlt das Icon, ohne dass irgendwo ein Fehler
# auftaucht.
#
- path: Halo
excludes:
- "AppIcon.icon"
- path: Halo/AppIcon.icon
type: file
dependencies:
- target: DownloadWidget
# Die neutrale Grundschicht. Ausdrücklich genannt, obwohl sie über
# JellyfinKit ohnehin im Baum hängt: die App soll sie direkt benutzen und
# nicht davon abhängen, dass ein bestimmter Server sie mitschleppt.
- package: HaloKit
product: MediaKit
# Der Server. Steht auf der neutralen Schicht, nicht darin — kommt ein
# zweiter dazu, steht er daneben.
- package: HaloKit
product: JellyfinKit
- package: HaloKit
product: HaloDesign
- package: HaloKit
product: HaloPlayback
- package: HaloKit
product: HaloDownloads
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo
PRODUCT_NAME: Halo
# Verweist auf AppIcon.icon. Der frühere leere AppIcon.appiconset im
# Asset-Katalog ist entfernt — zwei Dinge namens AppIcon, und Xcode
# nimmt das falsche.
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
# iPhone und iPad aus demselben Target.
TARGETED_DEVICE_FAMILY: "1,2"
CODE_SIGN_STYLE: Automatic
info:
path: Halo/Info.plist
properties:
CFBundleDisplayName: Halo
UILaunchScreen:
UIColorName: LaunchBackground
# Der harte Schalter für „nur dunkel". Eine Ansicht mit
# .preferredColorScheme(.dark) allein reicht nicht: Systemdialoge,
# Statusleiste und Tastatur richten sich danach nicht.
UIUserInterfaceStyle: Dark
# Alle drei bleiben eingetragen: die Info.plist sagt, was die App
# grundsätzlich kann. Was davon gerade gilt, entscheidet zur Laufzeit
# OrientationLock über den App-Delegaten — Hochformat beim Blättern,
# Querformat im Player.
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
# Ohne diesen Modus hält iOS die Wiedergabe an, sobald die App in den
# Hintergrund geht — und Bild-in-Bild gäbe es gar nicht.
UIBackgroundModes:
- audio
# Die Ausfuhr-Erklärung zur Verschlüsselung.
#
# `false` heißt: die App nutzt ausschließlich Verschlüsselung, die
# unter die Ausnahmeregelung fällt. Das trifft hier zu — HTTPS über
# URLSession und der Schlüsselbund für die Tokens, beides vom
# Betriebssystem gestellt. Eigene Algorithmen gibt es keine.
#
# Ohne diesen Eintrag fragt App Store Connect bei JEDEM Build erneut
# („Missing Compliance"), und der Build lässt sich bis dahin nicht
# verteilen.
#
# Zu beachten, falls sich das ändert: solange nur Dateien vom eigenen
# Server abgespielt werden, bleibt es bei Standardverfahren. Käme
# irgendwann etwas mit eigener Verschlüsselung dazu — DRM etwa —,
# gehört dieser Wert überprüft.
ITSAppUsesNonExemptEncryption: false
# Ohne diesen Schlüssel lehnt ActivityKit jede Anfrage ab — und
# zwar wortlos, was die Suche nach dem Grund unangenehm macht.
NSSupportsLiveActivities: true
NSAppTransportSecurity:
# Medienserver im Heimnetz laufen überwiegend auf blankem HTTP —
# Jellyfin ab Werk auf Port 8096. Diese Ausnahme erlaubt genau das,
# ohne die Transportsicherheit für das offene Netz abzuschalten.
NSAllowsLocalNetworking: true
# Seit iOS 14 verlangt jeder Zugriff auf Adressen im eigenen Netz eine
# Freigabe. Fehlt der Text, verweigert das System sie kommentarlos und
# die Serversuche findet dauerhaft nichts.
NSLocalNetworkUsageDescription: >-
Halo benötigt Zugriff auf das lokale Netzwerk, um deinen
Jellyfin-Server zu erreichen und Medien direkt von ihm zu laden.
# Die Fassung für den Fernseher.
#
# **Eigene Ansichten, geteilte Modelle.** Die Quellenpfade sind die ganze
# Aussage dieses Blocks: `HaloTV/` gehört nur hierher, `Halo/Session/`
# gehört beiden. Keine Datei muss dafür umziehen — XcodeGen nimmt Pfade je
# Ziel, und die iOS-Fassung sieht von der ganzen Sache nichts.
HaloTV:
type: application
platform: tvOS
# Aus demselben Grund ausgeschrieben wie bei Halo — siehe dort.
scheme: {}
sources:
# Die eigenen Ansichten.
- path: HaloTV
# Die Modelle.
- path: Halo/Session
excludes:
# Ein Fernseher dreht sich nicht.
- "OrientationLock.swift"
# Heruntergeladen wird auf dem Fernseher nichts.
#
# `DownloadRefiller.swift` und `DownloadService.swift` kommen
# trotzdem mit: `PlayerModel` nennt den Nachfüller in einem Zweig,
# der auf tvOS nie läuft (`downloads` ist dort `nil`), und der
# Nachfüller wiederum den Dienst. Sie auszuschliessen hiesse, eine
# Plattformfrage mitten in zweitausend Zeilen Wiedergabelogik zu
# tragen — teurer als zwei Dateien mitzuübersetzen, die niemand
# aufruft. Beide sind reine Übersetzer ohne UIKit.
# Reines Modell trotz des Ordnernamens: LoadState, ErrorText und
# MediaRoute liegen zusammen in einer Datei.
- path: Halo/Views/LoadState.swift
type: file
# Der Player. **Ohne PlayerView** — die zeichnet auf iOS das Bild und
# alle Bedienelemente selbst; auf dem Fernseher macht das Apples
# AVPlayerViewController. Das Modell darunter ist dasselbe.
- path: Halo/Views/Player/PlayerModel.swift
type: file
- path: Halo/Views/Player/VideoSurface.swift
type: file
- path: Halo/Branding.swift
type: file
# Die Texte. Dieselben Schlüssel, dieselben Übersetzungen.
- path: Halo/Resources/Localizable.xcstrings
type: file
dependencies:
- package: HaloKit
product: MediaKit
- package: HaloKit
product: JellyfinKit
- package: HaloKit
product: HaloDesign
- package: HaloKit
product: HaloPlayback
# Nur damit `PlayerModel` den Typ nennen darf. Angelegt wird nie einer:
# HaloTVApp reicht `nil` durch, und damit gibt es auf tvOS auch keinen
# ModelContainer und kein SwiftData.
- package: HaloKit
product: HaloDownloads
- target: HaloTopShelf
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.tv
PRODUCT_NAME: Halo
# Nur Apple TV.
TARGETED_DEVICE_FAMILY: "3"
# Kein AppIcon.icon: Icon Composer deckt iOS, macOS und watchOS ab,
# tvOS nicht. Der Name zeigt auf die Brand-Assets-Gruppe im Katalog,
# und der Katalog liegt unter HaloTV/Resources.
ASSETCATALOG_COMPILER_APPICON_NAME: "App Icon & Top Shelf Image"
CODE_SIGN_STYLE: Automatic
CODE_SIGN_ENTITLEMENTS: HaloTV/Halo.entitlements
info:
path: HaloTV/Info.plist
properties:
CFBundleDisplayName: Halo
UIUserInterfaceStyle: Dark
ITSAppUsesNonExemptEncryption: false
# Über dieses Schema schickt das Regal zurück in die App. Ohne den
# Eintrag kennt tvOS es nicht, und ein Druck auf ein Plakat tut
# nichts — ohne Fehler, ohne Hinweis.
CFBundleURLTypes:
- CFBundleURLName: com.scarriffleservices.halo.topshelf
CFBundleURLSchemes:
- halo
# Was hier **nicht** steht, und warum:
#
# UILaunchScreen — tvOS hat kein Startbild dieser Art.
# UISupportedInterfaceOrientations — ein Fernseher dreht sich nicht.
# NSSupportsLiveActivities — ActivityKit fehlt im tvOS-SDK.
# CFBundleURLTypes — die Anmeldung braucht kein Rückkehr-Schema.
# UIBackgroundModes: audio — ein Apple TV schickt keine App in den
# Hintergrund, aus dem sie weiterspielen müsste.
NSAppTransportSecurity:
NSAllowsLocalNetworking: true
NSLocalNetworkUsageDescription: >-
Halo benötigt Zugriff auf das lokale Netzwerk, um deinen
Jellyfin-Server zu erreichen.
# Das Regal über der App im tvOS-Menü.
#
# Eine eigene Erweiterung, weil tvOS sie zeichnet, während die App **nicht**
# läuft — und mit weniger als einer Sekunde Zeit. Sie liest deshalb nur eine
# Datei aus der App-Gruppe, die die App beim Laden der Startseite ablegt:
# kein Netz, kein Schlüsselbund, kein Blättern.
HaloTopShelf:
type: tv-app-extension
platform: tvOS
sources:
- path: HaloTopShelf
dependencies:
- package: HaloKit
product: MediaKit
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.tv.topshelf
PRODUCT_NAME: HaloTopShelf
CODE_SIGN_STYLE: Automatic
CODE_SIGN_ENTITLEMENTS: HaloTopShelf/HaloTopShelf.entitlements
SKIP_INSTALL: YES
info:
path: HaloTopShelf/Info.plist
properties:
CFBundleDisplayName: Halo
# **Die Erweiterung hat ihre eigene Info.plist.**
#
# Und damit ihre eigene Transportsicherheit: die Ausnahme in der
# Info.plist der App gilt hier nicht. Ohne sie blockt das System jedes
# Plakat weg, das über blankes HTTP vom Jellyfin im Heimnetz kommt —
# lautlos, und das Regal bleibt leer. Genau so sieht „geht halt nicht"
# aus.
NSAppTransportSecurity:
NSAllowsLocalNetworking: true
NSExtension:
NSExtensionPointIdentifier: com.apple.tv-top-shelf
NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).TopShelfProvider
# Nur zum Hinsehen: fährt die App mit der Attrappe in einen bestimmten
# Zustand und hält sie dort an, damit von außen fotografiert werden kann.
# Kein Teil der App, wird nie mitgeliefert.
HaloUITests:
type: bundle.ui-testing
platform: iOS
sources:
- HaloUITests
dependencies:
- target: Halo
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.uitests
TEST_TARGET_NAME: Halo
TARGETED_DEVICE_FAMILY: "1,2"
CODE_SIGN_STYLE: Automatic
GENERATE_INFOPLIST_FILE: YES
# Die Live Activity für Downloads. Eine eigene Erweiterung, weil iOS die
# Darstellung im Sperrbildschirm in einem getrennten Prozess zeichnet — die
# App selbst läuft zu dem Zeitpunkt meist gar nicht.
# Dieselben Quellen, dasselbe Aussehen.
DownloadWidget:
type: app-extension
platform: iOS
sources:
- path: DownloadWidget
dependencies:
- package: HaloKit
product: HaloDownloads
- package: HaloKit
product: HaloDesign
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.downloadwidget
PRODUCT_NAME: DownloadWidget
CODE_SIGN_STYLE: Automatic
SKIP_INSTALL: YES
# Von XcodeGen erzeugt statt von Hand geschrieben. Eine eigene Datei ohne
# CFBundleIdentifier führt zu einer Erweiterung ganz ohne Kennung — und
# der Fehler lautet dann „not prefixed with the parent app's bundle
# identifier", was auf eine falsche Fährte führt.
info:
path: DownloadWidget/Info.plist
properties:
CFBundleDisplayName: Halo Downloads
NSExtension:
NSExtensionPointIdentifier: com.apple.widgetkit-extension