From d8dc2636902eb9660d3e41a61cace105ea4c6325 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 16 Sep 2026 23:12:16 +0200 Subject: [PATCH] fix(client): page sans overflow, listes scrollables, miniatures agrandies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Layout en hauteur pleine (html/body/.shell à 100%/100vh, overflow hidden sur body) : la page ne défile plus jamais. Seules download-form__results et queue-panel__list défilent en interne (flex:1 1 0 + min-height:0 + overflow-y:auto), propagé depuis .shell via min-height:0 à chaque niveau flex descendant. - .shell__main : ratio de colonnes 3fr/2fr -> 2fr/1fr et max-width 960px -> 1280px, pour une liste de vidéos plus large. - Miniatures de résultats agrandies (96x54 -> 144x81, ratio 16:9) et titre sur 2 lignes (line-clamp) plutôt qu'une troncature sur une seule ligne, pour mieux différencier les vidéos au premier coup d'œil. Vérifié en navigateur avec des données de test (16 éléments de file, 12 résultats factices injectés) : bodyScrollHeight reste égal à windowInnerHeight (page sans overflow) alors que les deux listes ont un scrollHeight très supérieur à leur clientHeight (défilement interne réel, pas juste visuel). Co-Authored-By: Claude Sonnet 5 --- client/src/shell/Shell.scss | 21 +++++++++--- client/src/styles/base.scss | 10 ++++++ .../src/views/downloadForm/DownloadForm.scss | 33 +++++++++++++------ client/src/views/queuePanel/QueuePanel.scss | 12 +++++++ spec/architecture.md | 1 + 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/client/src/shell/Shell.scss b/client/src/shell/Shell.scss index 06e181b..78d8a0c 100644 --- a/client/src/shell/Shell.scss +++ b/client/src/shell/Shell.scss @@ -1,10 +1,11 @@ .shell { display: flex; flex-direction: column; - min-height: 100vh; + height: 100vh; margin: 0; &__header { + flex: 0 0 auto; padding: 1rem 1.5rem; background: var(--color-surface); border-bottom: 1px solid var(--color-border); @@ -17,13 +18,19 @@ } } + // min-height: 0 est nécessaire ici (et à chaque niveau descendant + // jusqu'aux listes scrollables) : par défaut un enfant flex ne + // rétrécit jamais sous la taille de son contenu, ce qui empêcherait + // tout overflow interne et ferait déborder toute la page à la place. &__main { - flex: 1; + flex: 1 1 auto; + min-height: 0; display: grid; grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; gap: 1.25rem; padding: 1.5rem; - max-width: 960px; + max-width: 1280px; margin: 0 auto; width: 100%; box-sizing: border-box; @@ -31,16 +38,20 @@ @media (min-width: 800px) { &__main { - grid-template-columns: 3fr 2fr; - align-items: start; + grid-template-columns: 2fr 1fr; + grid-template-rows: 1fr; } } &__panel { min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 12px; padding: 1.25rem; + overflow: hidden; } } diff --git a/client/src/styles/base.scss b/client/src/styles/base.scss index cb285af..b4ae1aa 100644 --- a/client/src/styles/base.scss +++ b/client/src/styles/base.scss @@ -22,6 +22,16 @@ box-sizing: border-box; } +// La page elle-même ne défile jamais : seules les listes internes (file +// d'attente, résultats de recherche) le font, voir Shell.scss et les +// vues concernées (chacune doit propager height:100%/min-height:0 +// jusqu'à son propre élément scrollable pour que ça fonctionne). +html, +body { + height: 100%; + overflow: hidden; +} + body { margin: 0; background: var(--color-bg); diff --git a/client/src/views/downloadForm/DownloadForm.scss b/client/src/views/downloadForm/DownloadForm.scss index c0187a2..25e5435 100644 --- a/client/src/views/downloadForm/DownloadForm.scss +++ b/client/src/views/downloadForm/DownloadForm.scss @@ -2,6 +2,16 @@ display: flex; flex-direction: column; gap: 0.85rem; + height: 100%; + min-height: 0; + + // Par défaut, aucun enfant direct ne grandit/rétrécit (taille fixée à + // son contenu) - seul __results (règle plus bas, même spécificité + // mais déclarée après donc prioritaire) doit remplir l'espace restant + // et défiler en interne. + > * { + flex: 0 0 auto; + } h2 { margin: 0; @@ -83,26 +93,28 @@ padding: 0; display: flex; flex-direction: column; - gap: 0.5rem; - max-height: 65vh; + gap: 0.6rem; + flex: 1 1 0; + min-height: 0; overflow-y: auto; } &__result { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.85rem; border: 1px solid var(--color-border); border-radius: var(--radius); - padding: 0.5rem; + padding: 0.6rem; } &__result-thumb { - width: 96px; - height: 54px; + width: 144px; + height: 81px; object-fit: cover; - border-radius: 4px; + border-radius: 6px; flex-shrink: 0; + background: var(--color-bg); } &__result-info { @@ -112,10 +124,11 @@ &__result-title { margin: 0; - font-size: 0.9rem; - white-space: nowrap; + font-size: 0.92rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; overflow: hidden; - text-overflow: ellipsis; } &__result-meta { diff --git a/client/src/views/queuePanel/QueuePanel.scss b/client/src/views/queuePanel/QueuePanel.scss index b8a0425..5df04b8 100644 --- a/client/src/views/queuePanel/QueuePanel.scss +++ b/client/src/views/queuePanel/QueuePanel.scss @@ -2,6 +2,15 @@ display: flex; flex-direction: column; gap: 0.75rem; + height: 100%; + min-height: 0; + + // Par défaut, aucun enfant direct ne grandit/rétrécit - seul __list + // (règle plus bas, même spécificité mais déclarée après donc + // prioritaire) doit remplir l'espace restant et défiler en interne. + > * { + flex: 0 0 auto; + } h2 { margin: 0; @@ -33,6 +42,9 @@ display: flex; flex-direction: column; gap: 0.5rem; + flex: 1 1 0; + min-height: 0; + overflow-y: auto; } &__item { diff --git a/spec/architecture.md b/spec/architecture.md index af2f421..da018f0 100644 --- a/spec/architecture.md +++ b/spec/architecture.md @@ -38,6 +38,7 @@ Pas de `docker-compose.yml` pour l'instant (une seule image, pas encore de dépe - `src/views/downloadForm/` : formulaire d'ajout, deux entrées possibles (URL directe, ou recherche de chaîne via `GET /api/channel` puis ajout par résultat) partageant un même sélecteur de mode (vidéo/audio/miniature + options). Le mode courant est lu depuis le DOM à l'ajout, pas stocké en état séparé. - `src/views/queuePanel/` : sonde `GET /api/queue` toutes les 1,5s (pas de push temps réel pour l'instant, voir Statut) et affiche chaque élément (titre ou URL à défaut, statut, barre de progression, erreur). Un même bouton retire l'élément quel que soit son état - libellé "Annuler" s'il est en attente/en cours, "Supprimer" une fois terminé (`DELETE /api/queue/:id` gère les deux cas côté serveur). Lien "Télécharger" natif (``) uniquement si un fichier est encore disponible ; bouton "Tout télécharger" dans l'en-tête qui déclenche, avec un léger décalage entre chacun (évite le blocage navigateur des téléchargements multiples simultanés), tous les téléchargements prêts. Expose `refresh()` pour un rafraîchissement immédiat après un ajout. - Piège CSS relevé en testant dans un vrai navigateur (pas seulement les tests) : un attribut `hidden` combiné à une règle `display: flex` sur le même sélecteur se fait écraser par cette règle (même spécificité, la feuille de l'auteur gagne sur celle du user-agent) - toujours ajouter `&[hidden] { display: none; }` quand un conteneur togglé porte aussi un `display` explicite. +- Layout en hauteur pleine page (`html`/`body`/`.shell` à `height:100%`/`100vh`, `overflow:hidden` sur `body`) : la page elle-même ne défile jamais, seules `download-form__results` et `queue-panel__list` le font (chacune `flex:1 1 0; min-height:0; overflow-y:auto` dans un parent qui propage `height:100%`/`min-height:0` depuis `.shell__panel`). `min-height: 0` est nécessaire à *chaque* niveau flex descendant - un enfant flex ne rétrécit jamais sous la taille de son contenu par défaut, ce qui ferait déborder la page entière au lieu de faire défiler juste la liste. Chaque vue neutralise le grow/shrink de ses enfants directs non-scrollables via `> * { flex: 0 0 auto; }`, puis la règle plus spécifique de l'élément scrollable (même spécificité, déclarée après donc prioritaire) le fait remplir l'espace restant. - Tests : Vitest (`client/test/`), uniquement sur les fonctions pures (`format.ts`) pour l'instant - pas de configuration jsdom, le câblage DOM (formulaires, rendu de liste) est vérifié manuellement dans le navigateur plutôt que testé automatiquement. - `src/styles/base.scss` : palette et styles de base partagés (custom properties CSS sur `:root`, pas de `$variables` Sass - une variable Sass ne traverserait pas les fichiers, chaque vue étant compilée/importée séparément ; une custom property CSS si, puisqu'elle se résout à l'exécution dans le navigateur une fois tout le CSS regroupé par esbuild). Importé une fois depuis `app.ts`. -- 2.45.2