diff --git a/PersonaDLE_Update_Documentation/PersonaDLE 2.3/DEV_CHANGELOG.md b/PersonaDLE_Update_Documentation/PersonaDLE 2.3/DEV_CHANGELOG.md index e7d5d0af..612cf92b 100644 --- a/PersonaDLE_Update_Documentation/PersonaDLE 2.3/DEV_CHANGELOG.md +++ b/PersonaDLE_Update_Documentation/PersonaDLE 2.3/DEV_CHANGELOG.md @@ -45,6 +45,60 @@ Découpage en lots — une branche, une PR vers `develop` par ligne : --- +## 2026-09-30 — Hotfix 2.3.3 : le bouton Rejouer avait disparu de Classique + +Signalé par Hamza : en Classique, gagné ou perdu, normal ou Expert, plus aucun moyen de +relancer une partie. **Régression introduite par la 2.3.2**, par le correctif `hidden` +lui-même. + +### Ce qui s'est passé + +`modeClassique.js` ajoutait `.hidden` à `.input-row` dans ses **trois** fins de partie. Or +`#resetButton` — le bouton Rejouer — vit **dans** cette rangée. + +Tant que `.hidden` n'était défini nulle part globalement, ces lignes ne faisaient rien et +personne ne les avait remarquées. La règle `[hidden], .hidden { display: none !important }` +livrée en 2.3.2 les a rendues vraies. + +Deux aggravations : + +- **rien ne retire jamais la classe** — une fois masquée, la rangée ne revient pas de la + partie : le joueur est bloqué jusqu'au rechargement complet de la page ; +- **aucun des cinq autres modes** ne masque sa rangée de saisie. Classique était le seul, et + seulement par du code mort. + +### Pourquoi la vérification de la 2.3.2 ne l'a pas vu + +Le rayon d'action de la règle avait pourtant été mesuré page par page sur la production +avant de l'écrire — mais sur des pages **fraîchement chargées**. L'état « partie terminée » +n'est jamais atteint par un simple `goto` : la régression était hors de portée de la mesure. + +C'est une lacune de méthode, pas de malchance : une règle CSS globale doit être éprouvée sur +les **états** de l'application, pas seulement sur ses pages au chargement. + +### Le correctif + +Les trois lignes retirées. Sans effet fonctionnel : le code neutralise déjà les contrôles +juste au-dessus (`textbar.disabled`, `pointer-events: none` sur Valider, Give Up grisé). +Masquer la rangée en plus n'apportait rien et coûtait le Rejouer. Classique se comporte de +nouveau comme les cinq autres modes. + +### Vérifications + +- `tests-e2e/classic_replay_button.spec.js` — **nouveau**. Joue une partie jusqu'à + l'abandon, puis vérifie que Rejouer est visible, **réellement cliquable** + (`elementFromPoint`, parce qu'un bouton « visible » peut être recouvert ou à zéro pixel), + et qu'un clic relance bien une partie. Deux largeurs, plus le parcours complet. +- `CACHE_VERSION` v105 → **v106**. Le JS est servi en *network-first*, donc le correctif + arrivait de toute façon ; le bump est là par principe de release. + +### Angle mort connu, pas corrigé ici + +`prod_smoke.sh` (serveur maison) a trouvé un **débordement horizontal de 14 px sur la page +profil** à 390 px, et 13 px à 480 px. Réel, antérieur, sans rapport avec ce hotfix — traité +séparément pour que ce lot ne contienne que la régression. + +--- ## 2026-09-27 — Release 2.3.2 Sortie de ce qui s'était accumulé sur `develop` depuis la 2.3.1. **Aucune migration à diff --git a/PersonaDLE_Update_Documentation/PersonaDLE 2.3/PersonaDLE_Update.html b/PersonaDLE_Update_Documentation/PersonaDLE 2.3/PersonaDLE_Update.html index 730b7b7e..941990c1 100644 --- a/PersonaDLE_Update_Documentation/PersonaDLE 2.3/PersonaDLE_Update.html +++ b/PersonaDLE_Update_Documentation/PersonaDLE 2.3/PersonaDLE_Update.html @@ -2483,6 +2483,10 @@

