BoardGame Referee — Mode d'emploi

Mémo perso : comment utiliser l'app, l'admin, et dépanner les pannes utilisateur.

Admin

Admin

Consulter les feedbacks

Consulter les feedbacks

Dernière mise à jour : 2026-05-10

Pourquoi

Voir où l'oracle se plante (et où il assure) pour orienter les améliorations RAG.

/admin/feedback

Détail d'un feedback

Clic sur une ligne → panneau AdminFeedbackDetail se déploie en dessous :

Bouton "Copier (md)"

Génère un dump markdown complet (buildAnalysisMarkdown) avec :

Tu peux le coller dans un éditeur ou dans une session Claude pour analyser un cas spécifique.

Export CSV

Bouton "Export CSV" en haut → POST /api/admin/feedback/export → fichier CSV avec toutes les questions filtrées (questionId, vote, gameId, question, answer, bestScore, intent, timestamps).

Utile pour bench RAG, dashboards externes, ou simplement archive.

Alimenter le banc d'éval

Les feedbacks votés down avec commentaire deviennent des cas-test naturels du banc tests/rag/. Le workflow propre serait :

  1. Sélectionner les feedbacks down avec commentaire détaillé
  2. Reformuler en cas-test (question, expected behavior)
  3. Les ajouter dans tests/rag/dataset.json (à voir si le format évolue)
  4. Lancer npm run test:rag pour mesurer la régression

(Pas encore automatisé — pour l'instant c'est manuel.)

Admin

Resync les cartes d'un TCG

Resync les cartes d'un TCG

Dernière mise à jour : 2026-05-11

Pourquoi

Quand un TCG sort un nouveau set (ou que tu veux refresh la base après un fix de bug source ou une amélioration de traduction), il faut re-synchroniser la collection Qdrant avec la nouvelle source.

Depuis le 2026-05-11, tout se fait depuis /admin — plus besoin de SSH dans le container pour la majorité des TCG.

/admin → section Bases de cartes.

Tu y trouves une ligne par TCG, avec :

Les deux modes de resync

🔁 Mode "Live" — Riftbound uniquement

Bouton "Resync. (live)" : la source Riftbound est une API Riot interrogeable à chaud. Un clic suffit pour comparer la collection Qdrant avec ce que l'API renvoie en direct, et appliquer le diff (ajouts / mises à jour / suppressions).

La progression s'affiche inline sous le nom du deck : "Connexion à la source… → Diff calculé → Embedding 32/45 → Upsert Qdrant → Terminé : +3 ajouts, 12 modifs, 0 supprimé / 1 280 total".

C'est rapide (< 2 minutes) car aucune donnée n'est téléchargée localement.

⚙️ Mode "Pipeline" — MTG, Lorcana, FAB, Terraforming Mars, Ark Nova

Bouton "Lancer le pipeline" : ces TCG nécessitent un téléchargement préalable (bulk Scryfall pour MTG, LorcanaJSON pour Lorcana, package npm pour FAB, données locales pour TM/Ark Nova). Un clic enchaîne automatiquement toutes les étapes :

  1. Téléchargement de la source (sauf FAB qui est bundlé)
  2. Extraction / parsing (filtrage FR, dédoublonnage, etc.)
  3. Embedding + ingestion Qdrant

Le détail des étapes est affiché dans le modal de progression qui s'ouvre au clic. Tu y vois :

Tu peux fermer la fenêtre

Le pipeline continue côté serveur même si tu fermes le modal ou navigues ailleurs. Si tu reviens sur /admin plus tard (avant la fin du job ou dans les 10 minutes qui suivent), le modal se rouvre automatiquement avec l'historique complet des logs et l'étape en cours.

C'est utile car certains pipelines sont longs :

TCG Durée typique
FAB ~5-10 min
Lorcana ~5-10 min
Terraforming Mars ~5 min
Ark Nova ~5 min
MTG ~25-40 min (download bulk Scryfall + 30k embeddings)
Riftbound (mode live) ~1-2 min

Une seule resync à la fois

Pour ne pas saturer le GPU d'embedding, un seul job tourne à la fois (toutes collections confondues). Si MTG est en cours et que tu cliques sur FAB, le bouton FAB est désactivé et un message t'indique qu'un autre pipeline tourne déjà. Attends la fin du premier (ou ferme le modal et fais autre chose pendant que ça mouline).

Vérifier que ça a marché

