La Grosse Caisse · Surface membre · 21 août 2026
Passage en revue intégral de la PWA membre par six audits parallèles en lecture de code : auth & onboarding, accueil & navigation, check-in & QR, roue & tombola & lots, rencontre & collection & chansons, agenda & réservations, plus un balayage transversal du catalogue i18n. Aucun fichier modifié, aucun rendu mesuré — les constats sont lus dans la source, cités fichier par fichier.
Les six audits ont travaillé sans se voir. Quand le même défaut remonte de deux ou trois tranches indépendantes, ce n’est plus un bug local : c’est un motif de fabrication. Sept se dégagent.
Le membre agit, et l’app ne dit rien. C’est le défaut le plus répété, présent dans les six tranches : le renvoi du code de connexion échoue sans un mot (y compris en 429) ; le check-in réussi n’a aucune phrase de confirmation, ni côté membre ni côté staff ; le like de Rencontre n’a aucun accusé de réception ; l’unmatch échoue en silence ; la participation au tirage refusée (409) ne rend rien ; la proposition de chanson ne confirme qu’en région invisible ; la consommation d’un lot se signale par… une carte qui disparaît. Plusieurs boutons sont morts sans explication (soumission login avec e-mail vide, code incomplet, « Installer » sans capture du prompt).
La barre d’onglets a été retirée et remplacée par un lien « Check-in » déclaré TEMPORARY — jamais remplacé (layout.tsx:98-104). Conséquences en chaîne : aucun aria-current, aucun <nav>, /app/messages sans retour en PWA installée, dix libellés « Retour aux mini-apps » qui mènent à un écran qui n’existe plus (redirection vers l’accueil). Trois écrans sont carrément inatteignables ou morts : « Mes matchs » (aucun lien depuis l’annuaire alors qu’une pastille le compte), le scanner de lots (rewards/scan, zéro point d’entrée), et la tuile SONG SUGGESTIONS (slug song-suggestions sans route — le shadowing annoncé par songs/page.tsx n’a jamais lieu).
Toutes les routes paginent et renvoient hasMore ; aucun écran membre ne le lit : messages (30), agenda (30), matchs (30), fil de conversation (50), collection (60), réservations (30), annuaire (30). grep "hasMore\|page=" sur (member)/app → zéro. Un membre avec 31 matchs en perd un, et la conversation du 31ᵉ devient « introuvable ». Le calendrier de l’agenda, lui, interroge une route non paginée : il affiche des pastilles sur des jours que la liste (page 1 seulement) déclare vides.
Aucun écran ne réagit à visibilitychange, au retour de focus ni au temps qui passe : le QR membre expire (TTL 5 min) sans signal ni geste de rafraîchissement pendant que expiresAt est reçu et jeté ; l’écran check-in ne voit pas le pointage fait par le staff ; le fil de chat ne reçoit jamais un message entrant (même pas un bouton « Actualiser », que l’annuaire possède) ; la tombola arrête son polling pile avant la validation que le gagnant attend devant le bar ; les réservations restent « En attente » après la décision de l’équipe ; nowIso figé au rendu laisse un lot expiré « À utiliser ».
Trois écrans affichent des secondes brutes (« réessayez dans 3601 secondes », et « 0 secondes » quand l’en-tête Retry-After manque — le bug Number(null)===0 est copié-collé dans trois fichiers). Pire, plusieurs messages affirment le contraire de la vérité : « Code envoyé à… » alors que l’envoi SMTP a échoué (la route avale l’erreur et répond 202) ; « Reconnectez-vous » sur une panne réseau avec session valide ; « La roue n’a pas pu être chargée » pour un échec de tour ; « Votre code n’a pas pu être vérifié » alors que le lot a été attribué ; une panne de lecture de présence rendue comme « vous n’êtes pas présent » ; une recherche de chansons définitivement morte présentée comme indisponible « pour le moment ». Et beaucoup de refus précis (conversation fermée, compte suspendu, quota de 5 propositions) sont repliés sur un générique « Réessayez » — invitation à réessayer un geste garanti d’échouer.
Quatre mots pour un des trois bars (adresse / lieu / établissement / endroit — dont deux dans un même bloc de trois lignes) ; « staff » contre « équipe » pour les mêmes personnes ; trois noms pour le geste de présence ; « lot » et « gain » dans la même phrase ; deux marques (« La Grosse App » à l’écran, « La Grosse Caisse » dans l’onglet) ; deux mini-apps quasi homonymes (« ROUE DE LA FORTUNE » ouvre « La roue », à côté de « LA GRANDE ROUE »). Mesuré au catalogue : 0 espace insécable contre 61 espaces ordinaires avant : ; ! ?, 42 apostrophes typographiques contre 381 droites (mélangées dans une même phrase), cinq clés en « (s) » (« 1 lot(s) ») alors que le motif singulier/pluriel existe trois lignes plus loin, et un unique tutoiement — « Un tour t’attend » — posé sur l’accueil, l’écran le plus vu (trouvé indépendamment par trois audits).
Aucun error.tsx, not-found.tsx ni loading.tsx sous (member)/app (l’admin et l’encodeur ont les leurs) : toute exception ou URL périmée rend l’écran Next par défaut, en anglais, sans retour, dans une PWA installée sans chrome navigateur. Aucun onboarding : les consentements naissent tous à false, donc aucune notification ne partira jamais, et le geste requestPermission n’est armé nulle part — l’interrupteur du profil promet ce qui ne peut pas arriver. Les deep links sont perdus hors de trois préfixes (une notification vers un profil ou l’agenda dépose le membre sur l’accueil), et la reprise du QR staff perd son ?p=. Les navigations n’ont aucun indicateur (prefetch=false + pas de loading.tsx) : sur réseau de bar, un tap semble ne pas avoir marché.
Classement par gravité vécue : ce qui trompe le membre ou casse un parcours passe avant ce qui l’agace.
| # | Défaut | Pourquoi c’est prioritaire |
|---|---|---|
| 1 | Flux de connexion : « Code envoyé » sur échec SMTP, renvoi silencieux, décompte de renvoi écrasé après 1 s, boutons morts muets | C’est l’unique porte d’entrée du produit, et elle peut affirmer le faux sans issue de secours. |
| 2 | Fil de chat Rencontre affiché à l’envers (plus récent en haut, message envoyé ajouté en bas), sans horodatage, sans rafraîchissement, plafonné à 50 | Le chat est inutilisable au sens propre : une conversation ne se lit pas, un message entrant n’arrive jamais. |
| 3 | Écrans inatteignables : « Mes matchs », scanner de lots, tuile SONG SUGGESTIONS → impasse [slug] | Des fonctionnalités livrées et testées que personne ne peut atteindre. |
| 4 | Navigation membre : stopgap « Check-in » jamais remplacé, « Retour aux mini-apps » ×10 vers un écran disparu, /app/messages sans retour | Toute la surface en dépend ; en PWA installée il n’y a pas de bouton retour de secours. |
| 5 | Check-in : aucune confirmation de succès, éjection auto vers la roue avant lecture, re-POST à chaque remontage (quota grillé), QR expirable sans geste | Le rituel central du produit, en conditions réelles (file, bar sombre, réseau faible). |
| 6 | error.tsx / not-found.tsx / loading.tsx absents côté membre | Trois fichiers standards ; sans eux, chaque panne rend un écran Next anglais sans issue. |
| 7 | Onboarding absent → consentements à false, requestPermission jamais appelé : le push est mort par construction | Toute la promesse « notifications » (matchs, réservations, lots) repose dessus. |
| 8 | Pagination jamais consommée (7 listes tronquées en silence) + calendrier qui contredit la liste | Perte de données invisible ; casse aussi des parcours (conversation « introuvable »). |
| 9 | Erreurs 429 en secondes brutes (3601 s / 0 s, trois écrans), refus précis repliés sur « Réessayez » | Même bug copié trois fois ; les clés i18n justes existent déjà et ne sont pas branchées. |
| 10 | Blocage Rencontre irréversible côté app (la route DELETE existe, aucun consommateur), erreurs d’action affichées derrière le scrim du dialogue | Geste de sécurité sans porte de sortie ni retour visible. |
| 11 | Fiche agenda sans date, soirée qui disparaît de la liste à l’heure où elle commence, réservations sans année ni passé/à venir | L’axe du temps — la matière première de ces deux mini-apps — est perdu. |
| 12 | a11y des moments : roue muette 4,6 s, régions aria-live montées avec leur contenu (donc muettes) sur claim/consommation/tombola, « aujourd’hui » invisible au lecteur d’écran | Le défaut « live region montée pleine » a déjà été mesuré et corrigé sur la roue ; le correctif n’a pas franchi les frontières d’unité. |
console.error) et la route répond 202 statut: 'envoye' ; l’issue resendThrottled retombe sur le même 202. api/auth/code/route.tssetInterval réécrit le cooldown à 0 au tick suivant — le bouton se réarme en 1 s. code-form.tsx:109-115if (!response.ok) return; + catch vide). code-form.tsx:186-205button.tsx revendique.errors.invalidSession existe et n’a aucun consommateur. middleware.ts:155-181false ; requestPermission jamais appelé.Le push est inopérant par construction, et la contrainte iOS « l’onboarding doit le traiter » n’est traitée nulle part. consents.ts, push-client/subscribe.ts:500-502'unavailable' est jeté, la carte disparaît, et l’invitation est proposée sur des navigateurs où elle est garantie inerte ; trois refus fantômes épuisent le plafond d’affichage. use-install-journey.tsnot-found qui liste les documents sans les lier, et trois documents videsalors que le login demande un consentement. legal/[document]/not-found.tsx:32-36sw/service-worker.ts--fs-body rend 12,7 à 15,6 px, toujours sous les 16 px qui déclenchent le zoom Safari à la mise au point. field.css + tokens.css:107login-form.tsx:206-268TabBar mort dans le dépôt, zéro aria-current, zéro <nav>, zéro skip-link. Le remplaçant « MA SOIRÉE » (U-B1c) n’a jamais été livré. layout.tsx:98-104, 341/app/mini-apps est une redirection vers l’accueil) ; /app/messages n’a aucun retour alors que /app/profile, frère de grille, en a un./app/mini-apps/song-suggestions), la route s’appelle songs/ : le membre tombe sur le cadre [slug] état nominal, qui ne rend rien. Invisible en E2E parce que le test navigue à l’URL directe. catalog/view.ts:100page.tsx:187-203establishment-selector.tsx[] sans requête) avec un état vide qui affirme « Aucune demande. » ; liste plafonnée à 30 sans indice ; chaque message est un bouton dont le clic n’a aucun effet visible et dont le nom accessible contient le message entier.error.tsx, not-found.tsx ni loading.tsx membre— exceptions réelles dans page.tsx:175 et profile/page.tsx:81 ; navigation sans indicateur (prefetch=false).role="status" concurrentes ; bannières hors de tout landmark ; aria-disabled sur des div sans rôle ; badge numérique cassable entre deux chiffres (word-break: break-all) ; liens <a> nus (rechargement complet) dans le cadre mini-app et la bannière.expiresAt reçu et jamais utilisé). member-qr-card.tsxcheckin-token-screen.tsx:135-201rateLimited : secondes brutes (jusqu’à 3600), « 0 s » si l’en-tête manque, et écran sans aucune issue(les deux autres états d’erreur ont un lien retour, pas celui-ci).?p= est perdu— après connexion le bouton paraît actif et l’appui rend « QR non valide ». Et le membre déconnecté qui scanne un tag atterrit sur le login marketing, sans un mot sur son check-in en attente.accessible[0]) sans sélecteur.MINI_APP_SLUGS[5] ; cas « déplacement » indiscernable d’un premier check-in.memberRewardId est déjà dans les deux réponses de l’API et jeté par l’écran. wheel-screen.tsx:53-56Number(null)=0) ; quatre blocs de refus sans aucune action ; toutes les régions live montées avec leur contenu, donc muettes — le défaut exact que l’équipe roue avait mesuré et corrigé chez elle.nowIso figé (lot expiré resté « À utiliser ») ; vidéo gelée après arrêt ; role="status" qui écrase la sémantique de liste ; ordre des sections qui enterre « Bientôt valables » sous l’historique./matches ; seuls le push et le lien éphémère post-match (3 s) y mènent.desc rendu dans une colonne normale (plus récent en haut), et le message qu’on envoie est ajouté en bas, à la place du plus ancien. Plus : aucun horodatage visible, aucun rafraîchissement même manuel, historique plafonné à 50 sans indice, double envoi possible, unmatch en échec silencieux, signalement figé sur « le dernier message » (qui est le plus ancien affiché…), impossible de bloquer ou d’ouvrir la fiche du pair depuis la conversation, conversation fermée → « Réessayez » en boucle.DELETE …/block et GET /api/dating/blocks existent, zéro consommateur client ; le dialogue de blocage a une copie fautive (« ni dans ses profils ») et omet les deux conséquences qui comptent ; les erreurs d’action s’affichent derrière le scrim du dialogue resté ouvert.none câblé en dur), proposition sans retour visible (confirmation en clip-rect), titre refusé qui reste au classement et votable, quota de 5 rendu comme un rate-limit générique, chaque vote détruit l’écran (squelette + scroll perdu), vote non retirable malgré aria-pressed.hasReward traverse tout le tuyau et n’est jamais rendu ; le halo « nouveau badge » est permanent (seen_at jamais écrit) ; les badges « À découvrir » sont inertes, sans critère, et les cachés tous intitulés « Badge caché ».current/superseded : une photo est servie aux pairs dès son dépôt ; le produit prévoit le signalement en aval, rien en amont. Constat produit à arbitrer.role="tab" sans clavier ; aperçu de dernier message non tronqué (2000 caractères possibles dans une ligne) ; heure nue sans date dans la liste des matchs ; aucun indicateur de non-lu par conversation ; photo absente = carré vide sans repli onError ; annonce de match auto-effacée en 3 s ; unmatch confirmé par la phrase neutre « Cette conversation est fermée » ; retour des matchs qui sort de la mini-app. Points positifs vérifiés : focus traps des trois dialogues réels, vouvoiement constant, 320 px tenu.dateLabel au catalogue. Et la mention « déplacé » est une date nue qui duplique le champ Horaire dans un autre format.gte(startsAt, now) au lieu de endsAt) — précisément le moment où le membre est sur place ; le kicker « CE SOIR » ne peut jamais être vu après 20 h.hasMore jamais lu), sélection d’un jour = simple filtre local ; « aujourd’hui » n’est marqué nulle part dans la grille, et le repère de la liste est couleur seule et aria-hidden ; les trois filtres d’adresse s’annoncent tous aria-pressed="true" au repos ; cellules sous le plancher tactile à 320 px (≈39 px de large).: ; ! ? ; 42 apostrophes courbes / 381 droites (les deux dans une même phrase, profile.ts:139) ; cinq clés en « (s) » dont l’étiquette a11y de chaque case du calendrier ; un unique tutoiement, sur le hub ; capitales cuites dans 18 feuilles contre la règle écrite dans le catalogue lui-même.decision-notice.ts) qui divergent déjà de l’écran.nowIso figé (minuit, frontière de mois) ; course d’affichage des pastilles.aria-current et les impasses de retour en découlent.false, push mort, documents légaux non lus, geste de permission jamais posé. C’est une contrainte produit déjà écrite (iOS ≥ 16.4) restée sans propriétaire.auth-ui + api/auth : sincérité de l’envoi du code, décompte de renvoi, erreurs du renvoi et du 429 (formatage minutes partagé), boutons morts, retour de session expirée, deep links (paramètre de retour émis côté UI, cookie de reprise avec query).layout + racines : error.tsx / not-found.tsx / loading.tsx, conditionnement du raccourci Check-in, retour sur /app/messages, libellés de retour, tuile songs (slug ou route), état nominal du cadre [slug].rateLimited avec issue.i18nKey serveur (l’idiome existe dans songs).endsAt, « aujourd’hui » marqué (visuel + a11y), cohérence calendrier/liste, année + partition passé/à venir, confirmation de réservation, lien check-in dans l’erreur.decision-notice.ts, rejection-notice.ts) — puis un gate textuel qui verrouille (espace avant ponctuation double, apostrophe droite, « (s) », tutoiement sous ses deux apostrophes) pour que la dette ne se reforme pas./app/login/code (traversé, jamais mesuré — c’est écrit dans le spec du test), /app/offline, /app/legal, la grille du calendrier, la tombola à 500 participants.goto() : la tuile songs morte et le scanner inatteignable sont invisibles précisément parce que les tests naviguent à l’URL. Au moins un scénario par mini-app doit partir du hub.wheel-screen.tsx, et réapparu dans trois tranches — il mérite un contrôle rejouable.transitionend manquant, fraîcheur du badge de non-lus au retour navigateur.Six agents reviewers indépendants (contextes frais, tranches disjointes), lecture seule sur l’arbre main (5754327), chaque constat cité fichier + ligne ou commande + sortie collée. Les agents ont distingué Prouvé (lu dans la source) de Supposé (raisonnement non exécuté) ; plusieurs ont écarté eux-mêmes leurs faux positifs (labelable elements, palier --land-0, ellipses en commentaire). Aucun rendu n’a été observé : ni 320 px, ni lecteur d’écran, ni banc — conformément à la règle du projet, une mise en page non vue à 320 px n’a pas été vue, et ces constats-là sont marqués comme non mesurés, jamais comme verts. Les recouvrements entre tranches (raccourci Check-in, tutoiement unique, secondes brutes, pagination, apostrophes) ont été comptés une fois dans la synthèse : trouvés par deux ou trois audits aveugles l’un à l’autre, ce sont les signaux les plus sûrs du rapport.
Constats positifs, pour l’équilibre : la discipline 320 px est réelle dans le CSS (grilles minmax, overflow-wrap, cibles tactiles), les focus traps des dialogues sont corrects, le vouvoiement est constant à une chaîne près, les gardes serveur (session revalidée, 404-jamais-403, portée) sont solides. Le produit est bien construit dans chaque écran ; il se casse entre les écrans et après les gestes.