Récap unique pour la fin de partie et l'historique — v1.60.0 — 2026-08-12
L'écran de fin et le détail d'historique montraient la même chose en deux exemplaires : rien ne les séparait sur le fond, seulement leur source de données — une session vivante d'un côté, un GameRecord de l'autre.
GameRecord et rien d'autre : ligne de contexte, bannière, podium, onglets Classement / Feuille de score / Manches, classement complet. L'écran de fin lui passe le record qu'il s'apprête à écrire (buildRecord(), mémoïsé — il génère un id et un endedAt à chaque appel), donc ce qu'on fête est exactement ce que l'historique gardera. Ce qui entoure le récap (confettis, lien retour, boutons) reste à la page.
- Partager depuis l'historique, ce qui n'était pas possible : le payload de la carte image
vient désormais du record (podiumCardFromRecord()), partagé par les deux écrans.
- Rejouer depuis l'historique : relance le jeu avec les mêmes personnes, dans la couleur du
jeu (le détail est maintenant enveloppé dans GameTheme). Masqué pour une « Partie rapide » (sa définition vivait dans la session, il ne reste rien à relancer) ; une équipe sans effectif nommé — tableaux de score sportifs — renvoie au setup plutôt que de deviner. À noter que l'ordre de jeu repart du classement final, le record ne mémorisant pas l'ordre du setup. Les 19 jeux result_only y gagnent le plus : ils n'ont aucun écran de fin (ils atterrissent directement sur la liste d'historique), donc ces deux actions leur manquaient entièrement.
- La ligne « jeu · date · durée » passe au-dessus du podium : en bas de page, elle se
retrouvait sous un tableau de quinze lignes — on apprenait en dernier ce qu'on était en train de lire.
- Deux détails alignés au passage : le classement de fin de partie liste tout le monde avec les
médailles de l'historique (il ne montrait que la 4e place et au-delà), et la ligne set par set d'un sport hiérarchique n'apparaît plus qu'une fois, sous le podium.
Historique — relire une partie manche par manche — v1.59.0 — 2026-08-12
Deuxième lot du chantier ouvert en v1.58.0, pour les 22 jeux joués en manches (RoundAccumulation, BiddingRounds, TrickTaking — Skyjo, Uno, 6 qui prend !, Time's Up !, Skull King, Belote, Coinche…) : l'onglet à côté du classement devient un tableau manches × personnes, chaque case portant ce que la manche a rapporté et le total après elle.
- Le journal d'actions ne suffisait pas : une donne de Belote est une seule action pour
toute la table, et le score de manche de Skull King est calculé par le moteur (value: 0 dans l'action). La session tient donc un registre de scores (GameSession.roundScores) : les scores au départ, puis à chaque fin de manche. Il est alimenté par le store via appendRoundSnapshot() — aucun moteur n'est touché, une frontière de manche se voyant de l'extérieur (round qui bouge, ou partie terminée). undo le restaure avec le reste de la session, et il survit à une reprise de partie (contrairement à la pile d'annulation, vidée à la reprise).
fin de la partie, colonnes triées comme le podium ; les deltas se déduisent par différence entre deux relevés. Même contrat que scorecard : champ optionnel, pas de migration.
« Manche » figée et scroll horizontal au-delà de 3-4 colonnes — lire une manche en travers est tout l'intérêt, une feuille par personne l'aurait perdu. Les onglets du lot 1 accueillent l'un ou l'autre panneau (un jeu est une feuille de score ou des manches, jamais les deux).
- Écart assumé : une partie déjà en cours au moment de la mise à jour n'a pas de registre et
reste sans onglet — un tableau qui commencerait à la manche 7 se lirait plus mal que pas de tableau. RaceToTarget reste dehors : une « manche » y est un tour de table, soit 20 à 30 lignes d'un ou deux points pour un Mölkky.
Historique — relire la feuille de score finale — v1.58.0 — 2026-08-12
Une partie terminée ne se relisait que par son podium. Pour un jeu à feuille de score (Yam's, Azul, Wingspan, Catan… 14 jeux ScoreCard), c'est pourtant la feuille qu'on rouvre : qui a eu la prime de section, qui a barré une ligne, combien a rapporté chaque catégorie.
- Le détail était perdu à la fin de la partie : les entrées par catégorie vivent dans
GameSession.actions, et la session est supprimée juste après l'écriture du GameRecord (persistence/sync.ts), qui ne gardait que des totaux. GameRecord porte désormais un champ optionnel scorecard — un instantané auto-suffisant de la feuille finale, écrit par buildScorecardRecap() depuis buildRecord(). Pas de migration IndexedDB (champ optionnel sur une valeur schemaless) : les parties déjà en historique n'ont pas l'instantané et s'affichent exactement comme avant.
- Onglets
Classement / Feuille de score sur les deux surfaces — l'écran de fin de partie
(GameSummary) et le détail d'historique (RecordDetail) — via le nouveau SegmentedTabs (pattern WAI-ARIA tabs, navigation flèches/Home/End). Rejouer / Partager / Accueil restent hors onglets.
plutôt qu'une grille catégories × personnes : à 6 au Yam's, la grille imposerait un scroll horizontal sur téléphone. Sections, ligne de prime (sous-total/seuil → +valeur, atteinte ou non), case jamais remplie en —, total en pied. Les libellés suivent la langue de l'app (lus dans le catalogue courant), avec le libellé instantané en secours si la catégorie a disparu.
- Les totaux affichés restent ceux du moteur (
session.scores) ; l'instantané ne recalcule
rien. Le récap manche par manche des autres modèles (RoundAccumulation, RaceToTarget, TrickTaking, BiddingRounds) reste à faire — voir ROADMAP.
Setup — couleur facultative par personne (et par équipe) — v1.57.0 — 2026-08-11
La couleur d'une personne était déterministe et invisible au setup : impossible de la faire correspondre au pion réellement pris à la table. Chaque ligne de setup porte désormais une pastille cliquable qui déplie une palette ; ne rien toucher garde exactement le comportement d'avant.
pastille + palette repliée, fermeture au clic extérieur et à Échap, aria-expanded sur la pastille, aria-pressed sur chaque teinte, couleurs déjà prises par une autre ligne atténuées. Le wrapper PlayerColorPicker y branche la palette curatée et les libellés.
- Palette curatée de 12 teintes (
PICKER_PALETTE dans
helpers/colors.ts), orientée pions et distincte des 16 teintes du hash, qui restent l'attribution automatique : aucune couleur existante ne change, ni dans l'app ni dans l'historique. Les 8 couleurs de TEAM_PALETTE y figurent, mais la liste est déclarée à part — étendre TEAM_PALETTE aurait créé de nouveaux noms d'équipe et déplacé la borne maxTeams du setup sport.
- Portée session, assumée : le choix part dans
SessionSetup.players[].color, le moteur le
transporte tel quel et sessionStore.start applique p.color ?? colorForName(p.name). Il est snapshoté dans le GameRecord comme n'importe quelle couleur, mais rien n'est persisté — une couleur stable par personne relève des « personnes récurrentes enrichies » (ROADMAP), d'où le découplage explicite de ces deux briques.
- Consolidation du setup sport :
SportSetup adopte le même composant et **perd sa rangée de
8 pastilles dépliée en permanence** (le point qui motivait ce chantier). Sa pastille, jusqu'ici un <span aria-hidden> décoratif, devient un bouton focusable et annoncé (setup.teamColor), et les couleurs des autres équipes y sont atténuées.
- Équipes de sport nommées « Équipe 1 » / « Équipe 2 » au lieu du nom de leur couleur de
palette : ce défaut mentait dès qu'on changeait la couleur (équipe verte encore nommée « Rouge »), et il affichait des libellés français dans les six langues puisque les noms de TEAM_PALETTE sont des étiquettes de pastilles, pas des noms traduisibles. Nouvelle clé setup.defaultTeamName. Les jeux en équipes type Pétanque ne sont pas concernés : le nom d'équipe *y est* la couleur par construction (teamColor(name)).
- Hors mode équipes : l'unité de score y est l'équipe et sa pilule porte déjà la couleur, donc
pas de pastille personnelle ; basculer « Partie rapide » en équipes efface les surcharges plutôt que de garder un état invisible et non annulable.
- La palette s'ouvre dans le flux de la ligne (ligne pleine sous la personne) et non en
panneau flottant : animate-pop-in laisse un transform (fill-mode: both) sur chaque carte de setup, qui piège tout z-index et faisait passer le panneau sous la carte suivante.
- i18n :
colors.* (12 teintes), setup.playerColor, setup.colorAuto, setup.teamColor dans
les six langues (« Automatisch » en allemand, où « Auto » veut dire *voiture*), puis i18n:bless.
changer repeindrait tout le monde) et les invariants de la palette ; sessionStore.test.ts vérifie qu'une couleur choisie survit jusqu'au GameRecord et que les autres gardent leur défaut.
Catalogue — Mise en place dans les règles (1/n) : Uno, Skyjo, 6 qui prend ! — v1.56.0 — 2026-08-11
Première grappe du chantier « infos de mise en place » (ROADMAP) : les fiches disaient comment on joue et comment on compte, jamais combien de cartes on distribue. Une ligne « Mise en place » ouvre désormais rules.<locale> des trois jeux où l'oubli renvoyait à la boîte. Passe catalogue pure, aucun code.
- Uno : 7 cartes par personne.
- Skyjo : 12 cartes par personne, faces cachées en 3 rangées de 4, dont 2 à retourner avant de
commencer — la disposition fait partie de la mise en place, pas seulement le nombre.
- 6 qui prend ! : 10 cartes par personne, puis 4 cartes face visible au centre pour amorcer les
quatre rangées. Les règles mentionnaient déjà les rangées sans dire d'où elles venaient.
- Chaque valeur a été vérifiée sur une source de règles avant d'être écrite, comme pour Skull
King : un chiffre faux dans les règles est pire que pas de chiffre.
- Les six langues sont écrites d'un bloc (pas de fallback FR), puis
i18n:bless. - La vitrine reprendra ces lignes à sa prochaine génération (
deploy:site), les pages jeu étant
alimentées par rules.<locale>.
Fix — a11y : le bouton boîte à outils annonçait une clé i18n — v1.55.1 — 2026-08-11
Sur l'écran de score, le bouton boîte à outils portait t("tools.title") comme label accessible (ScorecardToolsButton.tsx). Cette clé n'existe dans aucune locale : i18next retombait sur la clé brute, et les lecteurs d'écran annonçaient littéralement « tools.title » dans les six langues.
- Corrigé en pointant sur
app.toolbox (« Boîte à outils »), **déjà présente et traduite dans les
six locales** : c'est le titre que porte la ToolsSheet elle-même, et le pendant inactif de scorecast.toolboxActive (« Boîte à outils, Scorecast {{code}} actif ») utilisé par ce même bouton. Aucune clé nouvelle, donc aucune dérive i18n à bénir.
- Le même menu ouvert depuis l'accueil n'était pas touché : il porte son libellé correctement.
Catalogue — Skull King : bonus conservés sur un pari raté (option) — v1.55.0 — 2026-08-11
Option de setup sur Skull King : décocher « Bonus au pari exact seulement » garde les bonus marqués même quand le pari est raté (skull-king.json). Pur JSON — BiddingRounds gérait déjà bonusOnExactOnly, seule la déclaration setup.params manquait.
- Règle officielle vérifiée avant d'ouvrir l'option, comme la ROADMAP l'exigeait, et le
verdict corrige la fiche : il n'existe pas de variante officielle qui conserve les bonus sur un pari raté. Chez Grandpa Beck's, les bonus sont strictement conditionnés au pari exact. C'est donc une règle maison, livrée comme telle : bonusOnExactOnly: true reste le défaut et le help du toggle dit « Règle officielle. Désactivez pour… (règle maison) », sur le modèle du toggle d'élimination de Mölkky.
- L'assouplissement qui existe vraiment côté éditeur est The Rascal, un système de score
alternatif complet (base de 10 pts × cartes distribuées, moitié des points *et* des bonus sur un écart de 1, zéro au-delà) : hors périmètre de cette option, noté à la ROADMAP.
scoringHelp amendé dans les 6 langues : il affirmait « Les bonus ne comptent QUE si le pari
est atteint pile », ce qui devenait faux à l'écran une fois l'option décochée.
- Tests :
src/engine/scoring-models/BiddingRounds.test.ts fixe les deux comportements, y compris
sur un pari à 0 raté, et le défaut officiel quand l'option est absente.
Scoring — Valeur de fin annoncée dans le tableau des scores — v1.54.0 — 2026-08-11
Retour terrain : la barre de progression du Scoreboard ne disait pas vers quel nombre elle montait. Une ligne discrète au-dessus des lignes de score porte désormais la valeur, dans l'unité du jeu (Scoreboard.tsx, standings.ts).
- Trois formulations, parce que la même valeur ne veut pas dire la même chose selon le jeu :
« Objectif : 13 pts » (premier à l'atteindre gagne), « Objectif : exactement 50 pts » (winCondition: "exact", Mölkky) et « Fin de partie à 100 pts » quand l'atteindre arrête la partie alors que le plus petit total gagne (Skyjo, Rami, 6 qui prend !, Dominos, Odin). L'écrire « Objectif » là-dessus aurait dit l'inverse de la règle.
- Le nouveau helper
scoreGoal() lit les options brutes du moteur. Piège traité au passage :
winCondition ne couvre pas le même espace de valeurs selon le modèle (reach/exact pour RaceToTarget et TrickTaking, lowest/highest pour RoundAccumulation) — d'où deux branches distinctes selon que l'option lue est targetScore ou eliminationThreshold.
- Rien pour les jeux à rebours (Fléchettes) : le score affiché *est* la distance restante et
l'arrivée vaut toujours 0. Leur targetScore() est le score de départ (501), qui sert d'échelle à la barre — l'afficher comme un objectif aurait été faux.
- Rien non plus sans cible ni seuil (Score libre, distance « Libre » du billard, Golf/Mini-golf
qui se jouent au nombre de manches).
- La valeur reprend le formateur des lignes du tableau, donc l'unité déclarée par le jeu suit
automatiquement (scoreDisplay.unit).
- Périmètre volontairement limité à cette valeur : les autres
setup.params ne sont pas rappelés
sur l'écran de score. Contrat en SPEC §5.5 « Couche 5 — Format d'affichage du score ».
- Tests :
src/helpers/standings.test.ts couvre les trois formes, les cas muets, et re-vérifie
cinq jeux réels du catalogue pour qu'une édition JSON ne retourne pas une légende en silence.
Catalogue — Mini-golf — v1.53.0 — 2026-08-11
Ajout du Mini-golf en catégorie « Plein air », demandé depuis le terrain. Pur JSON : aucun moteur ni widget nouveau (mini-golf.json).
RoundAccumulation en winCondition: "lowest" + round_entry_table : une piste = une manche,
le plus petit total de coups gagne.
- Coups bruts, pas d'écart au par — c'est la différence de fond avec le
golf déjà au
catalogue : un parcours de mini-golf affiche rarement un par, on compte les coups joués.
- Saisie réduite à un seul
ScoreStepper borné à 1–10 coups, sans raccourcis : une piste se saisit
d'un ou deux taps, et le champ reste éditable au clavier. Le plafond est volontairement large — en famille, une piste ratée dépasse largement le cap habituel des parcours.
- Règles et aide au comptage volontairement minimales, sans référence au golf, et aucun alias.
- Option de setup « Nombre de pistes » : 9 ou 18 (défaut 18).
terminology.scoreUnit / scoreUnitOne déclarés, pour que le podium et l'historique accordent
correctement « 1 coup » / « 41 coups ».
- Illustration dédiée (
mini-golf.webp, app + vitrine + variante 320) — l'illustration de Golf
reste la sienne.
- Scénarios moteur : fin de partie sur 18 pistes et partie encore en cours.
Catalogue — Bohnanza, nom corrigé dans les 6 langues — v1.52.2 — 2026-08-11
La fiche décrivait bien Bohnanza (champs de haricots, beanomètre, 3ᵉ épuisement de la pioche) mais s'appelait « Bonanza » en fr/de/it, alors que en/es/pt portaient déjà le bon nom : le fichier se contredisait. Le de était faux à la source, le titre venant de _Bohne_ (haricot).
names uniformisé à Bohnanza dans les six langues
(bonanza.json).
- Aucun alias vers l'ancien nom : « Bonanza » sans h désigne un autre jeu, et le conserver
en nom de recherche entretiendrait la confusion. L'ancienne orthographe est abandonnée.
id et slug inchangés (bonanza) : ils portent l'URL vitrine, le deep link
app.scoria.gg/game/bonanza, les illustrations et le gameId des parties déjà en historique.
- Vitrine régénérée depuis le catalogue : fiches, hubs, homes,
llms.txt et sitemap affichent
Bohnanza dans les six langues, sans édition manuelle.