À la fin du pipeline :

  1. Le badge de fraîcheur passe au vert ("À l'instant" / "Il y a 1 min")
  2. Le compteur de cartes est mis à jour (ex : 1 280 → 1 312)
  3. La durée s'affiche ("dernière exécution : 24 min")

Pour valider concrètement :

En cas d'erreur

Si une étape échoue, le modal affiche :

Le badge de fraîcheur passe au rouge avec un tooltip qui montre les 60 premiers caractères de l'erreur.

Que faire ?

  1. Lis l'erreur dans la console : la plupart du temps c'est un service externe en panne (Scryfall down, LorcanaJSON inaccessible) ou un quota dépassé (Haiku rate-limit pour MTG).
  2. Attends quelques minutes, relance le pipeline.
  3. Si ça persiste, ouvre les logs container côté Unraid pour voir si une dépendance backend est cassée (Qdrant, TEI embeddings).

Cas particulier FAB (toujours utile à savoir)

Le package @flesh-and-blood/cards est embarqué dans l'image Docker. Le bouton "Lancer le pipeline" ré-ingère ce que ce package contient, mais pas plus : si un nouveau set est sorti et que le package n'a pas été bumpé côté repo, le resync ne ramènera rien de neuf.

Workflow pour vraiment ajouter de nouvelles cartes FAB :

  1. Update le package localement : npm update @flesh-and-blood/cards @flesh-and-blood/types
  2. Commit + push → CI Gitea build l'image
  3. Pull + restart le container sur Unraid
  4. Ensuite clic sur "Lancer le pipeline" depuis /admin
Admin

Valider un utilisateur

Valider un utilisateur

Dernière mise à jour : 2026-05-10

Pourquoi

Tous les nouveaux comptes (self-register ou créés par admin) sont en statut pending par défaut. Tant qu'ils sont pending, ils peuvent se connecter mais sont redirigés vers /pending sans accès aux features.

Comment

  1. Va sur /admin (compte admin requis)
  2. Section Utilisateurs
  3. Filtre éventuellement par username
  4. Clique Confirmer sur le compte concerné
  5. La ligne users.role passe de pending à user

À partir de là, l'utilisateur peut accéder à /, /play, /history, etc.

Permissions complémentaires

Notification mail

Au moment du register, l'app envoie automatiquement un mail de notification à toi (l'admin) si SMTP configuré (SMTP_HOST non vide). Le mail contient le username + lien direct vers /admin.

Pour rappel, la conf SMTP est optionnelle — si tu n'as pas configuré SMTP_HOST dans .env, aucun mail n'est envoyé, tu dois checker /admin manuellement.

Supprimer un utilisateur

/admin → Users → bouton Supprimer (avec ConfirmDialog).

Effets :

Ajouter un jeu

Ajouter un jeu

Ajouter un jeu

Ajouter un jeu

Dernière mise à jour : 2026-05-10

Réservé aux users avec canAddGames = true (admins par défaut, autres comptes : à toi de toggler dans /admin → Users).

Wizard /add-game (3 étapes)

Tu cherches le jeu sur BoardGameGeek (API XML v2). Tu sélectionnes le résultat → ses metadata (image, méchaniques, catégories, type) sont rapatriées et stockées dans la ligne games.

Si le jeu n'existe pas sur BGG (rare), tu peux skip cette étape et entrer le nom à la main.

Étape 2 — Upload PDF + métadonnées

Étape 3 — Ingest ritual

Si tu démarres tout de suite, tu vois un suivi SSE en temps réel des actes :

  1. Lecture : pdfjs-dist extrait le texte
  2. Reconnaissance optique (OCR — uniquement si PDF scanné, auto-détecté)
  3. Découpage : chunking sémantique (paragraphes + overlap)
  4. Structuration : Claude analyse l'arbre chapitre/section/sous-section
  5. Analyse contextuelle : Claude génère 1-2 phrases de contexte par chunk (Contextual Retrieval B, 10 SSH parallèles, cache JSON)
  6. Compréhension : embeddings TEI bge-m3 (batch de 32)
  7. Archivage : push Qdrant
  8. Conflits (extensions seulement) : compare aux chunks du parent, détecte replaces/modifies/extends
  9. PNG : pdftoppm rend chaque page en image 300 DPI dans /app/pdfs/images/<slug>/page-XX.png

Durée typique : 5-15 min selon taille du PDF + utilisation Claude (quota).

Pause quota Claude

