feat: recherche YouTube globale, avec défilement infini #11
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/youtube-search"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Résumé
Empilée sur #10 (toujours ouverte) — à merger après elle.
Nouveau formulaire « Recherche YouTube » (mots-clés + « Limite » optionnelle), branché sur la zone de résultats existante : miniatures, durée, bouton « Ajouter » par ligne. Pas de « Tout ajouter » pour la recherche, comme demandé.
Pourquoi une « session » côté serveur (mesuré avec le vrai yt-dlp)
La version naïve — relancer
ytsearchN:pour chaque page et trancher — n'est pas viable :-jsans--lazy-playlistDonc : un seul yt-dlp par recherche (
--flat-playlist --lazy-playlist -j), dont les pages sont servies depuis un tampon → ordre cohérent, aucun doublon, chaque résultat récupéré une fois.Le parcours n'avance qu'à la demande : quand le tampon a 20 résultats d'avance, la lecture de stdout est suspendue (nouvelle option
onSpawnderunYtDlp). Le tube se remplit et yt-dlp bloque sur son écriture (mesuré : plus aucune ligne pendant 10 s de pause). On n'aspire donc pas ~590 résultats — ni ~30 requêtes vers YouTube — quand l'utilisateur ne défile pas.API
POST /api/search{ query, max?, limit? }→201 { id, videos, nextOffset }GET /api/search/:id?offset=&limit=→200 { videos, nextOffset }DELETE /api/search/:id→204nextOffset: null= fin.400requête invalide ou hors bornes,404recherche inconnue/expirée (« relancez-la »),502yt-dlp échoue avant le moindre résultat.Cycle de vie des sessions
Chaque recherche ouverte garde un processus yt-dlp (suspendu) : 4 simultanées max (la moins récemment utilisée est fermée), expiration à 10 min d'inactivité / 30 min de vie, fermeture dès que la dernière page est servie, sur
DELETE(liste remplacée côté client), aupagehide(fermeture/rechargement de l'onglet), et à l'arrêt du serveur. La requête est validée (1–200 caractères, sans caractère de contrôle) et transmise en un seul argumentytsearchN:<requête>: jamais interprétable comme une option, jamais un moyen de faire récupérer une autre URL.À savoir
min-height: 13rem, et la colonne du formulaire défile au lieu de rogner (la page, elle, ne défile toujours jamais). Mesuré : 1920×950 → liste de 406 px, colonne fixe ; 1366×650 → 208 px, colonne défilante ; 800×450 → tout reste atteignable. Si trois entrées empilées te paraissent trop, des onglets (URL / recherche / chaîne) libéreraient ~130 px — dis-le-moi.Vérifications
npm run lint(Biome) + typecheck + build serveur/client : clean ; tests : 189 serveur (dont 62 nouveaux : session, service, route, validation,onSpawn) + 35 client.DELETE, ni au plafond (max) ; requêtes invalides → 400/404.pagehide; playlist et chaîne en non-régression.requestAnimationFrameet même unIntersectionObservertrivial ne se déclenchaient plus), j'ai donc piloté le défilement avec un observateur factice (mêmes appelsobserve/unobserve/disconnect, déclenchement à la demande). LeIntersectionObservernatif n'a pas été exercé en conditions réelles — usage standard (racine = élément défilant, marge, réarmement parunobserve/observe), mais à confirmer d'un coup d'œil dans ton navigateur : lancer une recherche sans limite et faire défiler la liste.🤖 Generated with Claude Code