Onboarding guidé systématique + tableau de bord simplifié
Build and deploy / test-python (push) Failing after 1m48s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Comportement SYSTÉMATIQUE à chaque création de jeu (admin compris), pas
une formalité réservée à l'inscription : /onboarding (routes/onboarding/)
devient le point d'entrée unique — 4 cartes retournables (survol =
explication au dos), défilement horizontal animé vers le nom du jeu. Un
admin y repasse à volonté (pas de project_slug dédié, jamais bloqué/
redirigé vers un projet précédent) ; un compte "user" n'en a plus qu'un
créé d'office (routes/auth/register_2fa.py), guidé ici à la place.

- db/games/game_type_catalog.py : catalogue des 4 types (Quiz/
  Embranchement-escape game/RPG/Créer mon jeu de A à Z), _meta['onboarding_type']
  décide du "kind" du premier écran créé et si le tableau de bord complet
  reste accessible.
- routes/games/game_dashboard.py, templates/game_dashboard_simple.html :
  un type restreint (quiz/embranchement/rpg) voit désormais SON tableau
  de bord (même route que "custom"), rendu en version simplifiée — juste
  ses écrans en cartes avec un aperçu RÉEL du contenu (scène mise à
  l'échelle par container query CSS, adaptée à la largeur réelle de la
  carte). "+ Ajouter un écran" n'y propose pas de choix de type : imposé
  par le projet (routes/screens/screens_new.py), verrouillé aussi côté
  serveur.
- core/auth_guard.py : plus de blocage de game_dashboard par type — la
  restriction se fait au rendu, pas à l'accès à la route.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 06:22:45 +02:00