Si pendant l'ingestion le CLI Claude renvoie son message « Usage limit reached, reset at HH:MM », l'app :

  1. Flush les caches JSON (contextual-llm + conflict-detect)
  2. Passe la ligne en ingestStatus='scheduled' avec ingestScheduledAt = resetAt + 2 min
  3. Émet un event SSE quota_pause → l'UI affiche un panneau dédié
  4. Re-déclenche runIngestion automatiquement à l'heure de reset
  5. La reprise re-utilise les caches JSON → aucun chunk n'est retraité, zéro perte

Tu n'as rien à faire — laisse tourner.

Si l'ingestion plante

Ajouter un jeu

Ingestion planifiée

Ingestion planifiée

Dernière mise à jour : 2026-05-10

Pourquoi

Comment

Dans le wizard /add-game étape 2, coche "Démarrer plus tard" et choisis une date/heure.

Contraintes : doit être dans le futur, max +7 jours.

Que se passe-t-il en interne

  1. Le PDF est uploadé sur disque immédiatement (/app/pdfs/...)
  2. La ligne games est créée avec ingestStatus='scheduled' + ingestScheduledAt
  3. Un setTimeout est armé dans src/cron/ingest-scheduler.ts pour déclencher startIngestJob(gameId) à l'heure prévue
  4. Le PDF n'est PAS analysé tant que l'heure n'est pas atteinte

Si tu redémarres le container avant l'heure

Au boot, restoreScheduledOnBoot() relit la BDD et re-programme tous les timers en attente. Si une date est déjà passée pendant le redémarrage, le job démarre immédiatement avec une grace de 1 seconde.

Annuler une ingestion planifiée

DELETE /api/games/:id/scheduled (via /admin → Games → bouton Annuler) :

  1. Clear le timer
  2. Supprime le PDF du disque
  3. Supprime la ligne SQLite

Renvoie 409 si le jeu est déjà passé en running ou done.

Distinction reprise après quota

Le même mécanisme (scheduleIngestStart) est réutilisé pour la pause auto en cas de quota Claude. Côté UI, le motif est différencié (reason: 'quota' vs 'user') pour afficher la bonne copie. Pour l'utilisateur c'est transparent : laisse tourner, ça reprendra tout seul.

Bienvenue

Bienvenue

À quoi sert cette app

À quoi sert cette app

Dernière mise à jour : 2026-05-10

Boardgame Referee est ton arbitre personnel pour les règles de jeux de société et de TCG. Tu lui poses une question en français, il répond en citant le passage exact du livret (avec page cliquable).

Cas d'usage typiques

Ce qui le rend différent

Bienvenue

Ce dont tu as besoin

Ce dont tu as besoin

Dernière mise à jour : 2026-05-10

Côté utilisateur

Côté admin (toi)

Côté infra (déjà en place sur ton Unraid)

Dépannage

Dépannage

L'oracle est en pause quota

L'oracle est en pause quota

Dernière mise à jour : 2026-05-10

Symptômes

Pourquoi

Le compte Claude utilisé par la VM oracle a atteint son quota d'usage (Pro / Team plan, fenêtre 5h glissante). Le CLI claude renvoie alors un message du genre :

Usage limit reached, your limit will reset at 21:00 PM

Le service claude-quota.ts détecte ce message (multiples patterns supportés : reset at, try again at, available again at, formats 12h/24h, timestamp Unix) et lève un ClaudeQuotaError au lieu de renvoyer la réponse vide.

Ce que fait l'app automatiquement

Pendant une ingestion

  1. Les workers SSH parallèles (contextual-llm, conflict-detect) arrêtent de spawn de nouvelles tâches dès qu'une ClaudeQuotaError est détectée
  2. Les caches JSON sont flushés sur disque AVANT de propager l'erreur (critique pour la reprise sans perte)
  3. Le coordinator.ts rattrape l'erreur, bascule la ligne SQLite en ingestStatus='scheduled' avec ingestScheduledAt = resetAt + 2 min
  4. Émet un event SSE quota_pause avec { resetAt, retryAt } → le wizard UI affiche la copie dédiée
  5. À l'heure de reset, le scheduler relance runIngestion qui re-rentre dans les stages, lit les caches JSON, et ne re-traite que les chunks manquants

Pendant une question /play

L'oracle peut hallu silencieusement quand le quota est dépassé (le CLI renvoie parfois la quota notice comme une réponse assistant normale). Le détecteur scanne aussi le contenu streamé pour ce cas. Si détecté, l'erreur est exposée à l'UI qui affiche un message clair.

