Parties enregistrées, branches et export SGF (étape 1b) #37

Manually merged
pierre merged 8 commits from parties-enregistrees into main 2026-11-03 19:55:20 +00:00
3 changed files with 54 additions and 3 deletions
Showing only changes of commit d228d70387 - Show all commits
+4 -1
View File
@@ -110,7 +110,10 @@ export function createGamesView(options: GamesViewOptions): GamesView {
actions.append(
button(
GAMES_TEXTS.resume,
run(() => options.resume(game.id)),
run(async () => {
await options.resume(game.id);
hide();
}),
"",
"resume",
),
+41 -1
View File
@@ -54,4 +54,44 @@ rechargement). Commits fréquents ; pas de push ni de MR.
## Résultat
À remplir à la clôture.
Réalisé le 2026-11-03 sur la branche `parties-enregistrees`, fusionnée avec
main après la révision UX (MR !35).
- **Stockage** (`app/src/storage/`) : `GameLibrary` (list, load, save,
remove, current, setCurrent) sur IndexedDB (base « ustagammon » v1 :
magasins games, journals, settings ; résumé et journal dans la même
transaction), en repli sur localStorage puis sur la mémoire. Opérations
exécutées dans l'ordre des appels. La partie de l'étape 0
(`ustagammon.current-game`) est migrée puis effacée. `GameSession` tient
la partie à l'écran : nouvelle partie, « Reprendre l'ancienne » (relue
par son identifiant, la nouvelle est supprimée), reprise d'une partie ou
d'une branche.
- **Domaine** : `branches(journal)` et `switchToBranch` (branches.ts),
`Game.branches` et `Game.switchBranch`, résumé d'une partie
(saved-game.ts), export SGF de GNU Backgammon avec les branches en
variantes, la branche courante d'abord (sgf.ts).
- **Interface** : « Mes parties », ligne du menu en feuille, ouvre une
feuille (games-view.ts) : reprendre une partie ou une branche (changement
de partie à l'écran, sans recharger), exporter en SGF, supprimer en deux
touchers (jamais la partie affichée).
- **Vérification SGF** : `tests/test_web.sh` joue 6 parties sur le vrai
moteur (complètes, branches, videau, positions particulières), les
exporte (9 fichiers, un par branche) et les fait relire par le gnubg de
référence (`tests/sgf/check.py`) : après chaque coup, sa position égale
celle du journal, et le résultat concorde. Une altération volontaire d'un
coup ou de la position de départ est détectée.
- **Scénarios** : `docs/scenarios/mes-parties.feature` (reprendre une partie
parmi plusieurs, rejouer d'ici sans perdre l'original, exporter) ; le
scénario « Reprendre l'ancienne » de `regler-l-application.feature` passe
par la session. Glossaire : partie-enregistree.md, sgf.md, branche.md
(code: Branch).
- **Tests** : `make lint`, `make test` (10/10, 20 scénarios) et
`make test-browser` (Chromium et Firefox : parties listées, SGF
téléchargé, première partie reprise sans rechargement, « Reprendre
l'ancienne » sans partie en trop) passent.
Limites connues : gnubg 1.08 ne lit que la première variante d'un SGF ;
l'enregistrement n'est pas attendu (une fermeture dans les millisecondes
qui suivent une action peut la perdre) ; pas de score de match avant 1c ;
le schéma IndexedDB devient un contrat durable (amendement de l'ADR 0007 à
envisager). L'adresse `#/parties` viendra avec 0e.4.
+9 -1
View File
@@ -1188,7 +1188,15 @@ step("mes parties");
await shot("4b-mes-parties");
const loads = await page.evaluate(() => performance.getEntriesByType("navigation").length);
await oldest.locator('[data-action="resume"]').click();
await page.waitForSelector("#games-veil", { state: "hidden" });
await page.waitForSelector("#games-veil", { state: "hidden", timeout: 20_000 }).catch(async () => {
const state = await page.evaluate(() => ({
message: document.querySelector("#games .games-message")?.textContent,
busy: document.body.classList.contains("busy"),
info: document.getElementById("info")?.textContent,
panel: !document.getElementById("panel-veil").hidden,
}));
throw new Error(`reprise bloquée : ${JSON.stringify(state)}`);
});
await page.waitForSelector("#panel");
const resumed = await savedJournal();
check(JSON.stringify(resumed) === JSON.stringify(finished), "la première partie n'a pas été reprise");