fix(shorts): flux vide + menu absent + flèches invisibles (Rumble)
CI / build-and-test (push) Successful in 14m55s

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.
This commit is contained in:
2026-10-02 10:26:58 -04:00
parent 4e44d85717
commit 3ab61821b2
2 changed files with 18 additions and 4 deletions
@@ -54,7 +54,6 @@
</div>
}
@if (current(); as v) {
<!-- Menu FAB : toutes les options (fournisseur, langue, actions). -->
<app-player-menu [(open)]="menuOpen">
<div class="pm-section-label">Fournisseur</div>
@@ -146,6 +145,7 @@
<span class="pm-item-label">Passer (vidéo illisible)</span>
</button>
</app-player-menu>
@if (current(); as v) {
<!-- Capteur tactile (mobile uniquement) : l'iframe mange les
touchers, donc le swipe up/down passerait à la trappe. Cette
@@ -208,12 +208,12 @@
<!-- Navigation précédent / suivant : SUR la vidéo (hors auto-hide du
chrome, hors geste tactile — stopPropagation dans les handlers). -->
<button type="button" (click)="prev(); $event.stopPropagation()" [disabled]="!canPrev()"
class="absolute left-1.5 top-1/2 z-20 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-30"
class="absolute left-1.5 top-1/2 z-20 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-40"
aria-label="Short précédent">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M15.5 4.5 8 12l7.5 7.5z"/></svg>
</button>
<button type="button" (click)="next(); $event.stopPropagation()" [disabled]="!canNext()"
class="absolute right-1.5 top-1/2 z-20 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-30"
class="absolute right-1.5 top-1/2 z-20 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-40"
aria-label="Short suivant">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8.5 4.5 16 12l-7.5 7.5z"/></svg>
</button>
+15 -1
View File
@@ -1492,6 +1492,16 @@ export class WatchShortComponent implements OnDestroy {
return;
}
const provider = this.provider();
// Rumble : on va droit au chemin historique (flux natif rumble.com/shorts
// + remplissage recherche). Le catalogue `type=shorts` ne rend que des
// vidéos <=75 s HORizontales (Rumble n'expose aucune dimension : ni largeur
// ni hauteur dans les pages, vignettes en crop fixe 16:9 — vérifié 9/9),
// ce qui donnait un feed de « briques » 16:9 au lieu des shorts officiels.
if (provider === 'rumble') {
this.catalogActive = false;
this.loadLegacyFeed();
return;
}
// Shorts via le CATALOGUE d'abord (`/api/search?type=shorts` : cache,
// provenance, feature-flags). Twitch garde son flux de clips historique
// (pas d'équivalent catalogue : la recherche serveur renvoie des VOD).
@@ -1646,8 +1656,12 @@ export class WatchShortComponent implements OnDestroy {
const raw = (res.items || []).filter(v => !!v.videoId);
this.nextCursor.set(res.nextCursor || null);
if (!res.nextCursor) this.exhaustedQueries.add(this.currentQuery());
// NOTE : `list` est DÉJÀ dédupliqué par chaque appelant (ligne 1671 /
// branche YouTube). Un second passage ici marquait tout comme « déjà vu »
// et posait une liste VIDE en silence — écran « Aucun short » sans erreur
// ni requête suivante (bug reproduit au navigateur, fix racine).
const show = (list: Video[]) => {
const deduped = this.dedup(list);
const deduped = list;
this.items.set(deduped);
this.index.set(0);
this.error.set(null);