Ce que tu peux faire

Si la détection ne marche pas

Si Claude change le wording du message de quota et que claude-quota.ts ne le reconnaît plus, l'erreur sera générique ("answer empty") au lieu de pause planifiée.

Étendre QUOTA_MARKERS dans src/services/claude-quota.ts :

const QUOTA_MARKERS = [
  'usage limit reached',
  'you've hit',
  'rate limit reached',
  // ... ajouter le nouveau marker
];

Et tester avec un mock de stream qui contient le nouveau message.

Dépannage

Réinitialiser un mot de passe

Réinitialiser un mot de passe

Dernière mise à jour : 2026-05-10

Self-service (utilisateur)

Pas implémenté pour l'instant — pas d'envoi de mail magic-link en self-service. Si tu oublies ton mot de passe, contacte un admin.

Via admin (toi)

Deux options :

Option 1 — Bouton "Send password reset"

/admin → Users → ouvrir un user → bouton "Renvoyer mail de reset".

Appelle POST /api/admin/send-password-reset/:userId qui :

  1. Génère un token de reset
  2. Envoie un mail à l'adresse en BDD avec le lien /reset-password?token=...

⚠️ Nécessite SMTP configuré (SMTP_HOST non vide). Sans SMTP, ça plantera silencieusement (à vérifier dans les logs).

Option 2 — Reset manuel via DB

Si SMTP indisponible :

docker exec -it boardgame-referee sh
sqlite3 /app/data/database.db
> UPDATE users SET password_hash = '<nouveau-hash-argon2>' WHERE username = 'foo';

Pour générer un hash argon2 :

docker exec -it boardgame-referee node -e "
import('argon2').then(a => a.hash('nouveau-mdp')).then(console.log)
"

Page /reset-password

Accepte un ?token=... depuis l'URL. Vérifie le token, demande un nouveau mot de passe, le hashe (argon2) et update la BDD. Le token est consommé (one-shot).

Si tu te bloques toi-même (admin)

Hash argon2 directement la nouvelle valeur en CLI comme ci-dessus, ou redémarre le container avec FIRST_ADMIN_USERNAME + FIRST_ADMIN_PASSWORD mis à jour — au boot, l'app crée/réinitialise l'admin défini par ces vars.

Poser une question

Poser une question

Attacher un deck (FAB)

Attacher un deck (FAB)

Dernière mise à jour : 2026-05-10

Disponible uniquement sur Flesh and Blood pour l'instant (hasCardDatabase = 'flesh-and-blood-cards').

Workflow

  1. Sur Fabrary, ouvre ton deck → bouton "Copy as Text"
  2. Sur /play (jeu FAB sélectionné), clique sur le bouton Deck dans la pill du composer
  3. Modale "DeckImportModal" s'ouvre → colle le decklist dans le textarea
  4. Clique "Analyser"
  5. Preview affichée : héro, weapon, equipment, mainboard avec vignettes de cartes ; les non-matchés (s'il y en a) en bas
  6. Clique "Attacher au chat" → un bandeau compact apparaît au-dessus du composer (héro + N cartes + bouton détacher)
  7. Toutes tes questions suivantes ont le deck en sticky mentions (cap 80)

Ce qui est extrait du texte Fabrary

Cas d'usage

Limites

Poser une question

Citer une carte avec @

Citer une carte avec @

Dernière mise à jour : 2026-05-10

Sur les jeux qui ont une base de cartes (hasCardDatabase non-null en BDD), tu peux citer une carte directement dans ta question avec @.

TCG supportés

Workflow

  1. Tape @ dans le composer → un popover apparaît
  2. Tape les premières lettres de la carte → liste filtrée (debounce 150ms)
  3. Navigue avec ↑↓, valide avec Entrée (ou clique)
  4. La carte est insérée dans le texte et "épinglée" à la question
  5. Quand tu envoies, l'oracle reçoit les détails complets de la carte (nom, type, mana, abilities, set, rareté…) en plus du retrieval normal

Différence vs retrieval vectoriel

Sticky mentions (multi-tours)

Les cartes citées au tour 1 restent disponibles aux tours suivants (cap FIFO 20). Plus besoin de les re-taper. Le bloc CARTES ÉVOQUÉES DANS LA CONVERSATION les rappelle à l'oracle au format abrégé. Si tu veux qu'une sticky déclenche une nouvelle expansion synergy, re-tape la avec @.

Citation par l'oracle

Quand l'oracle veut référencer une carte dans sa réponse, il écrit [[card:Nom exact]] — rendu côté UI comme un bouton avec mini-image cliquable, ouvre la modale zoom détaillée (CardZoomModal).

Cap "deck attaché"

Si tu attaches un deck (cf. page suivante), le cap sticky passe de 20 à 80 — assez pour un classic constructed FAB (60 main + 12 sideboard).

Poser une question

Poser une question (flux classique)

Poser une question (flux classique)

Dernière mise à jour : 2026-05-10

Étapes

  1. Va sur /play depuis le Home en cliquant sur un jeu.
  2. Coche les extensions actives dans le header (les non cochées sont ignorées par le retrieval).
  3. Tape ta question dans le composer en bas.
  4. Envoie (Entrée, ou bouton). L'oracle commence à streamer la réponse.
  5. Lis et clique sur les citations pour vérifier la source dans le PDF.
  6. Vote pouce ↑↓ + commentaire si la réponse t'a aidé ou pas — tout est enregistré pour /admin/feedback.

Ce que tu vois pendant la génération

Que faire si la réponse semble fausse

  1. Vote pouce bas + commentaire détaillé.
  2. Va sur /admin/feedback, filtre par ton jeu + vote down, ouvre le détail.
  3. Clique "Copier (md)" pour avoir un dump markdown des diagnostics (chunks retrouvés, scores, HyDE, timings).
  4. Si c'est un problème de retrieval (mauvais chunks), vérifie que le PDF est bien indexé (count chunks > 0) et que la question n'est pas trop floue.
  5. Si c'est un problème de réponse Claude malgré bons chunks, l'Oracle a hallu — pas grand-chose à faire à part voter pour qu'il apprenne (le feedback alimente ton banc d'éval RAG).

Limites

Premiers pas

Premiers pas

Accéder à l'app

Accéder à l'app

Dernière mise à jour : 2026-05-10

URLs

Login

  1. Va sur /login.
  2. Username + password.
  3. Cookie de session valide 7 jours, HTTP-only, SameSite=Lax.

Rate-limit : 5 tentatives ratées par IP avant 15 min de cooldown. Si tu te bloques, attends 15 min ou redémarre le container (la map en mémoire se reset).

Premier admin (au boot)

Au tout premier démarrage du container, l'app crée automatiquement un admin avec les credentials des env vars FIRST_ADMIN_USERNAME + FIRST_ADMIN_PASSWORD. Tu peux ensuite changer le mot de passe via /me.

Pas de token API

Pas d'auth header bearer, tout passe par cookie de session. Si tu veux scripter des appels (ex. depuis un cron), tu dois te login via POST /api/auth/login et conserver le cookie.

Si tu vois pending après login

Tu es en attente de validation admin. Va sur /admin (avec un compte admin) → Utilisateurs → bouton Confirmer. Sinon Telegram / IRL me ping.

Premiers pas

Tour de l'interface

Tour de l'interface

Dernière mise à jour : 2026-05-10

Navigation

Pages principales

Route But
/ (Home) Ludothèque visuelle : grille des jeux + bandeau "Reprendre la partie" si une session de moins de 6h existe
/play Cœur du produit : chat avec l'oracle, citations cliquables, autocomplete @card, deck attaché
/history Timeline de tes feedbacks groupée par jeu
/add-game Wizard 3 étapes : recherche BGG → upload PDF → ingest ritual (suivi en SSE)
/me Profil + change password
/me/settings Préférences
/admin Dashboard admin : services health, jeux, users, resync cards
/admin/feedback Table des feedbacks avec diagnostics RAG complets + bouton "Copier markdown"

La table de jeu (/play)

Citations cliquables

Quand l'oracle cite un livret ([HEAT, p.4 : "..."]), tu peux cliquer pour ouvrir le PDF directement à la page citée dans un viewer modal. Si plusieurs livrets sont cités (base + extension), le bon est résolu automatiquement via le nom du livret dans la citation.

Cartes citées ([[card:Nom]])

Quand l'oracle cite une carte de TCG, c'est rendu comme un bouton avec mini-image. Clic → modale zoom (CardZoomModal) avec stats détaillées.

Mode "table" (useTableMode)

Toggle dans le header pour adapter l'affichage en mode tablette posée à plat (texte plus gros, contrastes renforcés).