co-authored by Claude Sonnet 5
parent 0f23024882
commit 76a50fa87a
23 changed files with 876 additions and 52 deletions
+13 -1
View File
@@ -20,7 +20,7 @@ exécute un vrai CREATE TABLE, et remplir le formulaire généré exécute un
vrai INSERT dans cette table.
"""
from .constants import PROJECTS_DIR, FIELD_TYPES, GLOBAL_VARIABLE_TYPES
from .constants import PROJECTS_DIR, FIELD_TYPES, GLOBAL_VARIABLE_TYPES, SCORE_STATUS_CHOICES, SCORE_STATUS_LABELS
from .slugify import slugify
from .table_name_for import table_name_for
from .game_dir import game_dir
@@ -36,6 +36,11 @@ from .games.create_game import create_game
from .games.update_game_name import update_game_name
from .games.delete_game import delete_game
from .games.move_game import move_game
from .games.game_type_catalog import (
ONBOARDING_TYPES, DEFAULT_ONBOARDING_TYPE,
get_onboarding_type, get_onboarding_type_raw, set_onboarding_type,
is_restricted,
)
from .definitions.list_definitions import list_definitions
from .definitions.get_definition import get_definition
@@ -66,6 +71,10 @@ from .global_vars.update_global_variable import update_global_variable
from .global_vars.delete_global_variable import delete_global_variable
from .global_vars.delete_global_variable_by_id import delete_global_variable_by_id
from .scoring.get_score import get_score
from .scoring.set_score_value import set_score_value
from .scoring.set_status import set_status
from .custom_events.list_custom_events import list_custom_events
from .custom_events.get_custom_event import get_custom_event
from .custom_events.create_custom_event import create_custom_event
@@ -73,9 +82,12 @@ from .custom_events.update_custom_event import update_custom_event
__all__ = [
"PROJECTS_DIR", "FIELD_TYPES", "GLOBAL_VARIABLE_TYPES", "PLAYER_SHARED",
"SCORE_STATUS_CHOICES", "SCORE_STATUS_LABELS", "get_score", "set_score_value", "set_status",
"slugify", "table_name_for", "game_dir", "db_path", "connect",
"list_games", "game_meta", "create_game", "update_game_name", "delete_game", "move_game",
"is_public_played", "set_public_played", "get_game_type", "DEFAULT_GAME_TYPE",
"ONBOARDING_TYPES", "DEFAULT_ONBOARDING_TYPE", "get_onboarding_type",
"get_onboarding_type_raw", "set_onboarding_type", "is_restricted",
"list_definitions", "get_definition", "create_definition", "rename_definition",
"definitions_referencing", "add_field_to_definition", "update_field", "delete_field",
"delete_definition",
+8 -5
View File
@@ -1,6 +1,6 @@
from ..connection import connect
from .is_public_played import is_public_played
from .get_game_type import get_game_type
from .game_type_catalog import get_onboarding_type
def game_meta(slug):
@@ -14,8 +14,11 @@ def game_meta(slug):
# ligne" en a besoin pour afficher son état actuel), sans avoir à
# appeler is_public_played(slug) séparément dans chaque route.
"is_public_played": is_public_played(slug),
# Type de jeu (fondations multi-éditeurs) : "document" (éditeur
# générique) ou "jeu_2d" (éditeur de scène) — voir
# get_game_type.py et routes/screens/screen_edit.py.
"game_type": get_game_type(slug),
# Onboarding guidé (voir game_type_catalog.py) : utilisé par
# templates/base.html pour cacher le lien "Tableau de bord" à un
# compte "restreint" (quiz/embranchement/rpg).
"onboarding_type": get_onboarding_type(slug),
# Pas de "game_type" ici : depuis la fusion des moteurs (voir
# screens/screens_repo/ensure_schema.py), "document"/"jeu_2d" est
# une propriété PAR ÉCRAN (screen["kind"]), plus du projet.
}
+98
View File
@@ -0,0 +1,98 @@
"""Onboarding guidé (voir routes/onboarding/onboarding_new.py) : les 4
choix proposés à CHAQUE création de projet — PAS un 3e type de moteur
(voir screens/screens_repo/ensure_schema.py, fusion des moteurs :
"document"/"jeu_2d" est une propriété PAR ÉCRAN), une classification
produit posée par-dessus. Chaque choix décide du "kind" du tout premier
écran créé (screen_kind ci-dessous, aussi imposé à tout écran ajouté
ensuite — voir routes/screens/screens_new.py) et si le tableau de bord
complet (game_dashboard, objets/éléments de jeu/variables) reste
accessible ensuite, ou seulement sa version simplifiée (juste les écrans
en cartes — voir routes/games/game_dashboard.py, restricted).
_meta['onboarding_type'] (une ligne _meta par projet, même convention que
is_public_played/l'ancien game_type) retient CE choix — jamais lu par le
rendu jouable, seulement par le routage (routes/games/game_dashboard.py,
routes/screens/screens_new.py)."""
import db
DEFAULT_ONBOARDING_TYPE = "custom"
ONBOARDING_TYPES = {
"quiz": {
"label": "Quiz",
"tagline": "Questions, réponses, score.",
"icon": "🎯",
"description": "Gérer des questions/réponses, avec score.",
"screen_kind": "document",
"restricted": True,
},
"embranchement": {
"label": "Jeu à embranchement / escape game",
"tagline": "Chaque choix a une conséquence.",
"icon": "🗝️",
"description": (
"Former à travers le jeu sur des concepts métier avec des "
"conséquences observables ; réaliser des escape games sous "
"forme d'énigmes."
),
"screen_kind": "document",
"restricted": True,
},
"rpg": {
"label": "Jeu RPG",
"tagline": "Ton monde, ton héros, ton scénario.",
"icon": "🗺️",
"description": "Réaliser des serious games scénarisés sous forme de RPG 2D sur navigateur.",
"screen_kind": "jeu_2d",
"restricted": True,
},
"custom": {
"label": "Créer mon jeu de A à Z",
"tagline": "Toute la puissance du moteur, sans limite.",
"icon": "⚡",
"description": (
"Pour les utilisateurs avec des notions de dev/jeu vidéo : "
"accès à toute la puissance du moteur."
),
"screen_kind": "document",
"restricted": False,
},
}
def get_onboarding_type_raw(slug):
"""None si _meta['onboarding_type'] est absent — distingue un projet
JAMAIS passé par l'onboarding guidé (créé avant son existence, ou par
l'admin via "+ Nouveau jeu") d'un projet explicitement "custom"."""
conn = db.connect(slug)
row = conn.execute("SELECT value FROM _meta WHERE key = 'onboarding_type'").fetchone()
conn.close()
return row["value"] if row else None
def get_onboarding_type(slug):
return get_onboarding_type_raw(slug) or DEFAULT_ONBOARDING_TYPE
def set_onboarding_type(slug, onboarding_type):
conn = db.connect(slug)
conn.execute(
"INSERT OR REPLACE INTO _meta (key, value) VALUES ('onboarding_type', ?)",
(onboarding_type,),
)
conn.commit()
conn.close()
def is_restricted(onboarding_type_or_slug):
"""Accepte directement une clé de ONBOARDING_TYPES, ou un slug de
projet (résout alors son onboarding_type d'abord) — pratique aussi
bien pour core/auth_guard.py (a le slug) que pour un test unitaire (a
déjà la clé)."""
onboarding_type = (
onboarding_type_or_slug if onboarding_type_or_slug in ONBOARDING_TYPES
else get_onboarding_type(onboarding_type_or_slug)
)
return ONBOARDING_TYPES.get(onboarding_type, ONBOARDING_TYPES[DEFAULT_ONBOARDING_TYPE])["restricted"]