Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions PersonaDLE_Update_Documentation/PersonaDLE 2.3/DEV_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 à
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2483,6 +2483,10 @@ <h3>All the mode's animations together</h3>
<li><strong>Barre du bas</strong> — une pastille de notification rouge et <em>vide</em>
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.</li>
<li><strong>Rejouer en Classique</strong> — le bouton <em>Rejouer</em> 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.</li>
</ul>
</div>
<div data-i18n-block="en">
Expand Down Expand Up @@ -2518,6 +2522,10 @@ <h3>All the mode's animations together</h3>
<li><strong>Bottom bar</strong> — a red, <em>empty</em> 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.</li>
<li><strong>Replay in Classic</strong> — the <em>Replay</em> 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.</li>
</ul>
</div>

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<details>
<summary>2.2 — Profile showcase &amp; Compendium</summary>
Expand Down Expand Up @@ -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
```
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 %) |
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---
Expand Down
17 changes: 14 additions & 3 deletions classiqueMode/modeClassique.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)) {
Expand Down
2 changes: 1 addition & 1 deletion sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion tests-e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<img src="https://img.shields.io/badge/Playwright-Chromium-2EAD33?style=for-the-badge&logo=playwright&logoColor=white" alt="Playwright">
<img src="https://img.shields.io/badge/cible-stack%20Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white" alt="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).

</div>
Expand Down
100 changes: 100 additions & 0 deletions tests-e2e/classic_replay_button.spec.js
Original file line number Diff line number Diff line change
@@ -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();
});
});
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading