Retour utilisateur : les flèches Préc./Suiv. n'étaient toujours pas
visibles et la position attendue est précisée — une à GAUCHE et une à
DROITE de la vidéo, centrées verticalement.
- Flèches remises aux bords gauche/droite du cadre (top-1/2,
-translate-y-1/2) avec contraste renforcé : ring-2 white/50, fond
black/55 — lisibles sur toute image.
- Sorties de @if (current()) comme le menu : TOUJOURS montées, donc
présentes même sur l'état de flux mort (désactivées) — on ne cherche
plus où naviguer.
- Chaque changement de vidéo réveille les contrôles 2,5 s (poke sous
matchMedia hover : sur tactile le chrome reste visible en permanence,
un poke y aurait armé le minuteur et masqué le chrome — régression
détectée par la recette mobile puis corrigée).
- Recette Playwright desktop+mobile : 17/17 (bords à 6 px, centrage
vertical ±1 px, fondu repos 0.18, plein au mouvement, mobile plein,
pas de chevauchement 2D avec la flèche retour, flèches + menu
présents sur feed mort). Build + test:section + test:shorts-catalog
verts avant commit.
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.
Trois symptômes, deux racines — diagnostiqués au navigateur (Playwright
desktop + mobile) et hors navigateur (vraies réponses API + fonctions
front réelles importées via ts-node) :
1. Double dedup() dans handleShortsResponse : show() re-dédupliquait une
liste déjà dédupliquée par son appelant → le second passage marquait
tout comme « déjà vu » et posait items=[] en silence → écran
« Aucun short à afficher » sans erreur ni requête suivante, dès que
le catalogue type=shorts rendait moins de 3 items (aléa de la
requête tirée au sort). Racine des écrans morts, présente depuis le
commit 983c6cb.
2. Menu FAB et flèches Préc./Suiv. vivaient dans @if (current()) :
absents de TOUS les états vides, donc impossibles à utiliser
exactement quand le flux meurt (impossible de changer de
fournisseur). Le menu est désormais toujours monté (z-30 au-dessus
des placeholders), et la flèche désactivée passe en opacity 0.4
(au 1er item, Préc. était quasi invisible à 0.3).
3. Format : Rumble n'expose AUCUNE dimension — vérifié : vignettes og:image
toutes en crop fixe 16:9 (9/9 échantillons, 1280x720 et 480x270) et
aucun width/height dans les pages vidéo → un filtre d'orientation
par vignette est impossible, la classification reste durée-seule.
En revanche le flux natif rumble.com/shorts (les vrais shorts
officiels) devient la voie primaire : loadFeed va droit au chemin
historique au lieu du catalogue qui ne rendait que des brives 16:9.
Tests verts avant commit : npm run build, test:section,
test:shorts-catalog, test:filters.
- 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é.
- Methode « touch listener b4 loading the iframe » : l'URL de l'embed Twitch
n'est produite qu'apres un premier geste — navigator.userActivation.
hasBeenActive a l'arrivee (clic sur un lien = fenetre d'activation de 5 s),
sinon pointerdown / keydown / wheel. Le premier chargement de l'iframe
tombe donc dans un contexte d'activation utilisateur.
- Invite « Touchez pour charger la vidéo » sur /watch et /shorts pendant
l'attente (sinon spinner infini sur /watch, cadre noir sur /shorts).
- /shorts : pas d'avance auto tant que l'embed n'est pas charge — le flux
ne switchait plus d'une video a l'autre sans jamais jouer.
- Le nudge (recharge &r=1 au premier geste) est conserve pour les videos
montees sans geste (avance auto, arrivee avec activation deja consommee).
- Verifs instance locale : 8/8 (arrivee a froid : invite + montage au geste)
et 6/6 (arrivee avec activation : montage immediat + recharge unique).
- 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
L'iframe Shorts utilisait referrerpolicy=strict-origin-when-cross-origin
alors que /watch utilise 'origin'. C'est la seule difference restante entre
les deux pages pour Dailymotion (URL identique, allow_embed=true) et elle
fait repondre 'Forbidden' au player. On aligne sur 'origin'.
Revert du referrerPolicy helmet (no-op pour les iframes qui ont leur propre
attribut, risque de side-effects inutile sur les CDN de vignettes).
Boutons fournisseurs avec disque aux couleurs de la marque + glyphe SVG (play YT, bulle Twitch, initiale sinon) ; icone etincelles pour Pour toi. Selecteur de langues Profil/FR/EN/FR+EN/Toutes (persiste newtube.shorts.langFilter, invite compris) : chaque flux passe par effectiveShortsLangs (resolveShortsLangs pur + spec). Docs 9.9.
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.
- format: des que largeur/hauteur sont connues, l'orientation fait autorite (horizontale/carree => jamais short, meme <75 s). Repli duree reserve aux providers muets
- dimensions mappees: Dailymotion (width,height), PeerTube (aspectRatio), Odysee (value.video.width/height) => validation verticale reelle
- YouTube: langue via snippet.defaultAudioLanguage (filtre FR/EN)
- autoplay: demarrage muet, suppression du deblocage automatique au scroll/selection (reload d'embed hors geste => ecran noir); son via le bouton dedie
- PeerTube embed: muted=1 pour l'autoplay
- server/search-filters.mjs: miroir de la regle d'orientation + tests mis a jour
- autoplay: les Shorts demarrent muets (comme Watch) pour garantir l'autoplay des iframes; le son se debloque a la premiere interaction (unlockSound) et reste controlable
- proxy prod: ajoute les alias /proxy/twitch-api, /proxy/twitch-auth, /proxy/dm, /proxy/odysee (evite le fallback SPA -> parse JSON 'status 200 ok:false'); forwardJson accepte les deux prefixes. Corrige aussi la validation des clips Twitch morts
- Dailymotion: allow_embed demande dans les flux shorts/trending -> les videos non-embeddables ('Forbidden') sont exclues
- langues: mapping language (Dailymotion/PeerTube/Odysee) + shortsLangOk applique a tous les fournisseurs; en+fr toujours acceptes
- PeerTube shorts: durationMin/Max cote serveur (plus de videos longues au hasard)
- Twitch: validation helix/clips de nouveau fonctionnelle + autoplay muet
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).