Le navigateur servait un CSS périmé après mise à jour (aucun invalidateur
sur /static). Les liens CSS/JS portent désormais ?v=<mtime des fichiers
statiques> : toute modification du design est visible immédiatement chez
tous les clients, sans hard-refresh ni bump manuel de version.
- Rails Nouveautés séparés : 🆕 animés (tri date Kitsu) et 🆕 séries & films
(French-Stream) — chaque type garde son rail, plus d'écrasement mutuel
- Section 🎭 Explorer : chips type × genre — animés via catégories Kitsu,
séries (/<genre>-series-/) et films (/films/<genre>/) via French-Stream ;
état dans l'URL (/discover?t=serie&g=medical), partageable
- Scraper : browse(media_type, genre) + catalogue YAML surchargeable ;
parsing div.short mutualisé avec les nouveautés
- Films réels retirés du mode « Animés » (ils suivent le rail séries)
- Endpoints /api/discover (latest_anime/latest_serie), /api/discover/genres,
/api/discover/browse — parcours filtré par la préférence du compte
- Warmup démarrage : latest_by_type()
En mode séries, le rail Nouveautés était vide : discover.latest() triait par
date de sortie Kitsu (catalogue animés uniquement) puis tronquait à 24 AVANT
le filtrage — les séries et films réels, absents de Kitsu (donc sans date),
étaient systématiquement évincés.
- allowed (types autorisés par préférence) descend dans discover.latest()
et filtre dès la collecte des sources
- remplissage du rail équilibré par type (round-robin) : en mode « les deux »,
8 animés / 8 séries / 8 films au lieu de 24 animés
- test de non-régression : 9 séries conservées face à 30 animés datés
Ces deux sections sont issues de Kitsu (catalogue animés) — elles
n'ont pas de sens pour un compte en préférence « séries » et montraient
des animés malgré le filtre des nouveautés. Elles ne sont même plus
calculées (aucun appel Kitsu) et l'UI masque les sections vides.
+1 test (Kitsu simulé) — 115 au total.
- Colonne users.content_preference (anime|serie|both, défaut both) avec
migration automatique des bases existantes.
- GET /auth/me expose la préférence ; PUT /auth/preferences la met à
jour (422 si valeur invalide).
- /api/search : filtre les résultats par media_type ET ignore les
sources hors périmètre (une source anime-only n'est pas interrogée
en mode séries). Les films restent associés aux animés.
- /api/discover : nouveautés filtrées selon la même règle.
- Sélecteur compact dans la topbar (⛩/📺/✨), persisté serveur.
- 7 tests dédiés (roundtrip, rejet invalide, filtrage search/discover,
skip de sources) — 114 au total, ruff clean.
Prête pour la première source de séries : il ne reste qu'un module
SourceScraper avec media_types=("serie",) à écrire.
- torznab : tvsearch sans q sert les nouveautés (RSS sync + test Sonarr)
au lieu d'une erreur 400 — ajoute TorznabService.latest_releases()
- qbit : POST /torrents/createCategory + GET /torrents/categories stateful
(Sonarr crée sa catégorie tv-sonarr puis la revérifie)
- qbit : /app/preferences expose save_path=/downloads (health Sonarr)
- sonarr : verify=False sur httpx — reverse proxy swizzin en certificat
auto-signé (usage LAN)
- /api/v2/* : login SID (mot de passe = clé Torznab), app/version,
torrents/info (progression temps réel), properties (content_path),
add (rejoue le grab encodé dans le .torrent de service, dédupliqué
par infohash SHA-1), delete (± fichiers), pause/resume
- Les grabs Sonarr sont marqués « sonarr:<hash>| » dans source_key →
suivis de bout en bout : Sonarr importe, renomme et range les épisodes
dans sa bibliothèque, puis retire le torrent de la file Ohm
- L'indexeur Torznab embarque les paramètres du grab dans l'announce
- README : nouveau mode « client de téléchargement » recommandé (Remote
Path Mapping documenté), blackhole en variante minimale
- 3 nouveaux tests (flux complet add → suivi → import → delete)
- DELETE /api/downloads/{id} (?delete_file=true pour effacer aussi le fichier)
- Événement SSE « removed » → toutes les pages ouvertes se synchronisent
- Page Téléchargements groupée par animé : groupes repliables (dépliés si
actifs), compteurs par statut, taille totale, tri par numéro d'épisode
- Actions par groupe : 🧹 retirer les terminés, ✖ annuler les actifs
- Limite de liste 200 → 2000 tâches
- Détection de version via l'API Gitea, application via Watchtower
- Router système, UI admin (intégrations), bandeau de mise à jour
- Dockerfile multi-étapes, docker-compose, scripts/release.sh
- Version centralisée dans app/version.py (pyproject ou OHM_VERSION au build)