Tutoriel guidé (didacticiel) : tour général d'orientation + tour dédié à la correction des métadonnées de recette #42
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Contexte
Accompagner l'utilisateur à sa première connexion avec un didacticiel :
popovers explicatifs + mise en évidence des menus, sur les écrans clés.
Décisions prises avec l'utilisateur : deux tours distincts — un tour
général d'orientation, et un tour spécifique pour la correction des
métadonnées de recette (technique/ingrédients/ustensiles/température) — tous
les deux rejouables à la demande depuis les paramètres.
Deux explorations ont établi que rien de tout ça n'a de précédent dans le
repo :
/onboarding/regime→/foyer→/sources→/allergenes) : mécanisme totalement différent, à but data-collection,sans aucun flag de complétion persistant nulle part — juste une navigation
impérative une seule fois après
signup()(SignupPage.tsx:55,void navigate("/onboarding/regime")).login()ne repasse jamais parl'onboarding, quel que soit l'état du profil. Rien à réutiliser tel quel
pour "a déjà vu le tutoriel", mais un précédent de forme utile existe dans
le schéma :
Ingredient.reviewedAt(DateTime?nullable, "stampé une foisfait,
nullen attente") est la colonne la plus proche de ce dont on abesoin.
Dialog.tsx,Tooltip.tsx,WeekNavigator'sCalendarPopover,TechStepCorrectionPopover) : aucun ne fait desuivi de position en temps réel d'un élément arbitraire — chacun est soit
un
<dialog>natif (showModal(), top layer du navigateur), soit ancré enCSS à un parent fixe voisin, soit en flux normal. Zéro
getBoundingClientRectde tracking (le seul hit du repo est un hit-testponctuel dans
Dialog.tsx, pas du positionnement), zéroResizeObserver,zéro
createPortal, zéro échelle de z-index dans_theme.scss(juste des10ad hoc dans quelques fichiers). Un tour qui doit surligner deséléments arbitraires à travers plusieurs pages est une brique d'UI
entièrement nouvelle pour ce repo, pas une extension d'un patron
existant.
id/data-*à cibleraujourd'hui — à ajouter partout où le tour doit pointer.
Choix de librairie — recherché en direct (web + vérification GitHub)
plutôt que deviné :
driver.js, pasreact-joyride. Comparatif :react-joyrideest le plus populaire pour React (~340k téléchargements/semaine) mais sans publication depuis ~9 mois et incompatible React 19 sans
version instable, ~34 Ko gzippé avec ses dépendances.
shepherd.js/intro.jssont en licence AGPL sauf achat d'une licence commerciale — àéviter pour ce projet.
driver.js: ~5 Ko gzippé, licence MIT, zérodépendance, TypeScript vanilla (pas de binding React officiel, mais son API
impérative simple s'appelle très bien depuis un hook/contexte React).
Conception — persistance (pensée pour N tours dès le départ)
Une nouvelle table plutôt qu'un flag unique, puisqu'il y a déjà 2 tours
connus et vraisemblablement d'autres à venir — même convention "clé stable"
que
Diet.key/Unit.key/TechStep.key/Source.key:Présence d'une ligne = "vu" (passer un tour marque aussi complété — pas de
re-nag après un skip volontaire).
GET /profile/tours→{ completedTourKeys: string[] },POST /profile/tours/:tourKey/complete→upsert la ligne (idempotent).
Ne bénéficie pas du passage "gratuit" par
/auth/me— vérifié :toSafeProfile()(apps/api/src/lib/safe-profile.ts) ne fait que retirerpasswordHashdu reste de la ligne PrismaUserProfile, donc une colonneajoutée directement là y apparaîtrait automatiquement, sans nouvel endpoint.
Une table séparée comme
TourProgressn'a pas ce raccourci (il faut sonpropre fetch, comme
themevia/preferencesaujourd'hui) — mais le gaind'extensibilité (aucune migration pour un 3e tour futur) l'emporte sur cette
petite latence supplémentaire. Un
TourProvider(contexte React, mêmeposition que
ThemeProviderdansmain.tsx, même patron queThemeContext.tsx: effet gardé suruser?.id, pas delocalStorage)charge
completedTourKeysune fois la session connue.Conception — moteur de tour réutilisable
useTour()exposestartTour(key)/isTourCompleted(key), pilotedriver.js(navigation React Router entre étapes situées sur des routesdifférentes ;
driver.jsgère le surlignage/popover une fois l'élémentmonté), et appelle l'endpoint de complétion à la fin de la séquence ou au
clic sur "Passer".
Portée volontairement limitée pour cette itération : le tour ne cible
jamais un élément qui n'existe qu'à l'intérieur d'un
<dialog>natif ouvert(
Dialog.tsx) — un<dialog>enshowModal()vit dans le top layer dunavigateur, au-dessus de tout, y compris l'overlay de
driver.jsqui n'estqu'un élément positionné classique. Cibler l'intérieur d'un dialogue
casserait visuellement l'effet de surlignage (le dialogue s'afficherait
par-dessus le halo/popover du tour). Les deux tours ci-dessous restent donc
sur du contenu de page normale.
Tour 1 — Général (orientation)
Déclenché automatiquement à l'arrivée sur
/si!isTourCompleted("general")— couvre aussi bien la sortie du wizardd'onboarding que la prochaine connexion d'un utilisateur déjà existant qui
ne l'a jamais vu (rien à distinguer entre les deux cas, la condition est la
même). Étapes (contenu réel à affiner en revue de PR) :
NavLinkdeAppLayout.tsx'sNAV_ITEMSgagne undata-tour-nav-<key>).PlanningPage) — comment lire la semaine.RecipePickerDialog, sans l'ouvrir (limite du<dialog>ci-dessus).Tour 2 — Correction des métadonnées de recette
Déclenché la première fois que
StepDescription'sactiveCorrectiondevient non-null en mode éditable
(
apps/web/src/features/recipes/steps/StepDescription.tsx) si!isTourCompleted("techStepMetadata").Dépendance à noter avant de définir les étapes précises :
l'issue #36 (le
popover de correction se ferme au lieu de rester ouvert pendant la sélection
d'un ingrédient/ustensile/température) est encore ouverte — ce tour ne
devrait pas apprendre un geste actuellement cassé. Soit #36 est corrigé
d'abord, soit ce tour se limite pour l'instant à la partie qui fonctionne
déjà (sélectionner du texte, choisir une technique) sans couvrir l'ajout
d'ingrédient/ustensile/température tant que #36 n'est pas réglé.
Conception — rejouer un tour depuis les paramètres
Nouvelle petite section (page dédiée
/parametres/aide, ou bloc ajouté àAccountSettingsPage.tsx— à trancher en implémentation) listant chaquetour connu avec un bouton "Revoir" — conçue pour scaler à un 3e tour futur
sans changement de structure (simple
mapsur le registre deTourDefinition, pas une entrée codée en dur par tour).Fichiers à toucher
apps/api/prisma/schema.prisma— modèleTourProgress+ migration.apps/api/src/modules/profile/— routes/serviceGET/POST /profile/tours*.apps/web/src/features/tour/(nouveau) —TourContext.tsx, définitionsdes 2 tours, wrapper autour de
driver.js.apps/web/src/layouts/AppLayout.tsx,pages/planning/PlanningPage.tsx,pages/shopping-list/ShoppingListPage.tsx— attributsdata-tour-*+déclenchement du tour général.
apps/web/src/features/recipes/steps/StepDescription.tsx— déclenchementdu tour métadonnées.
apps/web/src/pages/settings/— section "Revoir un tutoriel".apps/web/package.json— ajoutedriver.js.apps/web/src/locales/fr/translation.json— namespacetour.*.Tests
GET/POST /profile/tours/:key/complete(upsert idempotent,ne casse rien si rappelé plusieurs fois).
/et peut le terminer/passer ; une fois complété, aucun seconddéclenchement automatique (
isTourCompleted) ; le lien "Revoir" dans lesparamètres relance la séquence à la demande indépendamment de l'état
"déjà vu".
Hors scope
cuisine, foyer partagé...) — le registre est conçu pour en accueillir
facilement plus tard, aucun n'est écrit dans cette itération.
<dialog>ouvert (limite du toplayer expliquée plus haut).
structure et les cibles sont posées ici, le texte se peaufine en revue de
PR.
🤖 Filed by Claude Code
Révisé par issue #44 : le registre
TourDefinition[]statique en TypeScript (avec titres/contenus en clés i18n) décrit ci-dessus est remplacé par un modèle en base (TourScenario/TourScenarioStep/TourTarget, texte libre plutôt que clés i18n) éditable depuis l'admin. Implémenter #44 en premier (ou en même temps) — le moteur de tour (useTour(),driver.js) doit consommerGET /reference/toursplutôt que le registre statique décrit ici.🤖 Filed by Claude Code
Livré en 4 PR (avec #44, comme prévu par son corps) :
TourTarget/TourScenario/TourScenarioStep/TourProgress), migration, seed des 2 scénarios + 9 cibles,GET /reference/tours,GET/POST /profile/tours*(idempotent,tour.completed/tour.skippeden analytics)./admin/api/tours*.driver.js,useTour()/TourProvider, wrappertour-engine.ts(navigation inter-routes + issue unique), attributsdata-tour-*, déclenchement auto (tour général sur/, tour métadonnées à l'ouverture du popover de correction), section « Revoir un tutoriel » dans les préférences utilisateur./admin/didacticiels.Tests :
tour.feature(5 scénarios) +admin-tours.cy.ts(4) + Mocha (/reference/tours,/profile/tours, CRUD admin).Réserve : le tour
techStepMetadatane fait pour l'instant que 2 étapes (sélectionner du texte, choisir une technique) — l'ajout d'ingrédient/ustensile/température attend la correction de #36 (le popover se ferme en pleine sélection), exactement comme ce ticket l'anticipait. À rouvrir/compléter une fois #36 réglé, ou à fermer si la v1 convient.🤖 via Claude Code
Fermeture tardive — le travail est en réalité déjà livré depuis le 2026-09-06 via 4 PR mergées sur
main: #58 (socle données + API), #59 (CRUD admin API), #60 (moteur driver.js), #61 (UI admin CRUD). Les deux tours fonctionnent réellement (TourContext/tour-engine.ts, déclenchement auto surPlanningPageetStepDescription, rejouable depuis/parametres/preferences-utilisateur). Ce ticket était juste resté ouvert par oubli.