All the mode's animations together

  • Barre du bas — une pastille de notification rouge et vide restait collée à l'icône Amis, sur toutes les pages et à toutes les tailles d'écran. Elle ne s'affiche plus que quand vous avez vraiment quelque chose à lire.
  • +
  • Rejouer en Classique — le bouton Rejouer avait disparu + à la fin d'une partie, en normal comme en Expert : il n'y avait plus aucun moyen + d'en relancer une sans recharger la page. Il est de retour. Nos excuses — c'est + une correction de la mise à jour précédente qui l'avait emporté avec elle.
  • @@ -2518,6 +2522,10 @@

    All the mode's animations together

  • Bottom bar — a red, empty notification dot sat on the Friends icon on every page and at every screen size. It now appears only when you actually have something to read.
  • +
  • Replay in Classic — the Replay button had vanished at + the end of a round, in both normal and Expert: there was no way to start another + one without reloading the page. It's back. Our apologies — a fix in the previous + update had taken it along with it.
  • diff --git a/README.md b/README.md index 16711969..256eff8c 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ the Persona's real myth with its name masked. Expert keeps its own targets, stat - **Lighter**: opening your profile went from 125.6 MB to 13.5 MB, and the heaviest All-Out Attack round from 80.2 MB to 3.2 MB. - 2.3.1: badge and title unlocks are decided by the server, an Expert friend challenge no longer wins itself, Kotone's animated pictures stay animated, and the Classic page starts as high as the other modes on mobile. - 2.3.2: on phones, Hint and Give Up sit side by side again in Classic — the answer field moved 170 px up — the duplicate "Delete my account" button left the profile page, and the empty notification dot left the Friends icon. On Discord, the daily is announced by 39 voices instead of 8 and the Sunday recap shows the podium's portraits. +- 2.3.3: the Replay button is back at the end of a Classic round — it had vanished in 2.3.2, in both normal and Expert, leaving no way to start over without reloading the page.
    2.2 — Profile showcase & Compendium @@ -168,7 +169,7 @@ Every pull request runs the whole thing; `main` only receives releases from `dev ``` Vitest + jsdom → 1548 tests unitaires (logique de jeu, backend, streak, i18n, validation données) PHPUnit → tests de logique pure + intégration DB (contrat de schéma) -Playwright → 326 E2E sur la stack Docker complète (smoke, API, Social Link) +Playwright → 329 E2E sur la stack Docker complète (smoke, API, Social Link) PHPStan → analyse statique PHP niveau 5 · Psalm → taint analysis ESLint+Prettier→ lint + format · i18n / data / daily-pool consistency checks ``` diff --git a/ROADMAP.md b/ROADMAP.md index 71a7f59f..91efeb6b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -499,7 +499,7 @@ Nouveau jeu — cas A (roster inédit) > Synthèse : backend PHP/MariaDB complet (auth, sessions, social, leaderboard, admin, RGPD), > profil personnalisable (avatars groupés, musique, couleurs, badges, titres, wallpapers), > Social Link rangs 1-10, défis, streak globale + Jack Frost, FAQ, i18n 6 langues, -> **1548 tests JS · 407 PHPUnit · 326 E2E · PHPStan niveau 5 · CI/CD GitHub Actions**. +> **1548 tests JS · 407 PHPUnit · 329 E2E · PHPStan niveau 5 · CI/CD GitHub Actions**. ### Backend & Infrastructure @@ -555,7 +555,7 @@ Nouveau jeu — cas A (roster inédit) | # | Élément | Notes | | --- | --------------------------------------------- | ------------------------------------------------- | -| Q1 | Tests : 1548 Vitest · 407 PHPUnit · 326 E2E | `npm test` · `make test-php` · `npm run test:e2e` | +| Q1 | Tests : 1548 Vitest · 407 PHPUnit · 329 E2E | `npm test` · `make test-php` · `npm run test:e2e` | | Q2 | i18n EN/FR/ES/DE/IT/PT (1314 clés) | `npm run i18n:check` | | Q3 | PHPStan niveau 5 + ESLint + Prettier | Dans la CI | | Q4 | Seuils de couverture en CI | `npm run test:coverage` (~77 %) | diff --git a/TODO.md b/TODO.md index b7cee2f8..ab7023d7 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,7 @@ > > Chaque section numérotée est dimensionnée pour tenir dans **une seule branche**. > -> Vérifié le 2026-08-26 : 1548 tests Vitest (86 suites), 407 méthodes PHPUnit, 326 tests E2E, +> Vérifié le 2026-08-26 : 1548 tests Vitest (86 suites), 407 méthodes PHPUnit, 329 tests E2E, > lint et data/i18n/pools propres. --- diff --git a/classiqueMode/modeClassique.js b/classiqueMode/modeClassique.js index 06485ad7..a90ee4c9 100644 --- a/classiqueMode/modeClassique.js +++ b/classiqueMode/modeClassique.js @@ -555,7 +555,20 @@ function checkGuess(name, target, forceReveal = false) { giveUpButton.style.pointerEvents = "none"; giveUpButton.style.opacity = "0.5"; gameOver = true; - document.querySelector(".input-row")?.classList.add("hidden"); + // ⚠️ Ne PAS masquer `.input-row` en fin de partie : le bouton **Rejouer** + // (`#resetButton`) vit dedans, et rien ne retire jamais la classe — le joueur + // resterait bloqué jusqu'au rechargement de la page. + // + // Les trois fins de partie ajoutaient `.hidden` à `.input-row`. Ça n'a jamais + // rien fait tant que `.hidden` n'était défini nulle part globalement ; la règle + // `[hidden], .hidden { display: none !important }` de la 2.3.2 l'a rendue + // vraie, et le Rejouer a disparu de Classique — en normal comme en Expert. + // Les cinq autres modes ne masquent pas leur rangée : Classique était le seul, + // et seulement par du code mort. + // + // Les contrôles sont de toute façon déjà neutralisés juste au-dessus + // (`textbar.disabled`, pointer-events, Give Up grisé) : les masquer en plus + // n'apportait rien et coûtait le Rejouer. document.getElementById("autocompleteList")?.classList.add("hidden"); // !forceReveal : un Give Up ne doit jamais se logger comme une victoire — @@ -727,7 +740,6 @@ document.addEventListener("DOMContentLoaded", async () => { textbar.disabled = true; guessButton.disabled = true; setGiveUpEnabled(false); - document.querySelector(".input-row")?.classList.add("hidden"); document.getElementById("autocompleteList")?.classList.add("hidden"); revealNextLink({ nextHref: "../emojiMode/emojiMode.html" }); fillVictoryBox(target.nom, false); @@ -801,7 +813,6 @@ document.addEventListener("DOMContentLoaded", async () => { giveUpButton.style.pointerEvents = "none"; giveUpButton.style.opacity = "0.5"; gameOver = true; - document.querySelector(".input-row")?.classList.add("hidden"); document.getElementById("autocompleteList")?.classList.add("hidden"); if (!history.includes(target.nom)) { diff --git a/sw.js b/sw.js index f48059d8..df312baf 100644 --- a/sw.js +++ b/sw.js @@ -21,7 +21,7 @@ // Sans bump, `activate` ne purge rien et les assets servis en cache-first // (images, sons) restent ceux de la version précédente — invisible en test, // puisque c'est justement le cache des joueurs DÉJÀ venus qui est concerné. -const CACHE_VERSION = "personadle-v105"; +const CACHE_VERSION = "personadle-v106"; // Préfixe du sous-dossier : '/personadle' en dev local, '' en production (racine). // Calculé depuis l'URL du SW lui-même (ex: /personadle/sw.js → /personadle). diff --git a/tests-e2e/README.md b/tests-e2e/README.md index 6299aeaa..1883f151 100644 --- a/tests-e2e/README.md +++ b/tests-e2e/README.md @@ -5,7 +5,7 @@ Playwright Docker -> **326 tests (38 fichiers) sur un vrai navigateur, contre la stack Docker complète.** +> **329 tests (39 fichiers) sur un vrai navigateur, contre la stack Docker complète.** > Couvre les parcours qu'aucun test unitaire ne voit (login, leaderboard, profil public, Social Link, admin). diff --git a/tests-e2e/classic_replay_button.spec.js b/tests-e2e/classic_replay_button.spec.js new file mode 100644 index 00000000..44618ee9 --- /dev/null +++ b/tests-e2e/classic_replay_button.spec.js @@ -0,0 +1,100 @@ +import { test, expect } from "@playwright/test"; +import { gotoSettled } from "./helpers/page.js"; + +/** + * Après une partie, on doit pouvoir rejouer. + * + * ── Ce que ce test garde ──────────────────────────────────────────────────── + * Les trois fins de partie de Classique ajoutaient `.hidden` à `.input-row` — + * la rangée qui contient le bouton **Rejouer** (`#resetButton`). Tant que + * `.hidden` n'était défini nulle part globalement, ces lignes ne faisaient rien + * et personne ne les avait remarquées. La règle `[hidden], .hidden { display: + * none !important }` livrée en 2.3.2 les a rendues vraies : le Rejouer a disparu + * de Classique, en normal comme en Expert, et **rien ne retirait jamais la + * classe** — le joueur restait bloqué jusqu'au rechargement de la page. + * + * Le contrôle du rayon d'action de cette règle n'avait mesuré que des pages + * FRAÎCHEMENT CHARGÉES : l'état « partie terminée » n'était jamais atteint, donc + * la régression était invisible. D'où ce test, qui va jusqu'au bout d'une partie. + * + * On passe par Abandonner plutôt que par une victoire : c'est le seul chemin + * déterministe sans connaître la cible du jour, et il emprunte exactement le même + * code de fin de partie. + */ + +/** Joue des réponses fausses jusqu'à ce qu'Abandonner soit disponible. */ +async function proposerFaux(page, combien) { + for (let i = 0; i < combien; i++) { + await page.fill("#textbar", "zzzz" + i); + await page.keyboard.press("Enter"); + await page.waitForTimeout(150); + } +} + +test.describe("Classique — le bouton Rejouer survit à la fin de partie", () => { + for (const largeur of [390, 1280]) { + test(`à ${largeur} px, Rejouer reste visible et cliquable après un abandon`, async ({ + browser, + }) => { + const ctx = await browser.newContext({ viewport: { width: largeur, height: 900 } }); + const page = await ctx.newPage(); + await gotoSettled(page, "/classiqueMode/classiqueMode.html"); + await page.waitForTimeout(600); + + const rejouer = page.locator("#resetButton"); + const rangee = page.locator(".input-row"); + + await expect(rejouer, "Rejouer doit être là avant de jouer").toBeVisible(); + + await proposerFaux(page, 3); + await page.locator("#giveUpButton").click(); + await page.waitForTimeout(800); + + // Le cœur du test : la partie est finie, le bouton doit rester atteignable. + await expect( + rangee, + "la rangée de saisie ne doit pas être masquée : elle contient Rejouer" + ).toBeVisible(); + await expect( + rejouer, + "Rejouer a disparu après la fin de partie — le joueur est bloqué jusqu'au rechargement" + ).toBeVisible(); + + // Visible ne suffit pas : un bouton recouvert ou à zéro pixel ne se clique pas. + const atteignable = await page.evaluate(() => { + const el = document.getElementById("resetButton"); + if (!el) return "absent"; + const r = el.getBoundingClientRect(); + if (r.width < 8 || r.height < 8) + return `trop petit (${Math.round(r.width)}×${Math.round(r.height)})`; + const dessus = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2); + return dessus && (dessus === el || el.contains(dessus)) ? "ok" : "recouvert"; + }); + expect(atteignable, "Rejouer n'est pas cliquable après la fin de partie").toBe("ok"); + + await ctx.close(); + }); + } + + test("cliquer Rejouer relance bien une partie", async ({ browser }) => { + const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } }); + const page = await ctx.newPage(); + await gotoSettled(page, "/classiqueMode/classiqueMode.html"); + await page.waitForTimeout(600); + + await proposerFaux(page, 3); + await page.locator("#giveUpButton").click(); + await page.waitForTimeout(800); + + await page.locator("#resetButton").click(); + await page.waitForTimeout(800); + + // Une nouvelle partie : le champ de saisie répond de nouveau. + await expect( + page.locator("#textbar"), + "après Rejouer, le champ doit être utilisable" + ).toBeEnabled(); + + await ctx.close(); + }); +}); diff --git a/tests/README.md b/tests/README.md index 3e03a4ec..d75f3965 100644 --- a/tests/README.md +++ b/tests/README.md @@ -17,7 +17,7 @@ ``` ╱╲ - ╱ ╲ Playwright E2E — 326 tests (stack Docker réelle) + ╱ ╲ Playwright E2E — 329 tests (stack Docker réelle) ╱────╲ smoke (5) + API badges/streak (2) + Social Link ami→XP→rang (6) ╱ ╲ ╱────────╲ PHPUnit — 407 méthodes (24 fichiers, dont intégration vraie MariaDB)