Nouvelle liste dans la section Vous de la barre latérale, avec un bouton
horloge a cote du coeur sur toutes les cartes (accueil, themes, provider-
themes, recherche).
- db : les 4 fonctions de listes (like/unlike/isLiked/list) prennent un
paramètre `tag` — la table `tags` sert déjà de mot-clés génériques, donc
zéro migration.
- api : les 4 routes likes sont générées par registerVideoTagRoutes(prefix,
tagName) ; /api/user/watch-later = même fabrique, même enrichissement
yt-dlp, même middleware cookie-aware. Entrée OpenAPI ajoutée.
- front : LikesService prend un VideoTag, like-button un @Input() list
(icône horloge, ambre quand enregistré, libellés a11y dédiés) — un seul
composant pour les deux boutons, pas de copie.
- cartes de recherche : la grille partagée n'avait AUCUN bouton ; cœur +
horloge ajoutés, masqués sur les lives et cartes chaîne (coin bas-gauche
déjà occupé par le compteur de spectateurs / pas des vidéos).
- route /library/watch-later = LikedComponent via data.list (titre, icône,
placeholder et état vide pilotés par la route), clés nav.watchLater FR/EN.
Tests : npm run test:api 41/41 (nouveau test watch-later : ajout, statut,
indépendance vis-à-vis des likes, liste, retrait, 400, 401) + 21 autres
suites vertes. Build OK, instance locale 4200 rebuildée. Sondage Playwright
desktop+mobile : 12/14 (2 faux positifs du sondage : accordéon replié sur
une route hors /library, 3×401 console pendant la phase déconnectée).
Barre de recherche
- Panneau de filtres unifié (remplace le ProviderPicker) : sources, type
(vidéos/shorts/direct/chaînes), période (heure/jour/semaine/mois/année),
durée (<4 / 4-20 / >20 min) et tri. Pastilles + roving tabindex
(←/→ dans un groupe, ↑/↓ entre groupes), focus trap, Échap, Ctrl/⌘+Maj+F,
recherches récentes, « mémoriser par défaut », « Réinitialiser ».
- Pastilles des filtres actifs sous la barre (retrait en un clic) + compteur.
- Typeahead : un seul keydown (fin du double Enter), ↑/↓ bouclants,
Home/End, PageUp/PageDown, Entrée valide la ligne surlignée (sinon
recherche brute), Tab complète sans chercher, Échap ferme puis vide,
ligne « Rechercher <q> », défilement de l'option active, aria à jour.
- Focus : le panneau se referme dès que le focus quitte la barre
(focusout + relatedTarget + activeElement), pointerdown neutralisé sur les
lignes pour garder le focus dans l'input (plus de scintillement).
- Opérateurs en clair : `linux live:`, `tuto today: long:` deviennent des
filtres et sont retirés de la requête (autocomplétion après le `:`).
Filtres côté serveur
- Modèle partagé : src/app/search/filters.ts + server/search-filters.mjs.
- /api/search?type=&duration=&period=&sort= : InnerTube (upload_date, type,
duration, features), Data API v3 (type, videoDuration, publishedAfter),
yt-dlp --dateafter ; post-filtrage pour les providers sans filtre natif.
Un champ manquant ne fait jamais disparaître un résultat.
- Filtres dans l'URL (partageables) + adapters + SearchService (clé de cache).
- Correction : la recherche Shorts renvoyait 0 (ShortsLockupView/GridShelfView
non mappés, id/titre dans l'endpoint) -> mappage + shelf + vignette/vues.
Tests / docs
- 23 tests unitaires (clavier, focus, opérateurs, panneau de filtres),
server/tests/search-filters.test.mjs (npm run test:filters) + CI,
scénarios e2e des filtres, README/api/README/todo/MCP à jour.