Balayage de la couche d'overrides de index.css : les 75 jetons neutres
slate/zinc/gray qui n'étaient pas couverts (36 fichiers) sont mappés sur les
variables du thème — fonds, textes, bordures, anneaux, divide, placeholders,
dégradés + variants hover/focus/focus-within/disabled/group-hover, alpha via
color-mix. Barre de recherche, menus, panneaux, cartes et résultats de
recherche suivent donc le thème appliqué (dark/light/blue/black) au lieu de la
palette slate figée. Volontairement exclus : bg-white/text-black (boutons
inversés), pastilles « inversées » conditionnelles au thème clair, chrome
au-dessus des vidéos, rouges sémantiques.
Panneau d'informations ⓘ des Shorts re-colorié dans son propre scope
(fond/bordures/textes/pastilles d'action → panel/text/muted), les mêmes tokens
restant blancs ailleurs (rail, méta). Marqueur border-l-4 des titres de page
sur --accent.
Cartes vidéo harmonisées : titre text-sm font-semibold leading-snug
(résultats de recherche, chaîne, thèmes), méta text-xs, rayon rounded-lg sur les
cadres, pastille de durée sans font-mono, titre « Abonnements » avec le
marqueur d'accent.
Contour fournisseur 1 px autour de chaque cadre vidéo, à la couleur du premier
stop du dégradé de la pastille (table unique en CSS, ids longs + courts) :
résultats de recherche, grille chaîne, pages thèmes, watch (lecteur + « à
suivre »), shorts, historique, aimés/à regarder plus tard, détail de playlist.
Tests verts (search, filters, kind, section, highlight, shorts-catalog,
api 41/41) ; vérif UI Playwright desktop 1280x900 + mobile 390x844 sur 4
thèmes : 250 assertions + 13 couleurs de contour au vert.
- Supprime les flèches Préc./Suiv. aux bords gauche/droite (troubles
d'affichage récurrents) : la navigation passe au rail vertical ancré au
bord DROIT de la vidéo, centré au bureau et ancré au-dessus de la méta
sur mobile, avec des flèches ↑/↓ plus grosses (44 px).
- Même rail : informations, j'aime, s'abonner (pastille = avatar), commentaires,
partager, lecture auto. Toujours monté (comme le FAB), désactivé sans vidéo.
- Bouton ⓘ : panneau d'informations (titre, vues, engagement, chaîne,
description, commentaires → Watch). Colonne à droite au bureau (la vidéo
rétrécit, jamais masquée : --shorts-panel-w retiré du calcul de taille),
tiroir plein écran sur mobile. Échap ferme.
- Transition de short : le doigt fait glisser le cadre (chrome solidaire),
résistance aux extrémités, seuil de bascule 25 % de la hauteur, ressort
sous le seuil, éjection + emboîtement au-delà (Web Animations, respecte
prefers-reduced-motion). Aucun état de lecture modifié.
- Lecture auto : interrupteur réel (rail + menu), persisté, respecté par le
minuteur et par la fin de vidéo YouTube.
- Tap sur un contrôle du rail = plus de play/pause parasite (garde
isChromeTarget dans onTouchStart/onTouchEnd).
- FAB menu conservé (haut droit, déplaçable) ; son touchmove ne déplace plus
le cadre en même temps.
Demandes (page /shorts) : retrouver les flèches Préc./Suiv. là où se
trouve le menu, et des contrôles presque invisibles au repos pour
laisser la vidéo respirer (ton pro).
- Flèches déplacées du milieu des côtés vers la rangée HAUTE du cadre,
centrées : même niveau que le FAB menu, sans chevaucher la flèche
retour mobile (gauche) ni le FAB (droite). Le toast « Lien copié »
passe en z-30 pour rester lisible sous la rangée.
- Fondu au repos : nouvelles règles index.css — flèches (.shorts-ctrl)
et FAB à 0.18 d'opacité quand le chrome est au repos (2,5 s sans
souris), pleins au moindre mouvement (chromeVisible) ou au survol
direct (:hover), jamais atténués quand le panneau est ouvert
(pm-fab-open). Sur tactile, le chrome ne se masque jamais (pas de
mousemove : comportement voulu, mobile inchangé).
- Le minuteur d'immobilité est armé au chargement (pokeChrome sous
matchMedia('(hover: hover)')) : sans un premier mousemove,
chromeVisible restait true et le fondu ne s'armait jamais.
- mouseenter sur l'iframe : en entrant sur la vidéo, le document
parent ne reçoit pas les mouvements internes à l'iframe — l'entrée
réveille les contrôles.
Recette Playwright desktop+mobile, 14/14 : position haute centrée,
absence de chevauchements, repos 0.18 (flèches + FAB), retour plein
après mouvement réel, mobile plein en permanence. Build + test:section
verts avant commit.
- Flèches : elles vivaient EN DEHORS du cadre (et se fadaient avec le
chrome auto-hide). Elles sont désormais posées dans le cadre, à mi-hauteur
sur les bords, toujours visibles (classe `shorts-chrome` retirée), sur
mobile comme sur bureau, et le stopPropagation protège le geste tactile.
- Rumble : la plateforme est16:9 mais n'expose ni largeur ni hauteur →
l'embed tombait dans un cadre 9:16 (étiré / réduit). `isWideEmbed()`
(pur + testé) décide de la boîte : dimensions connues => orientation
réelle, sinon repli « rumble = paysage » ; la CSS `.embed-wide` rend une
iframe 16:9 centrée (434x244 dans un cadre 434x771, mesuré). Les vidéos
verticales (dimensions connues) gardent le cadre pleine hauteur.
- État vide : un fournisseur sans résultat laissait un cadre NOIR muet
(ni squelette, ni message) — il affiche désormais « Aucun short à afficher
pour ce fournisseur » + Réessayer.
Tests : test:shorts-catalog (3 cas isWideEmbed), test:kind, test:section,
test:search verts ; build ng OK ; sondage Playwright local : flèches dans
le cadre + navigation suivant/précédent réelle, boîte embed-wide 16:9
centrée, short vertical inchangé (iframe pleine hauteur), état vide affiché.
- PeerTube : l'adaptateur pt.ts reconstruisait l'item SANS `url` et le pont
catalogue ne conservait l'URL que pour Odysee → /shorts reconstruisait
l'embed sur l'instance ACTIVE → « Sorry this video does not exist » sur
toute vidéo fédérée. `url` est désormais miroitée dans VideoItem (contrat
v2), propagée par PtAdapter et gardée par catalogShortToVideo :
peerTubeHostOf() dérive enfin le host d'origine. Vérifié de bout en bout :
embed tube.blueben.net → 200 + vrai titre, cas de test dédié dans
shorts-catalog.spec.
- FAB du menu : déplaçable au pointeur (seuil 6 px, position en % du cadre,
persistée en localStorage). Le clic qui suit un glissement n'ouvre pas le
menu, et le panneau s'ouvre TOUJOURS vers l'intérieur du cadre (classes
pm-opens-left / pm-opens-right / pm-opens-up posées par applyAnchor) :
sinon overflow:hidden le rognait dès qu'on déplaçait le bouton.
- Bureau : --shorts-chrome 9rem → 4rem (la vidéo passe de 691 à 771 px de
haut en 1280x900, 389 → 434 de large) et menu compact au-delà de 768 px
(FAB 36 → 30 px, panneau 233 → 202 px). Mobile inchangé (36 / 636 px).
Tests : test:shorts-catalog (nouveau cas PeerTube), test:kind,
test:section, test:search verts ; build ng OK ; sondage Playwright 19/19
sur l'instance locale (layout, glissement + restauration, embed PeerTube
200 sans erreur).
- app-player-menu : stopPropagation wheel/touchstart/touchend sur le host —
les handlers window:wheel et window:touch* de /shorts ne volaient plus les
gestes sur le panneau (impossible de scroller jusqu'en bas du menu, tap =
play/pause, swipe = changement de short)
- /shorts : ArrowUp/Down ignore quand le menu est ouvert
- /shorts : le FAB passe en haut a DROITE du cadre (la fleche retour mobile
z-20 passait par-dessus le FAB piege dans la scene z-10)
- Look/taille : FAB 40->36px, panneau 288->240px, items compacts,
max-height 70vh/480 -> 65vh/430 (plus de rognage par overflow:hidden),
pastille en degrade + relief, panneau rounded-xl + highlight interne
- PlayerMenuComponent : FAB circulaire translucide (haut-gauche) + panneau
deroulant, commun aux deux pages (position, style, open/close, clic exterieur)
- /shorts : consolidation de tous les boutons (fournisseurs, langue, actions)
dans le menu FAB ; suppression du rail a droite et de la rangee du haut
- /watch : actions (cinema, qualite, j'aime, playlist, transcription,
telechargement, partage, ouvrir) deplacees dans le menu FAB
- AddToPlaylistComponent : variante menuItem pour s'integrer au menu
- styles .pm-item/.pm-sep/.pm-section-label dans index.css
Chrome auto-hide au hover desktop (2,5 s). Capteur tactile mobile : swipe garanti (l'iframe mangeait les touchers), tap = play/pause YT ou 6 s d'acces direct au lecteur sinon. Fleche retour accueil mobile, cadre quasi plein ecran. Avance auto : handshake YT listening (les evenements player n'arrivaient jamais), onStateChange canonique, retry borne anti-prefetch. Docs 9.9.
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.
Cinq correctifs signalés sur le mode mobile et le mode desktop.
1. La modale « @ » était confinée dans le header (desktop + mobile)
<header> portait `backdrop-blur-sm`, et `backdrop-filter` fait de l'élément
le containing block de ses descendants en position:fixed. Les overlays du
provider-picker (et du menu Ctrl+K) se résolvaient donc contre le header :
mesuré 1440x64 au lieu de 1440x900, dialogue réduit à 32px de haut pour 466px
de contenu, haut de la fenêtre inatteignable.
Le flou passe sur une couche enfant (`-z-10`) qui n'est pas un ancêtre des
overlays : `fixed` redevient relatif au viewport.
Desktop : dialogue 512x468 centré. Mobile : 358x686, entièrement visible.
2. La barre de thèmes disparaissait sous le header (desktop + mobile)
`--header-offset` vaut 56/64px, mais le header mesure 57/65px (bordure) et
118px quand la feuille de recherche mobile est ouverte. La barre était donc
déjà glissée dessous, et totalement invisible dans ce dernier cas
(hauteur visible mesurée : 0px).
HeaderComponent publie maintenant sa hauteur réelle dans `--header-measured`
via ResizeObserver ; `--header-offset` l'utilise quand elle existe et garde
la valeur statique en repli. Barre visible dans tous les cas, y compris
feuille de recherche ouverte.
3. Barre de thèmes : icônes seules sur mobile
Les libellés sont masqués sous md (`aria-label` conservé pour
l'accessibilité). 7 pastilles visibles au lieu de 3, sans scroll.
4. Avatar en pastille
`.avatar-circle` est renforcé : `rounded-full` dans le markup plus
`border-radius: 9999px !important`, `aspect-ratio: 1/1`, min-width/min-height
et `line-height: 1`, pour qu'aucun style de thème ne puisse le dégrader en
rectangle.
Vérifié : audit navigateur sur 17 routes x 320/390/768/1440 px — aucun
débordement horizontal, barre de thèmes visible partout ; tests du dépôt verts ;
build production OK.
Le header était en grid-cols-[auto_1fr_auto] avec une barre de recherche
non-wrappable (7 chips + input + 2 boutons, aucun min-w-0) : ~1000px de
largeur intrinsèque dans un viewport de 375px. Toutes les routes
débordaient horizontalement et les boutons Connexion/Inscription partaient
hors écran.
Socle
- --header-h / --header-offset / --app-vh centralisent la métrique du
header : supprime les top-16 codés en dur (channel-tabs était en top-0 et
glissait sous le header au scroll).
- Safe-area iOS, viewport-fit=cover, color-scheme/theme-color.
- dvh partout (100vh > hauteur visible sur iOS), overflow-x: clip au lieu de
hidden (hidden cassait silencieusement tous les sticky).
- Cible tactile 44px sur pointeur grossier, skip link, focus-ring clavier,
scroll-rail pour les tab strips.
Header
- Barre mobile (burger, logo, recherche, menu ⋮) + barre desktop inchangée.
- Le menu ⋮ expose ce qui disparaissait : bande des thèmes, préférences,
historique, favoris, téléchargements, thème, connexion/inscription.
- Recherche en feuille sous le header avec focus automatique.
- Échap / clic extérieur / navigation ferment les panneaux transitoires.
Drawer
- Partait à inset-y-0 : les 56 premiers px du menu étaient derrière le
header. Descend sous le header, bouton fermer intégré, verrouille le
scroll, se ferme à Échap.
Barre de recherche
- Chips masquées sous lg, remplacées par un bouton résumé qui ouvre le
sélecteur de fournisseurs. Suggestions en dvh, menu Ctrl+K plein écran
sur mobile.
Pages
- Shorts : cadre 9:16 contraint par dvh ET vw (il débordait en paysage),
Préc./Suiv. qui étaient hors du conteneur height-capped.
- Watch : barre d'actions qui wrappe, mode cinéma en opacity-0 group-hover
donc invisible au doigt, thumbnails fluides, playsinline sur les iframes.
- Historique, thèmes, chaîne, playlists, préférences, sessions : rails
swipables, min-w-0, truncate, en-têtes qui wrappent.
Vérifié : audit navigateur (CDP) sur 17 routes x 7 largeurs (320→1440),
zéro débordement horizontal ; 19 tests d'interaction mobile ; suites de
tests du dépôt vertes (api 40/40, transcript 27/27, search, suggest,
playlists, preferences, subscriptions, highlight, telemetry).