From 16a7abbedc9a6a04dbeb0c19488b219723a433e6 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sun, 11 Oct 2026 01:53:47 -0400 Subject: [PATCH] [FIX] event header: a lone warning shows its action without scrolling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A four-line warning with its action needed 132 px in the header's side zone, capped at 7.25rem, so « Ouvrir les tables » hid below the fold. A zone holding a single warning now grows to 10rem, five lines plus the action; several warnings keep the lower cap, so the plan loses no more. Checked: 1, 4 and 13 missing places at 1366 and 1024 px, the action inside the visible zone; node 4012, browser 872. --- FR --- [FIX] en-tête : un avis seul montre son action sans défilement Un avis de quatre lignes et son action demandaient 132 px dans la zone latérale de l'en-tête, bornée à 7,25rem : « Ouvrir les tables » restait caché. Une zone qui ne porte qu'un avis monte à 10rem, cinq lignes et l'action ; plusieurs avis gardent la borne basse, le plan ne perd rien de plus. Vérifié : 1, 4 et 13 places manquantes à 1366 et 1024 px, l'action dans la zone visible ; node 4012, navigateur 872. Assisted-by: Claude Opus 5.5 --- src/interface/evenement/CadreMode.svelte | 11 +++ .../evenement/Enveloppe.navigateur.test.js | 83 ++++++++++++------- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/interface/evenement/CadreMode.svelte b/src/interface/evenement/CadreMode.svelte index b5be451..3ebbcb3 100644 --- a/src/interface/evenement/CadreMode.svelte +++ b/src/interface/evenement/CadreMode.svelte @@ -404,6 +404,17 @@ padding-block: 0.125rem; } + /* Une zone qui ne porte qu'un message — un seul enfant, qui n'en a qu'un + lui-même — n'a rien à faire défiler pour en montrer d'autres : son + plafond, 10rem, tient entier un avertissement de cinq lignes de texte + et son action dessous. Le plafond de plusieurs messages reste celui + ci-dessus, et la surface garde sa hauteur devant des avertissements + nombreux. Ces enfants viennent d'un autre composant : :global les + soustrait à la portée des styles de celui-ci. */ + .place > .annonces:has(> :global(:only-child > :only-child)) { + max-height: 10rem; + } + /* Seules — sans tête, en plein écran, ou devant un support rompu —, les annonces ne prennent jamais plus du quart du cadre, et cèdent d'abord leur hauteur à la surface : leur facteur de rétrécissement, mille fois diff --git a/src/interface/evenement/Enveloppe.navigateur.test.js b/src/interface/evenement/Enveloppe.navigateur.test.js index ec1b4de..46a9a94 100644 --- a/src/interface/evenement/Enveloppe.navigateur.test.js +++ b/src/interface/evenement/Enveloppe.navigateur.test.js @@ -133,6 +133,17 @@ async function enManque(m) { flushSync(); } +// L'événement passé en écriture, une table de trois places créée, puis +// trois personnes de plus que de places : le manque donné, à chaque tour. +async function enManqueDe(m, manque) { + await m.seance.passerEnEcriture(); + await m.seance.executer('creerTable', { forme: 'ronde' }); + for (let n = 0; n < EVENEMENT.siegesParDefaut + manque; n += 1) { + await m.seance.executer('ajouterParticipant', { champs: { nom: `${NOMS[n % NOMS.length]} ${n + 1}` } }); + } + flushSync(); +} + // --- Les épreuves ----------------------------------------------------------------- describe('la bande de mode (§ 8.4)', () => { @@ -436,37 +447,51 @@ describe('les avertissements, en messages d’avis', () => { // Le texte de l'avertissement et son action ne sont jamais rognés : ni // le texte, ni le message, ni aucune boîte qui les porte jusqu'au cadre // ne déborde de sa hauteur, et chacun tient dans la boîte visible de - // chaque ascendant qui coupe ce qui le dépasse. + // chaque ascendant qui coupe ce qui le dépasse, la zone des annonces la + // première. Le manque d'une place, de quatre et de treize : le nombre et + // l'accord allongent la phrase. for (const largeur of [1366, 1024]) { - test(`à ${largeur} × 700, le texte de PLACES_MANQUANTES et « Ouvrir les tables » se lisent en entier : aucune boîte ne les rogne jusqu’au cadre`, async () => { - await fixerCadre(largeur, 700); - const m = await neuf(); - await enManque(m); - const message = await attendre(() => { - const element = dans(m, '[data-annonces] [data-avertissement="PLACES_MANQUANTES"]'); - expect(element).not.toBeNull(); - return element; - }); - const texte = message.querySelector('p'); - const action = message.querySelector('[data-commande]'); - expect(texte.textContent).toBe(t('PLACES_MANQUANTES', { placesManquantes: 1 })); - expect(action).not.toBeNull(); - const borne = cadre(m); - for (let element = texte; element !== borne.parentElement; element = element.parentElement) { - const nom = `${element.tagName.toLowerCase()}${element.hasAttribute('data-annonces') ? '[data-annonces]' : ''}`; - expect(element.scrollHeight, `${nom} : hauteur du contenu`).toBeLessThanOrEqual(element.clientHeight + 1); - if (element === texte || style(element).overflowY === 'visible') continue; - const b = boite(element); - const bas = b.top + element.clientTop + element.clientHeight; - for (const [lu, porte] of [['texte', texte], ['action', action]]) { - expect(boite(porte).top, `${lu} dans ${nom}`).toBeGreaterThanOrEqual(b.top + element.clientTop - 0.5); - expect(boite(porte).bottom, `${lu} dans ${nom}`).toBeLessThanOrEqual(bas + 0.5); + for (const manque of [1, 4, 13]) { + test(`à ${largeur} × 700, il manque ${manque} : le texte de PLACES_MANQUANTES et le bouton « Ouvrir les tables » se lisent en entier, sans défiler`, async () => { + await fixerCadre(largeur, 700); + const m = await neuf(); + await enManqueDe(m, manque); + const message = await attendre(() => { + const element = dans(m, '[data-annonces] [data-avertissement="PLACES_MANQUANTES"]'); + expect(element).not.toBeNull(); + return element; + }); + const texte = message.querySelector('p'); + const action = message.querySelector('button[data-commande]'); + expect(texte.textContent).toBe(t('PLACES_MANQUANTES', { placesManquantes: manque })); + expect(action).not.toBeNull(); + expect(action.textContent.trim()).toBe(t('communs.remede.ouvrirSection.tables')); + // La zone des annonces ne défile pas, et le bouton tient dans sa + // boîte visible. + const zone = message.closest('[data-annonces]'); + expect(zone.scrollHeight, 'zone : hauteur du contenu').toBeLessThanOrEqual(zone.clientHeight + 1); + expect(zone.scrollTop).toBe(0); + const z = boite(zone); + const a = boite(action); + expect(a.top, 'bouton sous le haut de la zone').toBeGreaterThanOrEqual(z.top + zone.clientTop - 0.5); + expect(a.bottom, 'bouton au-dessus du bas visible de la zone').toBeLessThanOrEqual(z.top + zone.clientTop + zone.clientHeight + 0.5); + const borne = cadre(m); + for (let element = texte; element !== borne.parentElement; element = element.parentElement) { + const nom = `${element.tagName.toLowerCase()}${element.hasAttribute('data-annonces') ? '[data-annonces]' : ''}`; + expect(element.scrollHeight, `${nom} : hauteur du contenu`).toBeLessThanOrEqual(element.clientHeight + 1); + if (element === texte || style(element).overflowY === 'visible') continue; + const b = boite(element); + const bas = b.top + element.clientTop + element.clientHeight; + for (const [lu, porte] of [['texte', texte], ['bouton', action]]) { + expect(boite(porte).top, `${lu} dans ${nom}`).toBeGreaterThanOrEqual(b.top + element.clientTop - 0.5); + expect(boite(porte).bottom, `${lu} dans ${nom}`).toBeLessThanOrEqual(bas + 0.5); + } } - } - expect(visible(action)).toBe(true); - expect(document.documentElement.scrollHeight).toBeLessThanOrEqual(innerHeight); - expect(document.documentElement.scrollWidth).toBeLessThanOrEqual(innerWidth); - }); + expect(visible(action)).toBe(true); + expect(document.documentElement.scrollHeight).toBeLessThanOrEqual(innerHeight); + expect(document.documentElement.scrollWidth).toBeLessThanOrEqual(innerWidth); + }); + } } });