feat(client): frontend (formulaire d'ajout + file d'attente) #3

Merged
kyuno merged 1 commit from feature/frontend-ui into feature/download-queue 2026-09-16 09:30:10 +02:00
Owner

Résumé

Empilée sur #2 (elle-même sur #1) — à merger en dernier de la chaîne. Interface web complète : jusqu'ici tout était backend/API, cette PR ajoute l'UI par-dessus les routes existantes.

Détails

  • client/src/views/downloadForm/ : sélecteur de mode (vidéo/audio/miniature) partagé entre les deux façons d'ajouter une vidéo — URL directe, ou recherche de chaîne (GET /api/channel) avec bouton "Ajouter" par résultat.
  • client/src/views/queuePanel/ : sonde GET /api/queue toutes les 1,5s, affiche statut/progression/erreur par élément, bouton d'annulation et lien de téléchargement natif (<a download>, pas de fetch+blob — c'est le navigateur qui gère le téléchargement).
  • client/src/api/ : clients fetch typés vers le backend, chaque await dans son propre try/catch avec message d'erreur distinct (réseau/HTTP/JSON).
  • Rendu des résultats de recherche via createElement/textContent, jamais innerHTML avec du texte venant de YouTube (titres affichés tels quels, pas de risque d'injection).
  • QueueItem gagne un champ title optionnel (purement informatif, jamais utilisé pour construire la commande yt-dlp) — affichage plus lisible qu'une URL brute dans la file.
  • client/tsconfig.json/build.mjs : cible relevée à ES2022 (nécessaire pour Error.cause, utilisé dans les clients API).
  • Vitest ajouté côté client, pour l'instant seulement sur les fonctions pures de format.ts — pas de jsdom, le câblage DOM (formulaires, rendu) est vérifié manuellement dans le navigateur.

Deux bugs réels trouvés en testant dans un vrai navigateur (pas seulement en lisant le code)

  1. CSS : .download-form__options[hidden] restait affiché — un display: flex de ma propre feuille de style écrasait la règle [hidden] { display: none } du navigateur (même spécificité, la feuille de l'auteur gagne). Les panneaux d'options vidéo/audio s'affichaient tous les deux en même temps. Corrigé (&[hidden] { display: none; }).
  2. server/src/routes/channel.ts : ne mappait vers 502 que les YtDlpError (code de sortie non nul) — pas les erreurs brutes de spawn (ex. binaire yt-dlp introuvable, ENOENT), qui remontaient donc en 500 générique au lieu du message propre attendu. Repéré en testant sans yt-dlp installé localement. Corrigé + test de non-régression (channelRoute.test.ts).

Vérifications locales

  • npm run lint (Biome, racine) : clean
  • npm run typecheck + npm test + npm run build, serveur et client : clean, 60 tests serveur + 7 tests client
  • Testé en navigateur réel (pas juste les tests automatisés) : layout, bascule de mode, ajout par URL directe → apparition immédiate dans la file, recherche de chaîne → message d'erreur propre (testé sans yt-dlp installé localement, ce qui a justement révélé le bug ci-dessus), affichage du statut/erreur d'un élément en échec.

🤖 Generated with Claude Code

## Résumé Empilée sur #2 (elle-même sur #1) — à merger en dernier de la chaîne. Interface web complète : jusqu'ici tout était backend/API, cette PR ajoute l'UI par-dessus les routes existantes. ## Détails - `client/src/views/downloadForm/` : sélecteur de mode (vidéo/audio/miniature) partagé entre les deux façons d'ajouter une vidéo — URL directe, ou recherche de chaîne (`GET /api/channel`) avec bouton "Ajouter" par résultat. - `client/src/views/queuePanel/` : sonde `GET /api/queue` toutes les 1,5s, affiche statut/progression/erreur par élément, bouton d'annulation et lien de téléchargement natif (`<a download>`, pas de fetch+blob — c'est le navigateur qui gère le téléchargement). - `client/src/api/` : clients fetch typés vers le backend, chaque `await` dans son propre `try/catch` avec message d'erreur distinct (réseau/HTTP/JSON). - Rendu des résultats de recherche via `createElement`/`textContent`, jamais `innerHTML` avec du texte venant de YouTube (titres affichés tels quels, pas de risque d'injection). - `QueueItem` gagne un champ `title` optionnel (purement informatif, jamais utilisé pour construire la commande yt-dlp) — affichage plus lisible qu'une URL brute dans la file. - `client/tsconfig.json`/`build.mjs` : cible relevée à ES2022 (nécessaire pour `Error.cause`, utilisé dans les clients API). - Vitest ajouté côté client, pour l'instant seulement sur les fonctions pures de `format.ts` — pas de jsdom, le câblage DOM (formulaires, rendu) est vérifié manuellement dans le navigateur. ## Deux bugs réels trouvés en testant dans un vrai navigateur (pas seulement en lisant le code) 1. **CSS** : `.download-form__options[hidden]` restait affiché — un `display: flex` de ma propre feuille de style écrasait la règle `[hidden] { display: none }` du navigateur (même spécificité, la feuille de l'auteur gagne). Les panneaux d'options vidéo/audio s'affichaient tous les deux en même temps. Corrigé (`&[hidden] { display: none; }`). 2. **`server/src/routes/channel.ts`** : ne mappait vers `502` que les `YtDlpError` (code de sortie non nul) — pas les erreurs brutes de spawn (ex. binaire yt-dlp introuvable, `ENOENT`), qui remontaient donc en `500` générique au lieu du message propre attendu. Repéré en testant sans yt-dlp installé localement. Corrigé + test de non-régression (`channelRoute.test.ts`). ## Vérifications locales - `npm run lint` (Biome, racine) : clean - `npm run typecheck` + `npm test` + `npm run build`, serveur et client : clean, 60 tests serveur + 7 tests client - **Testé en navigateur réel** (pas juste les tests automatisés) : layout, bascule de mode, ajout par URL directe → apparition immédiate dans la file, recherche de chaîne → message d'erreur propre (testé sans yt-dlp installé localement, ce qui a justement révélé le bug ci-dessus), affichage du statut/erreur d'un élément en échec. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-16 09:29:10 +02:00
Interface web complète : formulaire d'ajout (URL directe ou recherche de
chaîne) et panneau de file d'attente avec sondage de la progression.

- views/downloadForm/ : sélecteur de mode (vidéo/audio/miniature) partagé,
  ajout direct par URL, recherche de chaîne (GET /api/channel) avec ajout
  par résultat. Construction DOM via createElement/textContent (jamais
  d'innerHTML avec du texte venant de YouTube), pas juste par prudence :
  ça évite aussi tout risque d'injection depuis un titre de vidéo.
- views/queuePanel/ : sonde GET /api/queue toutes les 1,5s, affiche statut/
  progression/erreur par élément, annulation et lien de téléchargement
  natif (<a download>, pas de fetch+blob).
- api/ : clients fetch typés (channelApi, queueApi), chaque await dans son
  propre try/catch avec message d'erreur adapté (réseau vs HTTP vs JSON).
- QueueItem gagne un champ title optionnel (purement informatif, jamais
  utilisé pour construire la commande yt-dlp) pour un affichage plus
  parlant qu'une URL brute.
- client/tsconfig.json + build.mjs : cible ES2022 (nécessaire pour
  Error.cause, jusque-là ES2020).
- Vitest ajouté côté client (fonctions pures de src/format.ts ; le câblage
  DOM est vérifié manuellement, pas de jsdom pour l'instant).

Deux bugs réels trouvés et corrigés en testant dans un vrai navigateur
(pas seulement en lisant le code) :
- CSS : un attribut `hidden` sur un conteneur `display: flex` se faisait
  écraser par cette règle (même spécificité, la feuille de l'auteur gagne)
  - les deux panneaux d'options restaient visibles en même temps.
- server/src/routes/channel.ts : ne mappait vers 502 que les YtDlpError
  (code de sortie non nul), pas les erreurs brutes de spawn (ex. binaire
  introuvable - ENOENT), qui remontaient donc en 500 générique. Corrigé +
  test de non-régression.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 6c5e29246c into feature/download-queue 2026-09-16 09:30:10 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/youtubeDownloader#3
No description provided.