---
name: tutorial-inject
description: >
  Injecte un systeme de tutoriel/onboarding complet dans une application existante (web ou mobile).
  Utiliser quand l'utilisateur demande d'ajouter un tutoriel, un onboarding, une visite guidee,
  un product tour, un carrousel d'introduction, ou des etapes guidees a son app.
  Compatible React/Vite, Next.js, React Native (Expo & CLI), Angular, Vue.
---

# Tutorial Inject

> Profiler. Cibler. Injecter. Valider. Rien ne passe sans preuve. Rien ne doit casser.

---

## Quand Utiliser

- Quand l'utilisateur demande un tutoriel, onboarding, product tour, visite guidee, carrousel d'introduction
- Quand une page ou feature cle manque d'accompagnement utilisateur
- Pour ajouter un bouton de relancement du tutoriel (FAB) ou un menu d'aide flottant (Help Hub)
- Pour migrer AsyncStorage vers MMKV sur un slider existant

## Ne PAS Utiliser

- Si le projet est natif Swift/Kotlin pur sans React Native (skill non couvert)
- Pour documenter le code (utiliser doc-writer)
- Pour analyser les features metier (utiliser feature-miner)

---

## Core Invariants (NON-NEGOTIABLE)

> Ces regles ne peuvent JAMAIS etre violees. Une action qui viole un invariant est ANNULEE.

1. **LIRE AVANT D'ECRIRE** — Chaque fichier modifie DOIT avoir ete lu avant toute mutation. Zero modification a l'aveugle.
2. **PROFIL D'ABORD** — Ne jamais ecrire une ligne de code avant que le profil technologique soit affiche et confirme.
3. **ARCHITECTURE D'ABORD** — Ne jamais ecrire une ligne de code avant que l'architecture cible (simple FAB / Help Hub / slider + tutoriel) soit clarifie avec l'utilisateur (Phase 0).
4. **AST UNIQUEMENT** — Il est formellement interdit de modifier du code source par Regex ou concatenation de strings. Les mutations passent par la lecture/reecriture semantique du fichier.
5. **ZERO DEPENDANCE AGPL** — Intro.js et toute lib sous licence AGPL sont INTERDITES en contexte commercial.
6. **3 A 8 ETAPES PAR FLOW** — Un tutoriel simple contient 3-5 etapes. Une tache complexe peut aller jusqu'a 8. Au-dela de 8, l'agent DOIT elaguer ou proposer de scinder en deux flows distincts.
7. **MMKV OBLIGATOIRE (React Native)** — AsyncStorage est INTERDIT pour la persistance du tutoriel en RN. Cause du flickering au boot. MMKV uniquement.
8. **data-tour-target UNIQUEMENT (Web)** — Le ciblage des elements se fait exclusivement via des attributs `data-tour-target`, jamais via des classes CSS (fragiles aux refactorisations).
9. **"use client" OBLIGATOIRE (Next.js)** — Tout composant Provider ou logique de tutoriel injecte dans l'App Router Next.js DOIT porter la directive "use client".
10. **COPYWRITING BENEFICE, PAS DESCRIPTION** — Les textes generes expliquent la valeur metier, jamais l'interface visuelle. "Configurez vos alertes de production" pas "Cliquez sur la cloche".
11. **POSTURE ADVERSARIALE** — L'agent part du principe que le codebase a des particularites qui peuvent faire echouer l'injection. L'objectif est de les TROUVER avant d'ecrire. Si la Phase 6 ne trouve AUCUN probleme, c'est que la validation est baclee.
12. **LOI D'AIRAIN** — Affirmer qu'une injection est reussie sans avoir execute tsc/build est un mensonge. AUCUNE declaration de succes sans preuve fraiche.
13. **CONTROLE UTILISATEUR OBLIGATOIRE** — Tout tutoriel genere DOIT exposer : bouton Skip visible, bouton Suivant en bas a droite, bouton Precedent (sauf etape 1), fermeture par Echap.
14. **NE PAS REINVENTER** — Si un systeme de tutoriel existe deja dans le projet (tutorialFlows.ts, OnboardingSlider.tsx, etc.), l'agent DOIT l'etendre, jamais en creer un second en parallele.
15. **STRICTMODE PROTECTION** — Toute instance Driver.js creee dans un useEffect DOIT avoir une fonction de cleanup qui appelle `instance.destroy()`. Ne jamais creer une nouvelle instance sans detruire la precedente.
16. **STYLING PREMIUM OBLIGATOIRE** — Le style par defaut de Driver.js est insuffisant pour une app professionnelle. Chaque injection DOIT inclure une classe CSS custom (ex: `driverjs-premium-popover`) avec au minimum : couleurs coherentes au design, border-radius >= 12px, ombres, boutons stylises, `-webkit-font-smoothing: antialiased`, `-moz-osx-font-smoothing: grayscale`, `text-shadow: none` sur tous les elements textuels (titre, description, boutons, progression), `font-family: inherit`. L'absence d'antialiasing cause un effet visuel de double contour (\"film 3D\") sur les themes sombres.
17. **CSS SAFE UNIQUEMENT** — Il est INTERDIT d'ecrire dans `globals.css` ou tout fichier CSS : (a) la syntaxe `var(--color/opacity)` dans des proprietes comme `box-shadow`, `background`, etc. — invalide en CSS standard et rejetee par PostCSS/Tailwind ; (b) toute autre syntaxe experimentale non supportee par PostCSS. Utiliser exclusivement : valeurs hexadecimales, `rgba()`, `var(--token)` seuls, ou classes Tailwind utilitaires.
18. **CONTEXT REACT STABLE** — Tout `TutorialProvider` DOIT stabiliser sa valeur de contexte avec `useMemo` sur l'objet passe a `.Provider`. Les callbacks exposes (`launchFlow`, `setShowSlider`, etc.) DOIVENT etre enveloppes dans `useCallback`. Le composant `HelpHubMenu` (ou tout composant UI du Hub) NE DOIT JAMAIS appeler `useTutorial` depuis l'interieur de sa propre declaration si ce composant est rendu par le `TutorialProvider` lui-meme — les fonctions doivent lui etre passees en props depuis le Provider pour eviter une boucle de dependance Context -> re-rendu -> Context.
19. **SSR GUARD OBLIGATOIRE (Next.js)** — Tout acces a `localStorage`, `sessionStorage`, `window`, ou `document` dans un composant Next.js App Router DOIT etre protege. Deux patterns valides : (a) `useEffect` avec un etat `isMounted` initialise a `false` puis passe a `true` apres le montage ; (b) verification explicite `typeof window !== 'undefined'` dans les fonctions appelees cote serveur. Les composants `HelpHubMenu`, `PresentationSlider`, et tout composant injecte a la racine du layout DOIVENT utiliser ce garde pour eviter les hydration mismatches.
20. **SEPARATION CONTEXT / PROVIDER (OBLIGATOIRE)** — Quand le `TutorialProvider` rend directement des composants UI (`HelpHubMenu`, `OnboardingSlider`, etc.) qui ont besoin d'acceder au contexte, il FAUT separer `TutorialContext` et `useTutorial` dans un fichier dedie (ex: `contexts/TutorialContext.tsx`), distinct du fichier `components/TutorialProvider.tsx`. Ce fichier exporte : (a) le `createContext`, (b) l'interface `TutorialContextValue`, (c) le hook `useTutorial`. Le fichier `TutorialProvider.tsx` importe depuis ce fichier context. Les composants UI (`HelpHubMenu`, `OnboardingSlider`) importent depuis ce fichier context. Cette separation EVITE les imports circulaires (`TutorialProvider` -> `HelpHubMenu` -> `TutorialProvider`). VIOLATION : creer `TutorialContext` et `useTutorial` dans le meme fichier que `TutorialProvider` quand ce dernier rend des composants UI qui consomment ce hook = erreurs TypeScript garanties (`duplicate identifier`, `Cannot find module`).
21. **SMOOTH SCROLL OBLIGATOIRE** — Toute instance Driver.js DOIT configurer `smoothScroll: true`. L'absence de ce parametre cause un highlight invisible sur les elements hors viewport (formulaires longs, ecrans laptop). INTERDIT d'initialiser `driver()` sans ce parametre.
22. **PRECEDENT = RETOUR D'ETAT COMPLET** — Le bouton Precedent dans un Task Walkthrough DOIT inverser l'etat UI que le step courant a cree : fermer le modal ouvert, revenir a l'URL precedente, scroller vers l'element precedent. Un bouton Precedent generique qui ignore l'etat UI viole cette regle. Chaque step avec un `enterAction` DOIT definir un `exitAction` correspondant.

---

## PHASE 0 — Decouverte d'Architecture

> GATE BLOQUANTE : Ne pas passer a la Phase 1 sans avoir pose ces questions et recu les reponses.

Cette phase est obligatoire. Elle evite de construire un FAB simple quand l'utilisateur veut un Help Hub complet.

### 0.1 Questions Obligatoires a Poser

```
QUESTIONS D'ARCHITECTURE — a poser avant tout travail

  Q1 — PROCEDURES :
    Listez chaque tutoriel souhaite sous forme de procedure nommee.
    Exemples : "Creer une presentation", "Inviter un membre", "Lancer une campagne".
    -> Si l'utilisateur a fourni la liste dans sa demande -> confirmer et valider avec lui.
    -> Si non fourni -> BLOQUER jusqu'a obtenir la liste complete.

    ⛔ INTERDIT : inferer ou auto-decouvrir les procedures depuis le code.
       Le developpeur definit le contenu. Le skill execute.

    Deduire le perimetre depuis le nombre de procedures :
    -> 1 procedure  -> Architecture simple (FAB + 1 flow)
    -> 2 procedures ou +  -> Architecture Help Hub (menu + N flows)

  Q2 — PARADIGME :
    Voulez-vous :
    (a) Un tutoriel guide (Driver.js : "Cliquez ici, regardez la")
    (b) Un slider de presentation (Framer Motion : "Voici ce que vous pouvez faire")
    (c) Les deux, avec un menu pour choisir
    -> Si (c) : les deux systemes coexistent, decrire le point d'entree unique

  Q3 — DECLENCHEMENT :
    Le tutoriel doit-il se lancer automatiquement a la premiere visite ?
    -> OUI -> Sur quelle(s) page(s) ? (premiere visite globale, ou par page ?)
    -> NON -> Uniquement via bouton manuel

  Q4 — NAVIGATION :
    Les tutoriels couvrent-ils plusieurs routes/pages ?
    -> OUI -> La navigation inter-routes est requise (redirect automatique)
    -> NON -> Tout se passe sur une seule page

  Q5 — LANGUE :
    Le projet est-il EN, FR, ou bilingue ?
    -> Bilingue -> Preciser le systeme i18n utilise (i18next, next-intl, etc.)

  Q6 — PARADIGME CONTENU (par defaut : Task Walkthrough) :
    Pour chaque procedure, quel type de guidage ?
    (a) Task Walkthrough [DEFAUT] -> Guide une action complete de A a Z
        ("Creez votre premier workflow en 4 etapes", l'utilisateur avance etape par etape)
    (b) UI Tour -> Presente les sections de l'interface
        ("Voici votre tableau de bord, voici les filtres")

    Si non precise -> supposer (a) Task Walkthrough pour toutes les procedures.
    Chaque procedure peut avoir un paradigme different si l'utilisateur le demande.
    ⛔ INTERDIT : generer un UI Tour si l'utilisateur a demande un Task Walkthrough.
```

### 0.2 Architectures Cibles

| Reponses | Architecture | Composants a creer |
|---|---|---|
| 1 flow, pas d'auto-launch | **FAB Simple** | tutorialFlows.ts + FAB bouton |
| 1 flow, auto-launch | **Auto-Tour** | tutorialFlows.ts + TutorialProvider + FAB |
| N flows, meme page | **Help Hub basique** | tutorialFlows.ts + HelpHubMenu + TutorialProvider |
| N flows, multi-pages | **Help Hub complet** | tutorialFlows.ts + HelpHubMenu + TutorialProvider + redirect logic |
| Slider + tutoriels | **Help Hub bimodal** | OnboardingSlider + tutorialFlows.ts + HelpHubMenu + TutorialProvider |

### 0.3 Rapport d'Architecture (afficher avant Phase 1)

```
RAPPORT D'ARCHITECTURE — Phase 0
  Architecture cible  : [FAB Simple / Auto-Tour / Help Hub basique / complet / bimodal]
  Procedures definies : [liste nommee des procedures fournies par le developpeur]
  Nb de flows         : [N]
  Paradigme/flow      : [ex: "Creer une presentation" -> Task Walkthrough, "Decouvrir le dashboard" -> UI Tour]
  Auto-launch         : [Oui, sur route X / Non]
  Multi-pages         : [Oui, routes: [...] / Non]
  Langue              : [FR / EN / Bilingue via i18next]
  Composants a creer  : [liste]
```

**STOP : Afficher ce rapport. Attendre confirmation avant Phase 1.**

---

## PHASE 1 — Profilage Topologique

> GATE BLOQUANTE : Ne pas passer a la Phase 2 sans avoir affiche le rapport de profil complet.

### 1.1 Scan des Manifestes

Lire dans cet ordre :
1. `package.json` (ou `build.gradle.kts` / `Package.swift`)
2. Fichiers de config framework : `next.config.*`, `vite.config.*`, `app.json`, `metro.config.js`
3. Point d'entree : `src/main.tsx`, `app/layout.tsx`, `App.tsx`, `index.js`

### 1.2 Detection du Framework

| Signal detecte | Framework | Point d'injection |
|---|---|---|
| `next` dans deps + `app/layout.tsx` | Next.js App Router | `app/layout.tsx` |
| `next` dans deps + `pages/_app.tsx` | Next.js Pages Router | `pages/_app.tsx` |
| `ReactDOM.createRoot` dans `src/main.tsx` | React + Vite | `src/main.tsx` |
| `createApp(App)` | Vue + Vite | `src/main.ts` |
| `bootstrapApplication` | Angular | `src/main.ts` |
| `expo` dans deps + `app/_layout.tsx` | React Native Expo Router | `app/_layout.tsx` |
| `expo` dans deps + `App.tsx` | React Native Expo Classic | `App.tsx` |
| Pas d'`expo` + `App.tsx` | React Native CLI | `App.tsx` |

**Si le framework ne correspond a aucune ligne : STOP. Demander a l'utilisateur avant de continuer.**

### 1.3 Inventaire du Systeme Existant

Rechercher activement dans le codebase :

```
EXISTANT A DETECTER :
  Tutoriel web    -> chercher : "driver", "joyride", "shepherd", "onborda", "react-joyride"
  Tutoriel mobile -> chercher : "OnboardingSlider", "walkthrough-tooltip", "highlight-overlay"
  Flows definis   -> chercher : "tutorialFlows", "tourSteps", "onboardingSteps"
  Persistance web -> chercher : "tutorialService", "hasSeenWelcome", "onboardingCompleted"
  Persistance RN  -> chercher : "AsyncStorage", "mmkv", "MMKV"
  FAB existant    -> chercher : "FloatingButton", "TutorialButton", "data-tour-trigger", "HelpHub"
  Provider exist. -> chercher : "TutorialProvider", "TourProvider", "OnboardingProvider"
  Slider exist.   -> chercher : "OnboardingSlider", "framer-motion", "AnimatePresence"
```

### 1.4 Detection de la Palette de Couleurs du Projet (OBLIGATOIRE)

> Cette etape est requise pour generer un CSS premium COHERENT avec le design existant du projet.
> Ne jamais hardcoder des couleurs specifiques (slate, orange, purple, etc.) dans le skill.

```
DETECTER DANS CET ORDRE :

  1. Lire globals.css (ou app/globals.css, src/index.css) :
     -> Chercher les variables CSS custom : --background, --foreground, --primary, --accent, --border, --muted
     -> Chercher les classes Tailwind utilisees massivement (background-color, border-color)
     -> Identifier : theme sombre (dark background) ou clair (light background) ?

  2. Lire tailwind.config.ts (ou tailwind.config.js) si present :
     -> Extraire le theme.extend.colors : noms et valeurs des couleurs custom
     -> Identifier la couleur primaire/accent (souvent : primary, accent, brand, cta)
     -> Identifier la couleur de fond principale (souvent : background, surface, card)
     -> Identifier la couleur de texte secondaire (souvent : muted, subtle, secondary)

  3. Si aucun des deux -> Lire 2-3 composants UI principaux (layout, sidebar, header) :
     -> Extraire les couleurs utilisees dans className (bg-*, text-*, border-*)
     -> Deduire la palette par frequence d'utilisation

  VARIABLES A EXTRAIRE (nommer explicitement) :
    COLOR_BG_POPOVER   : couleur de fond du popover (ex: #1e293b, var(--card), bg-slate-800)
    COLOR_BORDER       : couleur de bordure (ex: #334155, var(--border), border-slate-700)
    COLOR_TEXT_PRIMARY : couleur du titre (ex: #f8fafc, var(--foreground), text-white)
    COLOR_TEXT_MUTED   : couleur description (ex: #94a3b8, var(--muted-foreground), text-slate-400)
    COLOR_CTA          : couleur bouton principal (ex: #f97316, var(--primary), bg-orange-500)
    COLOR_CTA_TEXT     : texte sur bouton CTA (presque toujours white ou black selon contraste)
    BORDER_RADIUS      : border-radius general du projet (ex: 8px, 12px, 16px - infer de rounded-*)
```

**STOP si palette non determinable** : Si aucune couleur n'est extractible -> signaler a l'utilisateur et lui demander de fournir les valeurs ou le fichier CSS principal.

### 1.5 Rapport de Profil (OBLIGATOIRE — afficher avant de continuer)

```
RAPPORT DE PROFIL — tutorial-inject
  Framework       : [Next.js App Router / React+Vite / React Native Expo / ...]
  Point d'entree  : [chemin exact]
  Lib tutoriel    : [Existante: driver.js v3.x / Aucune -> A installer: driver.js]
  Framer Motion   : [Presente / Absente -> installation necessaire si slider voulu]
  Flows existants : [tutorialFlows.ts: intro_flow, editor_flow / Aucun]
  Persistance     : [tutorialService.ts (via authService) / AsyncStorage / Aucune]
  FAB / HelpHub   : [Present / Absent]
  Provider exist. : [TutorialProvider.tsx / Aucun]
  Action requise  : [Nouveau systeme complet / Ajout d'un flow / Help Hub / Migration MMKV / ...]

  Palette detectee :
    COLOR_BG_POPOVER   : [valeur exacte extraite du projet]
    COLOR_BORDER       : [valeur exacte extraite du projet]
    COLOR_TEXT_PRIMARY : [valeur exacte extraite du projet]
    COLOR_TEXT_MUTED   : [valeur exacte extraite du projet]
    COLOR_CTA          : [valeur exacte extraite du projet]
    COLOR_CTA_TEXT     : [white / black]
    BORDER_RADIUS      : [valeur en px]
    Theme              : [sombre / clair]
    Source             : [globals.css / tailwind.config.ts / composants inspectes]
```

**STOP : Afficher ce rapport. Attendre confirmation ou correction avant Phase 2.**

---

## PHASE 2 — Gestion des Dependances

> Ne jamais installer une dependance deja presente dans package.json.

### Matrice de Selection

| Framework | Lib principale | Lib slider | Persistance | Commande |
|---|---|---|---|---|
| Web agnostique / React / Vue | `driver.js` (MIT) | `framer-motion` si slider voulu | `localStorage` (natif) | `npm install driver.js` |
| Next.js + Framer Motion deja present | `onborda` (MIT) ou `driver.js` | deja presente | `localStorage` (natif) | selon choix |
| Next.js sans Framer Motion | `driver.js` (MIT) | `framer-motion` si slider voulu | `localStorage` (natif) | `npm install driver.js framer-motion` |
| React Native Expo Managed | `react-native-mmkv` | — | MMKV (synchrone) | `npx expo install react-native-mmkv` |
| React Native CLI | `react-native-mmkv` | — | MMKV (synchrone) | `npm install react-native-mmkv && cd ios && pod install` |
| Swift / SwiftUI | Aucune | — | UserDefaults natif | — |
| Android Compose | `intro-showcase-view` | — | SharedPreferences | Ajouter dans `build.gradle.kts` |

### Gate d'Installation

```
AVANT d'installer :
  1. La lib est-elle deja dans package.json ?
     -> OUI -> SKIP. Ne pas reinstaller.
     -> NON -> Installer et verifier la sortie (0 erreurs, 0 peer conflict)

  2. La lib a-t-elle une licence AGPL ?
     -> OUI -> REFUSER. Choisir l'alternative MIT.
     -> NON -> OK.

  3. Apres installation, tsc --noEmit passe-t-il toujours ?
     -> NON -> Corriger avant de continuer.
```

---

## PHASE 3 — Decouverte des Cibles

> L'objectif est de trouver 3 a 5 elements UI de haute valeur PAR FLOW. Pas plus. Pas moins.
> MINIMUM ABSOLU : 3 etapes par flow. Un flow a 1 seule etape generique est un ECHEC.

### 3.0 Scan des Vues et Composants du Projet (OBLIGATOIRE AVANT TOUT)

> Ne jamais generer des flows sans avoir d'abord inventorie les vues reelles du projet.

```
SCAN OBLIGATOIRE :

  1. Lister TOUTES les fichiers de vues/pages (PAS seulement layout ou page d'accueil) :
     -> Next.js : app/**/page.tsx, app/**/layout.tsx
     -> React+Vite : src/pages/**, src/views/**, src/screens/**
     -> Chercher aussi : components/**View.tsx, components/**Page.tsx
     GATE BLOQUANTE : Si l'architecture est multi-pages (Phase 0 Q1 >= 2 flows),
     le scan DOIT couvrir CHAQUE page individuellement.
     Un agent qui scanne uniquement le layout ou la page d'accueil pour N > 1 flows
     a viole cette gate. Revenir au scan avant d'ecrire quoi que ce soit.
     ASSERTION : afficher la liste de TOUTES les vues trouvees avant de passer au point 2.

  2. Pour CHAQUE vue identifiee (sans exception), lire le fichier et extraire :
     -> KPIs / metriques affichees (cards, chiffres, graphiques)
     -> Listes / tableaux de donnees (ExecutionsList, WorkflowTable, etc.)
     -> Filtres / controles (DatePicker, StatusFilter, SearchBar, etc.)
     -> Actions principales (boutons CTA, formulaires de creation, etc.)
     -> Navigation secondaire (onglets, breadcrumbs, pagination)
     -> Elements conditionnels (modals, drawers, tabs, dropdowns qui cachent des elements cibles)
       IMPORTANT : Pour chaque element conditionnel, noter : quel declencheur l'ouvre (bouton, click)
       et comment le fermer. Ces infos seront utilisees pour les Step Actions (Phase 5).

  3. Construire un INVENTAIRE PAR VUE avant de generer quoi que ce soit :
     Vue [nom] (route: /[chemin]) :
       - KPIs              : [liste des metriques visibles]
       - Listes            : [tableaux ou listes de donnees]
       - Filtres           : [filtres et controles de recherche]
       - Actions CTA       : [boutons ou formulaires d'action]
       - Navigation        : [onglets, sidebar items, etc.]
       - Elements conditionnels : [modals/drawers/tabs — declencheur + fermeture]

  4. Verifier qu'AUCUN flow n'est ecrit avant que cet inventaire soit complet.
     -> Flow ecrit sans inventaire = STOP. Revenir au scan.
```

**ASSERTION MINIMALE PAR FLOW** : Si une vue a moins de 3 elements distincts identifiables -> chercher les composants enfants importes dans cette vue (lire les imports et les composants utilises).

### 3.1 Perimetre : mono-page vs multi-pages

```
AVANT de scanner :
  Architecture est multi-pages (Phase 0) ?
  -> OUI -> Scanner CHAQUE page independamment.
             Chaque page = 1 flow independant = 3-5 cibles propres a cette page.
             La limite 3-5 s'applique PAR FLOW, pas au total du projet.
             GATE : Afficher la liste des pages qui SERONT couvertes avant d'ecrire
             quoi que ce soit. Si cette liste n'est pas affichee -> STOP.
             Un agent qui couvre 1 seule page alors que N > 1 flows ont ete confirmes
             en Phase 0 a viole cette gate. Recommencer le scan.
  -> NON -> Scanner la page unique. Appliquer la limite 3-5 globalement.
```

> **ASSERTION MULTI-PAGES** : Si Phase 0 Q1 = N flows > 1, le nombre de fichiers
> contenant des `data-tour-target` generes DOIT etre >= N. Un seul fichier modifie
> pour N > 1 flows = ECHEC. Corriger avant Phase 4.

### 3.2 Heuristiques de Detection (par priorite decroissante)

**Tier 1 — Semantique A11y (signal le plus fiable)**

| Signal | Cible probable | Etape suggeree |
|---|---|---|
| `<nav>` / `role="navigation"` | Menu / Sidebar principale | Etape 1 : orientation generale |
| `<main>` / `role="main"` | Zone de contenu primaire | Conteneur de l'action principale |
| `<header>` / `role="banner"` | En-tete (profil, notifs, settings) | Acces aux parametres |
| `role="search"` / `input[type=search]` | Barre de recherche | Outil de navigation secondaire |
| `accessibilityRole="header"` (RN) | En-tete natif | Idem web |

**Tier 2 — Lexical (signal moyen)**

Inspecter les attributs `id`, `className`, `testID`, `data-testid` contenant :
`submit`, `create`, `add`, `save`, `settings`, `profile`, `dashboard`, `checkout`, `upload`, `help`, `wizard`

Inspecter les noms de composants :
`<Dashboard />`, `<UserProfile />`, `<SettingsPanel />`, `<CreateButton />`, `<AnalyticsChart />`

Inspecter les composants metier specifiques au projet (exemples generiques) :
`<KpiCard />`, `<MetricsPanel />`, `<FilterBar />`, `<DataTable />`, `<StatusBadge />`, `<ItemRow />`

**Tier 3 — Structural (signal faible, dernier recours)**

Composants de navigation : `BottomTabNavigator`, `AppBar`, `Sidebar`, `DrawerNavigator`

### 3.3 Structure Progressive Obligatoire (HARD ASSERT)

```
CHAQUE FLOW DOIT RESPECTER CETTE STRUCTURE EN 3-5 ETAPES :

  Etape 1 — ORIENTATION (obligatoire) :
    -> Pointer vers la navigation globale ou le menu principal
    -> Titre : comment se reperer dans l'app
    -> Benefice : "Acces a toutes les fonctions depuis un seul endroit"

  Etape 2 — DONNEES PRINCIPALES (obligatoire) :
    -> Pointer vers les KPIs, metriques, ou liste principale de la page
    -> Titre : ce que la vue montre comme information cle
    -> Benefice : valeur metier de cette information (ex: "Identifiez les problemes critiques en un coup d'oeil")

  Etape 3 — CONTROLES / FILTRES (obligatoire si filtres presents) :
    -> Pointer vers les filtres, la barre de recherche, ou les controles de periode
    -> Titre : comment affiner les donnees
    -> Benefice : gain de temps / precision de l'analyse

  Etape 4 — ACTION PRINCIPALE (si CTA present) :
    -> Pointer vers le bouton ou formulaire d'action principal
    -> Titre : ce que l'utilisateur peut faire
    -> Benefice : resultat concret de l'action

  Etape 5 — FEATURE AVANCEE (optionnel) :
    -> Pointer vers une feature secondaire de valeur (export, drill-down, etc.)
    -> Titre : fonctionnalite bonus
    -> Benefice : gain specifique

  VIOLATION : Flow avec 1 seule etape generique ("Explorez le dashboard") = ECHEC IMMEDIAT.
  CORRECTION : Revenir au scan 3.0 et identifier les elements reels de la vue.
```

### 3.3bis Structure Task Walkthrough (NOUVEAU — quand Q6 = a)

> Utiliser cette structure quand la procedure est un Task Walkthrough (action complete de A a Z).
> La structure 3.3 (UI Tour) reste valide pour les procedures en mode UI Tour (Q6 = b).

```
CHAQUE TASK WALKTHROUGH DOIT RESPECTER CETTE STRUCTURE EN 3-5 ETAPES :

  Etape 1 — POINT D'ENTREE (obligatoire) :
    -> Pointer le bouton / CTA qui demarre l'action (ex : bouton "Nouveau projet", "Creer")
    -> Titre : verbe d'action OBLIGATOIRE (ex: "Lancez votre premier workflow")
    -> Benefice : ce que l'utilisateur va accomplir a la fin de ce tutoriel
    -> Step Action : si le bouton ouvre un modal/drawer, declarer enterAction + exitAction

  Etape 2 — SAISIE PRINCIPALE (obligatoire) :
    -> Pointer le champ / la selection la plus importante du formulaire ou de l'action
    -> Titre : verbe d'action OBLIGATOIRE (ex: "Nommez votre projet")
    -> Benefice : pourquoi ce choix determine le resultat
    -> Step Action : si ce champ est dans un modal ouvert par l'etape 1, enterAction deja actif

  Etape 3 — CONFIG / OPTIONS (si present) :
    -> Pointer les parametres ou options secondaires (categorie, dates, permissions, etc.)
    -> Titre : verbe d'action OBLIGATOIRE (ex: "Configurez les acces")
    -> Benefice : precision et personnalisation du resultat

  Etape 4 — ACTION DE VALIDATION (obligatoire si bouton submit present) :
    -> Pointer le bouton de soumission / confirmation
    -> Titre : verbe d'action OBLIGATOIRE (ex: "Finalisez et activez")
    -> Benefice : resultat concret obtenu apres cette action

  Etape 5 — RESULTAT / SUITE (optionnel) :
    -> Pointer ou retrouver l'element cree (ex: la carte du projet apparu dans la liste)
    -> Titre : verbe d'action OBLIGATOIRE (ex: "Retrouvez et ouvrez votre projet")
    -> Benefice : continuite du workflow — ce que l'utilisateur peut faire maintenant

VIOLATIONS TASK WALKTHROUGH :
  ❌ Etapes qui pointent des elements non lies a la procedure definie
  ❌ Titres sans verbe d'action (ex: "La section workflow" = REJETE)
  ❌ Etapes qui presentent des sections de l'interface sans guider une action
  ❌ Demarrer le tutoriel sur un element conditionnel sans enterAction declare
```

**Verbes d'action valides** (titre DOIT en contenir au moins un) :
creer, configurer, activer, exporter, connecter, definir, selectionner, nommer, lancer, valider, finaliser, retrouver, ouvrir, soumettre, inviter, importer

Exemples de titres :
- ✅ ACCEPTE : "Nommez votre workflow et definissez son declencheur"
- ✅ ACCEPTE : "Finalisez et activez votre integration"
- ❌ REJETE : "La section workflow vous permet de saisir des informations"
- ❌ REJETE : "Voici le formulaire de creation"
- ❌ REJETE : "Explorez les options disponibles"

### 3.4 Regles d'Elagage (HARD ASSERT)

```
APRES detection brute pour CHAQUE PAGE/FLOW :
  1. Compter les cibles candidates -> N
  2. N > 5 ? -> ELAGUER. Garder uniquement :
       - 1 element de navigation globale
       - 1 KPI/metrique principale
       - 1 filtre ou controle de donnees
       - 1 action principale (CTA)
       - 1 feature secondaire utile (optionnel)
  3. N < 3 ? -> CREUSER. Lire les composants importes dans la vue.
               Si toujours < 3 apres lecture -> SIGNALER a l'utilisateur.
```

### 3.5 Couplage Cible <-> Tutoriel

**Web :** Injecter `data-tour-target="step-[nom]"` sur le noeud racine de l'element cible.
- Jamais sur un wrapper generique (`<div>`) quand un element semantique (`<nav>`) est disponible
- Jamais en utilisant des classes CSS comme selecteur (volatile)
- **Multi-pages** : Les attributs `data-tour-target` DOIVENT etre uniques globalement (pas seulement par page). Prefixer par la route : `data-tour-target="workflows-step-sidebar"`, `data-tour-target="dashboard-step-metrics"`.

**React Native :** Ajouter `testID="tour-[nom]"` ou encapsuler dans un wrapper avec `ref` si la lib l'exige.

### 3.6 Checkpoint Cibles (afficher avant Phase 4)

```
CHECKPOINT CIBLES — PAR FLOW

  FLOW : [nom_du_flow] (route: /[chemin])
    Cible 1 : data-tour-target="[nom-prefixe]" -> [fichier:ligne] -> "[Titre etape envisage]" [ORIENTATION]
    Cible 2 : data-tour-target="[nom-prefixe]" -> [fichier:ligne] -> "[Titre etape envisage]" [DONNEES]
    Cible 3 : data-tour-target="[nom-prefixe]" -> [fichier:ligne] -> "[Titre etape envisage]" [CONTROLES/FILTRES]
    ...
    Total : [N] etapes (minimum 3 requis : OK / < 3 : ECHEC — revenir au scan 3.0)

  [Repeter pour chaque flow si multi-pages]

  TOTAL FLOWS : [N] flows independants
  TOTAL data-tour-target : [M] attributs uniques (verifier absence de collision)
```

---

## PHASE 4 — Ingenierie de l'Etat et du Contenu

### 4.1 Persistance

**Web — Hook `useTourState`**

```typescript
// A creer dans hooks/useTourState.ts (ou equivalent)
// Si tutorialService.ts existe deja -> NE PAS creer ce hook.
// Utiliser fetchProgress() / updateProgress() de tutorialService.ts a la place.
//
// Architecture multi-tutoriels : chaque flowId est une cle independante.
// Les cles ne partagent JAMAIS de prefixe commun qui forcerait une reinitialisation globale.
const STORAGE_KEY = 'onboarding_completed';

export const useTourState = () => ({
  isCompleted: (flowId: string) =>
    localStorage.getItem(`${STORAGE_KEY}_${flowId}`) === 'true',
  markComplete: (flowId: string) =>
    localStorage.setItem(`${STORAGE_KEY}_${flowId}`, 'true'),
  reset: (flowId: string) =>
    localStorage.removeItem(`${STORAGE_KEY}_${flowId}`),
  resetAll: (flowIds: string[]) =>
    flowIds.forEach(id => localStorage.removeItem(`${STORAGE_KEY}_${id}`)),
});

// Exemple pour 3 tutoriels (noms adaptes au projet) :
// localStorage["onboarding_completed_home"] = "true"
// localStorage["onboarding_completed_settings"] = "true"
// localStorage["onboarding_completed_reports"] = "true"
// Chacun est independant. Reinitialiser "settings" n'affecte pas "home".
```

**React Native — Module `tutorialStorage` (MMKV)**

```typescript
// A creer dans services/tutorialStorage.ts (ou equivalent)
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV({ id: 'tutorial-storage' });

export const tutorialStorage = {
  isCompleted: (flowId: string) =>
    storage.getBoolean(`onboarding_${flowId}`) ?? false,
  markComplete: (flowId: string) =>
    storage.set(`onboarding_${flowId}`, true),
  reset: (flowId: string) =>
    storage.delete(`onboarding_${flowId}`),
};
```

**VIOLATION FATALE** : Si AsyncStorage est utilise dans un fichier existant pour la persistance du tutoriel -> le remplacer par MMKV. Ne jamais laisser coexister les deux pour la meme donnee.

### 4.2 Auto-Launch sur Premiere Visite

Si l'architecture requiert un auto-launch (Phase 0, Q3 = OUI) :

```typescript
// Dans TutorialProvider.tsx ou dans la page concernee
// Detecter "premiere visite de CETTE page" (independant des autres pages)
// Remplacer 'home', homeFlow, 'settings', settingsFlow par les ids reels du projet
useEffect(() => {
  // IMPORTANT : Empecher l'auto-launch si un Slider ou une intention de redirection est en cours
  // (Sinon le tuto et le slider se superposent)
  if (!isMounted || pendingFlowId || showSlider) return;

  const tourState = useTourState();

  // Si on est sur le Dashboard (/) et que le slider global n'est pas confirme = on abandonne l'auto-launch
  if (pathname === '/' && !tourState.isCompleted('onboarding_global')) return;

  // "premiere visite globale" -> verifier la cle de la page principale
  if (!tourState.isCompleted('home')) {
    startFlow(homeFlow, () => tourState.markComplete('home'));
    return;
  }

  // "premiere visite de CETTE page, mais pas premiere visite globale"
  // -> verifier la cle specifique a la page courante
  // if (pathname === '/settings' && !tourState.isCompleted('settings')) { ... }
}, [pathname, isMounted, pendingFlowId, showSlider]); // React hook deps

// IMPORTANT : Ne jamais lancer le tutoriel si l'utilisateur revient apres l'avoir complete.
// La cle localStorage protege contre le re-lancement.
```

### 4.3 Copywriting (Gate de Qualite Textuelle)

Pour chaque etape generee, appliquer ce test :

```
TEXTE CANDIDAT : "[texte de la bulle]"

  1. Decrit-il ce que l'utilisateur VOIT ? (ex: "Cliquez sur la cloche")
     -> OUI -> REFUSER. Reformuler avec le benefice metier.

  2. Decrit-il ce que l'utilisateur GAGNE ? (ex: "Ne manquez plus aucune anomalie")
     -> OUI -> ACCEPTER.

  3. Depasse-t-il 2 phrases ?
     -> OUI -> ELAGUER. Garder la phrase de benefice uniquement.

  4. La langue correspond-elle au projet ?
     -> Projet EN -> Anglais. Projet FR -> Francais. Bilingue -> voir 4.4.

  5. S'il s'agit d'un Task Walkthrough (Q6 = a) :
     Le TITRE de l'etape contient-il un verbe d'action ?
     (creer, configurer, activer, exporter, connecter, definir, selectionner, nommer, lancer, valider, etc.)
     -> NON -> REFUSER. Reformuler avec un verbe d'action.
     -> "Voici le formulaire" = REJETE
     -> "Nommez votre projet et definissez son type" = ACCEPTE
```

Exemples :
- REFUS : "Cliquez sur la barre laterale pour naviguer entre les sections."
- OK    : "Retrouvez toutes vos feuilles, approbations et parametres depuis ce menu central."
- REFUS : "Ce bouton cree une nouvelle fiche."
- OK    : "Creez votre premiere fiche de controle en moins de 30 secondes."

### 4.4 Projets Bilingues

Si le projet utilise un systeme i18n (i18next, next-intl, react-intl) :

```typescript
// NE PAS ecrire les textes en dur dans tutorialFlows.ts
// Utiliser les cles de traduction :
{
  element: '[data-tour-target="step-sidebar"]',
  popover: {
    title: t('tutorial.dashboard.step1.title'),
    description: t('tutorial.dashboard.step1.description'),
  },
}

// Ajouter les cles dans les fichiers de traduction AVANT d'ecrire le flow
// fr.json : { "tutorial": { "dashboard": { "step1": { "title": "...", "description": "..." } } } }
// en.json : { "tutorial": { "dashboard": { "step1": { "title": "...", "description": "..." } } } }
```

Si aucun systeme i18n n'existe, ecrire dans la langue principale du projet.

### 4.5 Controles UX (Non-Negociables)

Chaque tutoriel genere DOIT implementer :

| Controle | Position | Comportement |
|---|---|---|
| Bouton "Passer" / "Skip" | Gauche (bas de la bulle) | Ferme le tutoriel, marque comme complete |
| Bouton "Suivant" / "Next" | Droite (bas de la bulle) | Avance a l'etape suivante (+ declenche enterAction du step suivant si defini) |
| Bouton "Precedent" / "Back" | Gauche (visible des etape 2) | Inverse l'etat UI du step courant (exitAction si defini), revient a l'etape precedente |
| Bouton "Terminer" / "Done" | Droite (derniere etape) | Ferme et marque comme complete |
| Touche Echap | Global | Equivalent a "Passer" |
| Indicateur de progression | Dans la bulle | "Etape X sur Y" ou dots |

**INVARIANT 22 — Bouton Precedent** : Dans un Task Walkthrough, le Precedent DOIT restaurer l'etat UI :
- Si le step courant a ouvert un modal -> le fermer (`exitAction`)
- Si le step courant a provoque une navigation URL -> revenir a l'URL precedente
- Le scroll vers l'element precedent est gere automatiquement par `smoothScroll: true` (INVARIANT 21)
- Un Precedent qui ne fait que reculer l'index de l'etape sans restaurer l'etat UI = violation.

**Opacite du backdrop** : entre 0.50 et 0.75. Hors de cette fourchette -> contraste cognitif insuffisant ou contexte masque.

---

## PHASE 5 — Injection Structurelle

> Lire chaque fichier cible AVANT d'ecrire. Toujours.

### 5.1 Product Tour Web (Driver.js)

**Pattern : extension d'un systeme existant**

Si `tutorialFlows.ts` existe -> ajouter le flow a ce fichier en respectant le pattern existant
(`TutorialFlow` interface, exports nommes, i18n si present).

Si aucun systeme n'existe -> creer `lib/tutorial-flows.ts` ou `services/tutorialFlows.ts` :

```typescript
import type { DriveStep } from 'driver.js';

export interface TutorialFlow {
  id: string;
  route?: string;         // Route ou ce flow s'applique
  redirectRoute?: string; // Rediriger avant de demarrer si necessaire
  label: string;          // Nom affiche dans le Help Hub (si present)
  steps: DriveStep[];
}

// Nommer les flows selon les pages reelles du projet (ex: homeFlow, settingsFlow, reportsFlow)
export const homeFlow: TutorialFlow = {
  id: 'home',          // id unique, derive du nom de la page
  route: '/',          // route exacte ou ce flow s'applique
  label: 'Accueil',   // label affiche dans le Help Hub
  steps: [
    {
      element: '[data-tour-target="home-step-sidebar"]',
      popover: {
        title: 'Votre centre de navigation',
        description: 'Retrouvez toutes vos sections depuis ce menu central.',
        side: 'right',
      },
    },
    // ... (3-5 etapes total — adapter aux elements reels de la page)
  ],
};

// Exporter tous les flows pour le Help Hub (un par page couverte) :
export const ALL_FLOWS: TutorialFlow[] = [homeFlow /*, settingsFlow, reportsFlow, ... */];
```

**Initialisation Driver.js avec protection StrictMode (OBLIGATOIRE)**

```typescript
// Dans TutorialProvider.tsx — "use client" en premiere ligne si Next.js
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';
import { useRef } from 'react';
import type { Config as DriverConfig } from 'driver.js';

// INVARIANT 15 : ref pour tracker l'instance active et la detruire avant d'en creer une nouvelle
const activeDriverRef = useRef<ReturnType<typeof driver> | null>(null);

export const startFlow = (
  flow: TutorialFlow,
  activeDriverRef: React.MutableRefObject<ReturnType<typeof driver> | null>,
  onComplete?: () => void,
) => {
  // Detruire l'instance precedente si elle existe (protection StrictMode + navigation)
  if (activeDriverRef.current) {
    activeDriverRef.current.destroy();
    activeDriverRef.current = null;
  }

  const driverObj = driver({
    showProgress: true,
    animate: true,
    smoothScroll: true,          // INVARIANT 21 : OBLIGATOIRE — scroll automatique vers l'element
    overlayOpacity: 0.65,
    allowClose: true,
    popoverClass: 'driverjs-premium-popover', // INVARIANT 16 : classe CSS custom obligatoire
    nextBtnText: 'Suivant',
    prevBtnText: 'Precedent',
    doneBtnText: 'Terminer',
    onDestroyed: () => {
      activeDriverRef.current = null;
      onComplete?.();
    },
  });

  activeDriverRef.current = driverObj;
  driverObj.setSteps(flow.steps);
  driverObj.drive();
};

// Dans useEffect si le tutoriel se lance automatiquement :
useEffect(() => {
  // ... logique de lancement ...
  return () => {
    // Cleanup obligatoire : detruire l'instance si le composant est demonte
    if (activeDriverRef.current) {
      activeDriverRef.current.destroy();
      activeDriverRef.current = null;
    }
  };
}, []);
```

**Styling Premium Obligatoire (globals.css) — PARAMETRE PAR LA PALETTE DETECTEE**

> INVARIANT 16 : Ce bloc est OBLIGATOIRE. Utiliser EXCLUSIVEMENT les valeurs extraites en Phase 1.4.
> NE JAMAIS hardcoder des couleurs specifiques a un projet (slate-800, orange-500, etc.).
> Substituer les placeholders avec les valeurs detectees dans le Rapport de Profil.

Placeholders a remplacer avant d'ecrire le CSS :
- `[COLOR_BG_POPOVER]`   -> Phase 1.4 : COLOR_BG_POPOVER
- `[COLOR_BORDER]`       -> Phase 1.4 : COLOR_BORDER
- `[COLOR_TEXT_PRIMARY]` -> Phase 1.4 : COLOR_TEXT_PRIMARY
- `[COLOR_TEXT_MUTED]`   -> Phase 1.4 : COLOR_TEXT_MUTED
- `[COLOR_CTA]`          -> Phase 1.4 : COLOR_CTA
- `[COLOR_CTA_TEXT]`     -> Phase 1.4 : COLOR_CTA_TEXT
- `[BORDER_RADIUS]`      -> Phase 1.4 : BORDER_RADIUS (minimum 12px)

```css
/* INVARIANT 16 : CSS premium Driver.js adapte au design du projet — NE PAS hardcoder */
/* INVARIANT 17 : Jamais var(--x/opacity) — utiliser rgba() ou var(--token) seul         */
/* Feedback iter.6 : Remplacer par `.driver-popover.driverjs-premium-popover` pour battre la specificite de driver.js v1.x */
.driver-popover.driverjs-premium-popover {
  background: [COLOR_BG_POPOVER] !important;
  border: 1px solid [COLOR_BORDER] !important;
  border-radius: [BORDER_RADIUS] !important;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;
  padding: 24px !important;
  /* Antialiasing — obligatoire pour themes sombres (iter.5) */
  -webkit-font-smoothing: antialiased !important;
  -moz-osx-font-smoothing: grayscale !important;
  /* Police coherente avec le projet — evite heritage inattendu */
  font-family: inherit !important;
}

.driver-popover.driverjs-premium-popover .driver-popover-title {
  color: [COLOR_TEXT_PRIMARY] !important;
  font-size: 1.1rem !important;
  font-weight: 600 !important;
  /* Annuler text-shadow heritage (iter.5) */
  text-shadow: none !important;
}

.driver-popover.driverjs-premium-popover .driver-popover-description {
  color: [COLOR_TEXT_MUTED] !important;
  font-size: 0.9rem !important;
  line-height: 1.5 !important;
  /* Annuler text-shadow heritage (iter.5) */
  text-shadow: none !important;
}

.driver-popover.driverjs-premium-popover .driver-popover-footer button {
  padding: 8px 16px !important;
  border-radius: calc([BORDER_RADIUS] / 2) !important;
  font-weight: 500 !important;
  cursor: pointer !important;
  text-shadow: none !important;
  background: transparent !important;
  color: [COLOR_TEXT_MUTED] !important;
  border: 1px solid [COLOR_BORDER] !important;
}

.driver-popover.driverjs-premium-popover .driver-popover-footer button:hover {
  background: [COLOR_BORDER] !important;
  color: [COLOR_TEXT_PRIMARY] !important;
}

/* Boutons specifiques avec classe double pour dominer le CSS driver.js v1 */
.driver-popover.driverjs-premium-popover .driver-popover-next-btn,
.driver-popover.driverjs-premium-popover .driver-popover-footer button.driver-popover-next-btn {
  background: [COLOR_CTA] !important;
  color: [COLOR_CTA_TEXT] !important;
  border: none !important;
}

.driver-popover.driverjs-premium-popover .driver-popover-progress-text {
  color: [COLOR_TEXT_MUTED] !important;
  font-size: 0.8rem !important;
  text-shadow: none !important;
}
```

> Si le projet utilise des variables CSS (`var(--primary)`, `var(--card)`, etc.),
> les utiliser directement : `background: var(--card) !important;`
> Cela garantit que le popover suit le theme automatiquement (dark/light mode).

**VALIDATION avant d'ecrire le CSS** :
1. Palette detectee remplie en Phase 1.4 ? -> OUI -> Substituer les placeholders
2. Variables CSS custom presentes ? -> OUI -> Privilegier `var(--xxx)` sur les valeurs hexadecimales
3. BORDER_RADIUS detecte < 12px ? -> Monter a 12px minimum pour l'esthetique premium

### 5.2 Navigation Inter-Routes (Pattern "Pending Flow" — OBLIGATOIRE)

> **DANGER : Race Condition SPA** — Dans Next.js App Router (et toutes les SPA), un `router.push()`
> suivi d'un `setTimeout` ou d'un `await` pour demarrer le tutoriel CAUSE une boucle infinie.
> Le changement de route provoque un re-rendu complet qui peut recreer le Provider ou ses hooks,
> multipliant les timers en parallele. Ce pattern EST INTERDIT.

**Pattern INTERDIT (cause boucle infinie) :**
```typescript
// ❌ NE JAMAIS FAIRE
router.push(targetRoute);
setTimeout(() => startFlow(flow, ...), 800); // Race condition garantie
// ❌ NE JAMAIS FAIRE
router.push(targetRoute);
await new Promise(resolve => setTimeout(check, 300)); // Meme probleme
startFlow(flow, ...);
```

**Pattern OBLIGATOIRE : "Pending Flow" + Polling DOM**

Le principe : separer l'**intention** (stocker le flow voulu) de l'**action** (lancer le tutoriel).
Un `useEffect` surveille le `pathname` et ne demarre le tutoriel que quand la page ET le DOM sont prets.

```typescript
// Dans TutorialProvider.tsx — "use client"
// Etat : l'ID du tutoriel en attente de demarrage apres navigation
const [pendingFlowId, setPendingFlowId] = useState<string | null>(null);
const pathname = usePathname();

// Etape 1 : Enregistrer l'intention + naviguer
const launchFlowWithRedirect = (flow: TutorialFlow) => {
  const targetRoute = flow.redirectRoute ?? flow.route ?? pathname;

  if (pathname !== targetRoute) {
    // Stocker l'intention dans l'etat (persiste au re-rendu)
    setPendingFlowId(flow.id);
    // Naviguer — le useEffect prendra le relais apres le changement de pathname
    router.push(targetRoute);
  } else {
    // Meme page : polling direct sans navigation
    startFlowWithPolling(flow);
  }
};

// Etape 2 : Reagir au changement de route (useEffect survit au re-rendu)
useEffect(() => {
  if (!pendingFlowId) return;

  const flow = ALL_FLOWS.find(f => f.id === pendingFlowId);
  if (!flow) return;

  const targetRoute = flow.redirectRoute ?? flow.route;
  if (pathname !== targetRoute) return; // Pas encore sur la bonne page

  // La route correspond : DEMARRER LE POLLING (ne pas effacer pendingFlowId ici !)
  // Si on efface prev prematurement, le useEffect peut s'interrompre (Race Condition)
  startFlowWithPolling(flow);
}, [pathname, pendingFlowId]);

// Etape 3 : Polling robuste — attend que le 1er element soit present dans le DOM
const startFlowWithPolling = (flow: TutorialFlow) => {
  const firstStep = flow.steps[0];
  const selector = typeof firstStep?.element === 'string' ? firstStep.element : null;

  const MAX_ATTEMPTS = 30;  // 30 x 150ms = 4.5 secondes (couvre les compilations lentes)
  const INTERVAL_MS = 150;
  let attempts = 0;

  const intervalId = setInterval(() => {
    attempts++;
    const el = selector ? document.querySelector(selector) : document.body;

    if (el) {
      clearInterval(intervalId);
      setPendingFlowId(null); // FIX Iteration 6 : Effacer seulement quand on le trouve
      tourState.reset(flow.id);
      startFlow(flow, activeDriverRef, () => tourState.markComplete(flow.id));
    } else if (attempts >= MAX_ATTEMPTS) {
      clearInterval(intervalId);
      setPendingFlowId(null); // FIX Iteration 6 : Effacer en cas de timeout (eviter le blocage infini)
      // Element introuvable apres 4.5s : afficher une erreur discrete
      console.warn(`[tutorial-inject] Element "${selector}" introuvable apres ${MAX_ATTEMPTS} tentatives.`);
    }
  }, INTERVAL_MS);
};
```

**Pourquoi ce pattern fonctionne :**
1. `setPendingFlowId` stocke l'intention dans l'etat React — il survit au changement de route.
2. `router.push()` provoque un re-rendu, mais le `useEffect` surveille `pathname` et reagit proprement.
3. Le polling par `setInterval` (150ms x 30) attend le DOM reel, sans supposer un delai fixe.
4. `clearInterval` garantit qu'aucun timer zombie ne subsiste.

**Contrainte supplementaire :**
- Si le composant est demonte pendant le polling (ex : l'utilisateur navigue ailleurs),
  nettoyer l'intervalle dans le cleanup du `useEffect` ou de `startFlowWithPolling`.

```typescript
// Cleanup minimal a ajouter si le polling est lance dans un useEffect :
useEffect(() => {
  let intervalId: ReturnType<typeof setInterval> | null = null;
  // ... logique de polling utilisant intervalId ...
  return () => { if (intervalId) clearInterval(intervalId); };
}, [pathname]);
```

### 5.2bis Step Actions — Navigation UI Automatique (Task Walkthrough)

> **Probleme resolu** : Un element cible peut etre dans un modal, un drawer, ou un panneau
> qui n'est pas visible au chargement. Si le tutoriel tente de le highlighter sans d'abord
> l'exposer, l'highlight sera invisible ou plante.
>
> **Principe** : chaque step d'un Task Walkthrough peut declarer une action "entree" et une
> action "sortie". Le tutoriel les execute automatiquement — l'utilisateur n'a qu'a cliquer Suivant.

**Utilitaire `waitForElement` (OBLIGATOIRE pour tout enterAction)**

```typescript
// utils/waitForElement.ts — utilitaire reutilisable
// NE PAS utiliser setTimeout fixe — delai arbitraire qui echoue sur les connexions lentes
export const waitForElement = (selector: string, maxAttempts = 20): Promise<Element | null> => {
  return new Promise((resolve) => {
    let attempts = 0;
    const poll = setInterval(() => {
      const el = document.querySelector(selector);
      if (el || ++attempts >= maxAttempts) {
        clearInterval(poll);
        resolve(el ?? null);
      }
    }, 150); // 150ms x 20 = 3 secondes max
  });
};
```

**Pattern Step Actions dans tutorialFlows.ts**

```typescript
import type { DriveStep } from 'driver.js';
import { waitForElement } from '@/utils/waitForElement';

// Interface etendue pour les Task Walkthroughs
export interface TaskStep extends DriveStep {
  // Optionnel : action a executer AVANT d'afficher ce step
  enterAction?: () => Promise<void>;
  // Optionnel : action inverse a executer quand l'utilisateur clique PRECEDENT depuis ce step
  exitAction?: () => Promise<void>;
}

// Exemples :

// Step qui ouvre un modal avant de l'afficher
const openCreateModal: TaskStep = {
  element: '[data-tour-target="create-form-name"]',
  popover: {
    title: 'Nommez votre projet',
    description: 'Ce nom identifiera votre projet dans toute la plateforme.',
  },
  enterAction: async () => {
    // Cliquer le bouton qui ouvre le modal de creation
    document.querySelector<HTMLElement>('[data-tour-target="create-project-btn"]')?.click();
    // Attendre que le champ du formulaire soit dans le DOM
    await waitForElement('[data-tour-target="create-form-name"]');
  },
  exitAction: async () => {
    // Fermer le modal en cliquant le bouton de fermeture
    document.querySelector<HTMLElement>('[data-tour-target="modal-close-btn"]')?.click();
    // OU : appuyer sur Echap pour fermer
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
  },
};
```

**Integration dans `startFlow` — execution des Step Actions**

```typescript
// Modifier startFlow pour executer enterAction avant chaque step
// et exitAction lors du clic Precedent
const driverObj = driver({
  smoothScroll: true,           // INVARIANT 21
  popoverClass: 'driverjs-premium-popover',
  // ...autres options...
  onNextClick: async () => {
    const currentIndex = driverObj.getActiveIndex() ?? 0;
    const nextStep = flow.steps[currentIndex + 1] as TaskStep | undefined;
    if (nextStep?.enterAction) {
      await nextStep.enterAction();
    }
    driverObj.moveNext();
  },
  onPrevClick: async () => {
    const currentIndex = driverObj.getActiveIndex() ?? 0;
    const currentStep = flow.steps[currentIndex] as TaskStep | undefined;
    // Inverser l'etat UI du step courant avant de reculer
    if (currentStep?.exitAction) {
      await currentStep.exitAction();
    }
    driverObj.movePrevious();
  },
});
```

**Regles d'identification des Step Actions (en Phase 3)**

```
POUR CHAQUE ELEMENT CIBLE D'UN TASK WALKTHROUGH :

  L'element est-il visible dans le DOM au chargement de la page ?
  -> OUI -> Pas d'enterAction necessaire
  -> NON -> L'element est conditionnel (modal, drawer, tab, dropdown)
            DOIT declarer :
              enterAction : cliquer le declencheur + waitForElement
              exitAction  : fermer la source (close button, Escape, ou action inverse)

  L'element necessite-t-il une navigation vers une autre URL ?
  -> OUI -> Utiliser le pattern "Pending Flow" (section 5.2) + enterAction si modal supplementaire
  -> NON -> enterAction suffit

  Le step precedent a-t-il laisse un etat UI ouvert (modal, drawer) ?
  -> OUI -> exitAction du step courant DOIT fermer cet etat
  -> NON -> exitAction peut etre omise
```

**Checkpoint Step Actions (a inclure dans le Checkpoint Cibles Phase 3.6)**

```
FLOW : [nom_du_flow]
  Step 1 : [element] -> DOM natif : OUI / NON
    enterAction : [aucune / "click [selector] + waitForElement [selector]"]
    exitAction  : [aucune / "click [close-selector] ou Escape"]
  Step 2 : [element] -> DOM natif : [OUI/NON - si NON, le modal du step 1 l'expose]
    enterAction : [aucune - deja expose par step 1]
    exitAction  : [aucune - fermeture differee a la fin du flow ou au step suivant]
  ...
```

### 5.3 Help Hub — Menu Flottant Multimodal

Utiliser quand l'architecture Phase 0 = Help Hub (basique, complet, ou bimodal).

**Composant HelpHubMenu.tsx** — Structure obligatoire :

```tsx
"use client"; // Next.js uniquement

import { useState } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { ALL_FLOWS, type TutorialFlow } from '@/lib/tutorial-flows';

interface HelpHubMenuProps {
  onLaunchSlider?: () => void;        // Si architecture bimodale (slider + tutoriels)
  onLaunchFlow: (flow: TutorialFlow) => void;
}

export function HelpHubMenu({ onLaunchSlider, onLaunchFlow }: HelpHubMenuProps) {
  const [isOpen, setIsOpen] = useState(false);
  const pathname = usePathname();

  return (
    <>
      {/* Bouton FAB */}
      <button
        onClick={() => setIsOpen(!isOpen)}
        style={{ position: 'fixed', bottom: 24, right: 24, zIndex: 10001 }}
        aria-label="Centre d'aide"
      >
        ?
      </button>

      {/* Menu */}
      {isOpen && (
        <div style={{ position: 'fixed', bottom: 80, right: 24, zIndex: 10000 }}>

          {/* Section Slider (si architecture bimodale) */}
          {onLaunchSlider && (
            <section>
              <h3>Presentation</h3>
              <button onClick={() => { onLaunchSlider(); setIsOpen(false); }}>
                Revoir la presentation
              </button>
            </section>
          )}

          {/* Section Tutoriels */}
          <section>
            <h3>Tutoriels Interactifs</h3>
            {ALL_FLOWS.map((flow) => (
              <button
                key={flow.id}
                onClick={() => { onLaunchFlow(flow); setIsOpen(false); }}
              >
                {flow.label}
                {/* BADGE CONTEXTUEL : indiquer le tutoriel de la page actuelle */}
                {flow.route === pathname && (
                  <span aria-label="Tutoriel pour cette page">Ici</span>
                )}
              </button>
            ))}
          </section>

        </div>
      )}
    </>
  );
}
```

**Regles du Help Hub :**
- Le FAB et le menu sont TOUJOURS dans le layout global (jamais dans une page)
- Le badge contextuel ("Ici") compare `flow.route` avec `pathname` du router
- Les tutoriels d'autres pages redirigent automatiquement via `launchFlowWithRedirect`
- Si architecture bimodale : "Revoir la presentation" relance le slider SANS lancer de tutoriel apres
- Fermeture du menu : clic sur un item, ou clic en dehors du menu

### 5.4 Slider d'Onboarding Web (Framer Motion)

Utiliser pour les presentations marketing avant le tutoriel guide (architecture bimodale).

```tsx
// components/OnboardingSlider.tsx
"use client";

import { useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';

// INVARIANT ICONES : si les slides contiennent une icone React (lucide-react, heroicons, etc.),
// stocker la REFERENCE du composant (pas une instance JSX) dans le tableau de slides,
// puis l'instancier dans le rendu avec une variable intermediaire.
// ✅ CORRECT :
//   interface SlideData { icon: React.ComponentType<{ className?: string }> }
//   const slides = [{ icon: LayoutDashboard, ... }]
//   // Dans le rendu :
//   const IconComponent = slide.icon;
//   <IconComponent className="w-8 h-8" />
// ❌ INCORRECT :
//   const slides = [{ icon: <LayoutDashboard />, ... }]  // instance JSX — ne peut pas etre re-rendu
//   <slide.icon />  // TypeScript error si icon est React.ReactNode

interface SlideData {
  title: string;
  description: string;
  icon?: React.ComponentType<{ className?: string }>; // reference, pas instance JSX
  illustration?: React.ReactNode;                      // ReactNode uniquement si pas de composant icone
}

interface OnboardingSliderProps {
  slides: SlideData[];
  onComplete: () => void;    // Appele quand l'utilisateur termine ou skippe
  onLaunchTutorial?: () => void; // Optionnel : bouton "Commencer le tutoriel" sur derniere slide
}

export function OnboardingSlider({ slides, onComplete, onLaunchTutorial }: OnboardingSliderProps) {
  const [currentIndex, setCurrentIndex] = useState(0);
  const isLast = currentIndex === slides.length - 1;

  return (
    <div role="dialog" aria-modal="true" aria-label="Presentation du produit">
      <AnimatePresence mode="wait">
        <motion.div
          key={currentIndex}
          initial={{ opacity: 0, x: 50 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: -50 }}
        >
          {/* INVARIANT ICONES : variable intermediaire obligatoire pour les composants icone */}
          {slides[currentIndex].icon && (() => {
            const IconComponent = slides[currentIndex].icon!;
            return <IconComponent className="w-12 h-12" />;
          })()}
          {slides[currentIndex].illustration}
          <h2 id="slider-title">{slides[currentIndex].title}</h2>
          <p>{slides[currentIndex].description}</p>
        </motion.div>
      </AnimatePresence>

      {/* Indicateur de progression */}
      <div aria-label={`Slide ${currentIndex + 1} sur ${slides.length}`}>
        {slides.map((_, i) => (
          <span key={i} aria-current={i === currentIndex} />
        ))}
      </div>

      {/* Navigation */}
      <div>
        <button onClick={onComplete}>Passer</button>
        {currentIndex > 0 && (
          <button onClick={() => setCurrentIndex(i => i - 1)}>Precedent</button>
        )}
        {isLast ? (
          <>
            {onLaunchTutorial && (
              <button onClick={() => { onComplete(); onLaunchTutorial(); }}>
                Commencer le tutoriel
              </button>
            )}
            <button onClick={onComplete}>Terminer</button>
          </>
        ) : (
          <button onClick={() => setCurrentIndex(i => i + 1)}>Suivant</button>
        )}
      </div>
    </div>
  );
}
```

### 5.5 Slider d'Onboarding Mobile (React Native — FlatList)

**Pattern de reference** : FlatList paginee, 0 dependance native.

Structure obligatoire :
```typescript
// FlatList avec ces props obligatoires :
horizontal
pagingEnabled
scrollEnabled={false}  // Navigation via boutons uniquement
showsHorizontalScrollIndicator={false}
```

Etat minimal :
```typescript
const [currentIndex, setCurrentIndex] = useState(0);
const flatListRef = useRef<FlatList>(null);
// Navigation : flatListRef.current?.scrollToIndex({ index: nextIndex, animated: true })
```

Si AsyncStorage est utilise dans le fichier existant -> remplacer par `tutorialStorage` (MMKV) :
```typescript
// AVANT (a supprimer) :
await AsyncStorage.setItem('hasSeenOnboarding', 'true');

// APRES :
tutorialStorage.markComplete('onboarding');
```

### 5.6 TutorialProvider — Orchestrateur Central

Pour les architectures Help Hub ou Auto-Tour, centraliser la logique dans un Provider.

**INVARIANT 20 : Toujours creer 2 fichiers distincts** :

```tsx
// ========================================================
// FICHIER 1 : contexts/TutorialContext.tsx
// Exporte uniquement le contexte + l'interface + le hook
// ========================================================
"use client";

import { createContext, useContext } from 'react';
import type { TutorialFlow } from '@/lib/tutorial-flows';

export interface TutorialContextValue {
  launchFlow: (flow: TutorialFlow) => void;
  showSlider: boolean;
  setShowSlider: (v: boolean) => void;
}

export const TutorialContext = createContext<TutorialContextValue | null>(null);

export const useTutorial = () => {
  const ctx = useContext(TutorialContext);
  if (!ctx) throw new Error('useTutorial doit etre utilise dans TutorialProvider');
  return ctx;
};
```

```tsx
// ========================================================
// FICHIER 2 : components/TutorialProvider.tsx
// Importe TutorialContext depuis contexts/TutorialContext.tsx
// NE PAS re-exporter useTutorial depuis ce fichier
// ========================================================
// components/TutorialProvider.tsx
"use client"; // INVARIANT 9 — Next.js uniquement

import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import { useTourState } from '@/hooks/use-tour-state';
import { startFlow, launchFlowWithRedirect } from '@/lib/tutorial-service';
import { driver } from 'driver.js';
import type { TutorialFlow } from '@/lib/tutorial-flows';
// INVARIANT 20 : importer le contexte depuis son fichier dedie
import { TutorialContext } from '@/contexts/TutorialContext';

export function TutorialProvider({ children }: { children: React.ReactNode }) {
  const activeDriverRef = useRef<ReturnType<typeof driver> | null>(null);
  const [showSlider, setShowSlider] = useState(false);
  // INVARIANT 19 : garde SSR — ne jamais lire localStorage hors useEffect dans Next.js
  const [isMounted, setIsMounted] = useState(false);
  useEffect(() => { setIsMounted(true); }, []);

  const tourState = useTourState();
  const router = useRouter();
  const pathname = usePathname();

  // Auto-launch : surveiller les changements de route
  // Remplacer '/my-page' et 'myPageFlow' par les routes et flows reels du projet
  useEffect(() => {
    if (pathname === '/my-page' && !tourState.isCompleted('my-page')) {
      const timer = setTimeout(() => {
        import('@/lib/tutorial-flows').then(({ myPageFlow }) => {
          startFlow(myPageFlow, activeDriverRef, () => tourState.markComplete('my-page'));
        });
      }, 500); // Delai pour laisser le DOM se stabiliser
      return () => clearTimeout(timer);
    }
  }, [pathname]);

  // Cleanup a la destruction du Provider
  useEffect(() => {
    return () => {
      if (activeDriverRef.current) {
        activeDriverRef.current.destroy();
        activeDriverRef.current = null;
      }
    };
  }, []);

  // INVARIANT 18 : callbacks stables — ne pas recreer a chaque rendu
  const launchFlow = useCallback((flow: TutorialFlow) => {
    launchFlowWithRedirect(flow, pathname, router, activeDriverRef, tourState);
  }, [pathname, router, tourState]);

  const handleSetShowSlider = useCallback((v: boolean) => setShowSlider(v), []);

  // INVARIANT 18 : valeur de contexte memoizee — evite de re-rendre tout l'arbre
  const contextValue = useMemo(
    () => ({ launchFlow, showSlider, setShowSlider: handleSetShowSlider }),
    [launchFlow, showSlider, handleSetShowSlider],
  );

  // INVARIANT 19 : ne pas rendre les composants CSR avant le montage cote client
  if (!isMounted) return <>{children}</>;

  return (
    <TutorialContext.Provider value={contextValue}>
      {children}
      {/* INVARIANT 18 : HelpHubMenu recoit launchFlow en prop — il NE DOIT PAS appeler useTutorial() */}
      <HelpHubMenu onLaunchFlow={launchFlow} onLaunchSlider={() => handleSetShowSlider(true)} />
    </TutorialContext.Provider>
  );
}

// INVARIANT 20 : useTutorial est dans contexts/TutorialContext.tsx — NE PAS le re-declarer ici.
// Les composants consommateurs importent depuis '@/contexts/TutorialContext', pas depuis ce fichier.
```

### 5.7 Bouton d'Acces Flottant (FAB Simple)

Utiliser seulement si architecture = FAB Simple (1 seul tutoriel, Phase 0).
Pour multi-tutoriels, utiliser HelpHubMenu (section 5.3).

**Obligation** : Injecter dans le composant de layout global (pas dans une page specifique).

**Web :**
```tsx
<button
  onClick={() => { tutorialState.reset(flowId); startFlow(mon_flow, activeDriverRef); }}
  style={{ position: 'fixed', bottom: 24, right: 24, zIndex: 10001,
           borderRadius: '50%', width: 48, height: 48 }}
  aria-label="Relancer le tutoriel"
>?</button>
```

**React Native :**
```tsx
<TouchableOpacity
  style={{ position: 'absolute', bottom: 24, right: 24, zIndex: 999 }}
  onPress={() => { tutorialStorage.reset('onboarding'); setShowOnboarding(true); }}
  accessibilityLabel="Relancer le tutoriel"
>
  <Text>?</Text>
</TouchableOpacity>
```

### 5.8 Accessibilite (obligatoire)

Pour toute modale ou popover injecte :
- `role="dialog"` sur le conteneur
- `aria-modal="true"`
- `aria-labelledby` pointant vers le titre de l'etape
- **Focus trap** : le focus clavier reste dans la modale pendant le tutoriel (Tab et Shift+Tab cyclent dans la modale uniquement)
- `z-index > 10000` pour les popovers web

### 5.9 Configuration Next.js

Tout composant Provider injecte dans `app/layout.tsx` DOIT :
1. Etre dans un fichier separe marque `"use client"` en premiere ligne
2. Etre importe dans `layout.tsx` (qui reste un Server Component)

```tsx
// tutorial-provider.tsx
"use client";
// ... logique de tutoriel

// app/layout.tsx — reste un Server Component
import { TutorialProvider } from './tutorial-provider';
// ... <TutorialProvider>{children}</TutorialProvider>
```

---

## PHASE 6 — Validation et Audit

> LOI D'AIRAIN : Aucune declaration de succes sans preuve fraiche executee dans ce message.

### 6.1 Sequence de Validation Obligatoire

```
ETAPE 1 — Verification du typage statique
  Executer : tsc --noEmit (ou npm run typecheck si disponible)
  Resultat attendu : 0 erreurs TypeScript
  Resultat obtenu : [afficher la sortie reelle]
  SI erreurs -> CORRIGER AVANT DE CONTINUER. Ne pas declarer succes.

ETAPE 2 — Formatage
  Executer : prettier --write [fichiers modifies] (si Prettier configure)
  SI pas de Prettier -> ignorer cette etape

ETAPE 3 — Build de production
  Executer : npm run build (ou npx expo export si RN)
  Resultat attendu : build reussi, 0 erreurs fatales
  Resultat obtenu : [afficher la sortie reelle]
  SI echec -> IDENTIFIER la cause. CORRIGER. Relancer.

ETAPE 4 — Verification des attributs injectes
  Pour chaque data-tour-target injecte :
    -> Verifier que l'attribut est UNIQUE GLOBALEMENT (grep dans tout le codebase)
    -> Verifier que l'element est rendu cote client (pas un Server Component)
    -> Pour multi-pages : verifier le prefixe de route (ex: "workflows-step-X" != "dashboard-step-X")

ETAPE 5 — Verification anti-StrictMode (si tutoriel web)
  -> Verifier que activeDriverRef est utilise dans startFlow
  -> Verifier que useEffect retourne une fonction de cleanup qui appelle destroy()
  -> Verifier qu'aucune instance n'est creee sans verifier activeDriverRef.current d'abord

ETAPE 6 — Verification multi-tutoriels (si Help Hub)
  -> Verifier que chaque flow a un id unique
  -> Verifier que ALL_FLOWS contient tous les flows
  -> Verifier que HelpHubMenu est dans le layout global, pas dans une page
  -> Verifier que launchFlowWithRedirect gere le cas currentPath === targetRoute
```

### 6.2 Anti-Patterns a Detecter (Posture Adversariale)

L'agent DOIT activement chercher ces violations dans son propre travail :

| Violation | Symptome | Correction |
|---|---|---|
| Doublon de systeme | `OnboardingSlider2.tsx` cree alors que `OnboardingSlider.tsx` existe | Supprimer le doublon, etendre l'existant |
| Selecteur CSS comme cible | `element: '.sidebar-nav'` | Remplacer par `data-tour-target` |
| AsyncStorage residuel (RN) | `import AsyncStorage` dans un fichier de tutoriel | Remplacer par MMKV |
| Provider non-client (Next.js) | Erreur "useState in Server Component" | Ajouter "use client" |
| Plus de 5 etapes | `steps.length > 5` | Elaguer aux 4 etapes les plus critiques |
| Texte descriptif visuel | "Cliquez sur" dans une description | Reformuler avec le benefice |
| FAB dans une page | `position: fixed` dans `Dashboard.tsx` | Deplacer dans `Layout.tsx` |
| Z-index insuffisant | `zIndex: 100` sur un popover | Monter a > 10000 |
| Backdrop hors fourchette | `overlayOpacity: 0.9` | Ramener entre 0.50 et 0.75 |
| Pas de cleanup StrictMode | `driver()` dans useEffect sans return cleanup | Ajouter `return () => instance.destroy()` |
| Collisions data-tour-target | `data-tour-target="step-sidebar"` sur 2 pages | Prefixer par route : `dashboard-step-sidebar` |
| Style par defaut Driver.js | Pas de `popoverClass` dans la config driver | Ajouter `driverjs-premium-popover` + CSS |
| CSS hardcodee non-generique | `.driverjs-premium-popover { background: #1e293b }` sans extraction palette | Revenir Phase 1.4, extraire palette, substituer placeholders |
| Flow a etape unique | `steps.length === 1` avec titre generique "Explorez" | Revenir Phase 3.0, scanner les composants reels, generer 3 etapes min |
| Flows sans scan prealable | Flows generes sans inventaire des vues (Phase 3.0) | Lire chaque fichier de vue avant de generer les steps |
| HelpHubMenu dans une page | `HelpHubMenu` importe dans `Dashboard.tsx` | Deplacer dans `Layout.tsx` |
| Navigation inter-routes sans attente | `router.push(); driver.drive()` sans await | Utiliser le Pattern "Pending Flow" (section 5.2) |
| setTimeout apres router.push | `router.push(route); setTimeout(() => startFlow(...), 800)` | Race condition garantie — utiliser Pattern "Pending Flow" |
| Delai fixe pour attendre le DOM | `await new Promise(resolve => setTimeout(resolve, 500))` apres navigation | Remplacer par polling setInterval (150ms x 30) sur presence DOM |
| CSS `var(--x/opacity)` invalide | `box-shadow: 0 4px 6px var(--slate-900/10)` | Invalide en CSS standard — utiliser `rgba()` ou `var(--token)` seul (INVARIANT 17) |
| Context value non-memoize | `<Ctx.Provider value={{ launchFlow, showSlider }}>` sans `useMemo` | Re-render de tout l'arbre a chaque rendu du Provider — envelopper avec `useMemo` (INVARIANT 18) |
| HelpHubMenu appelle useTutorial | `const { launchFlow } = useTutorial()` dans HelpHubMenu rendu par le Provider | Boucle de dependance Context — passer `launchFlow` en prop depuis le Provider (INVARIANT 18) |
| localStorage lu au rendu initial | `const [done] = useState(() => localStorage.getItem('x'))` | Hydration mismatch SSR — proteger avec `isMounted` ou `typeof window` (INVARIANT 19) |
| Composant racine sans SSR guard | `HelpHubMenu` ou `OnboardingSlider` rendus sans garde `isMounted` dans Next.js | Lag ou erreur d'hydratation — utiliser `useEffect` + `isMounted` (INVARIANT 19) |
| N flows confirmes, 1 seul fichier modifie | Phase 0 Q1 = 3 flows, un seul fichier avec `data-tour-target` | Scan multi-pages incomplet — appliquer ASSERTION MULTI-PAGES (section 3.1) |
| TutorialContext dans TutorialProvider | `createContext` + `useTutorial` declares dans `TutorialProvider.tsx` alors que `HelpHubMenu` ou `OnboardingSlider` y sont aussi rendus | Import circulaire garanti — separer en `contexts/TutorialContext.tsx` (INVARIANT 20) |
| Instance JSX stockee comme icone | `slides = [{ icon: <LayoutDashboard /> }]` puis `<slide.icon />` | TypeScript error — stocker la reference `{ icon: LayoutDashboard }` et rendre avec `const I = slide.icon; <I />` |

**Si >= 1 violation detectee** : Corriger et relancer la validation (Etapes 1 a 3) avant de declarer termine.

### 6.3 Rapport de Livraison (obligatoire)

```
RAPPORT D'INJECTION — tutorial-inject
  Framework          : [detecte en Phase 1]
  Architecture       : [FAB Simple / Auto-Tour / Help Hub basique / complet / bimodal]
  Lib principale     : [driver.js / onborda / FlatList native / ...]
  Lib slider         : [framer-motion / FlatList native / Aucune]
  Dependances ajout. : [liste ou "Aucune"]

  Fichiers modifies  :
    - [chemin] -> [nature de la modification]
    - ...

  Flows generes      :
    Flow 1 : [id] (route: /[chemin])
      1. [data-tour-target="[prefixe]-step-X"] -> "[Titre]"
      2. ...
    [Repeter pour chaque flow]

  Slider             : [Oui — [N] slides / Non]
  Help Hub           : [Oui, dans [fichier] / Non]
  Badge contextuel   : [Oui / Non]
  Auto-launch        : [Oui, sur route /[chemin] / Non]
  Navigation inter-routes : [Oui, launchFlowWithRedirect / Non]

  Persistance        : [localStorage keys: liste / MMKV keys: liste]
  Styling premium    : [popoverClass: driverjs-premium-popover dans globals.css / Non applicable]
  StrictMode cleanup : [activeDriverRef + destroy() en place / Non applicable]

  Validation         : tsc -> [0 erreurs] / build -> [OK]
  Violations corrigees : [liste ou "Aucune"]
  Pour modifier les textes : [chemin exact du fichier de flows]
  Pour modifier les slides  : [chemin exact du composant slider]
```

---

## Red Flags — STOP IMMEDIAT

L'agent DOIT s'arreter si l'un de ces signaux est detecte dans son propre comportement :

- Ecrire du code dans un fichier qui n'a pas ete lu au prealable
- Utiliser "devrait", "probablement", "normalement" dans un rapport de validation
- Declarer "Build reussi" sans avoir execute la commande et affiche sa sortie
- Creer un second systeme de tutoriel alors qu'un premier existe
- Atteindre 9 etapes ou plus dans un flow (au-dela de 8 : elaguer ou scinder en deux flows)
- Generer un flow avec moins de 3 etapes (etape unique = echec, revenir au scan 3.0)
- Generer une etape avec un titre generique : "Explorez", "Decouvrez", "Bienvenue" sans contexte specifique
- Utiliser une classe CSS comme selecteur Driver.js
- Appeler AsyncStorage dans du code React Native de persistance tutoriel
- Commencer a coder sans avoir fait la Phase 0 (questions d'architecture)
- Creer une instance driver() sans activeDriverRef et sans cleanup useEffect
- Passer a la Phase 1 sans connaitre le nombre de tutoriels prevus
- Utiliser driver() sans definir `popoverClass` avec une classe CSS premium
- Ecrire du CSS pour `.driverjs-premium-popover` avec des couleurs hardcodees (slate, orange, etc.)
  sans avoir d'abord extrait la palette du projet en Phase 1.4
- Generer des flows sans avoir d'abord scan les vues/composants reels du projet (Phase 3.0)
- Utiliser `setTimeout` apres `router.push()` pour demarrer un tutoriel inter-pages (boucle infinie garantie dans les SPA/Next.js App Router — Pattern "Pending Flow" obligatoire, section 5.2)
- Utiliser un `await new Promise(resolve => setTimeout(...))` comme delai fixe apres une navigation (delai arbitraire qui echoue sur les compilations lentes — remplacer par polling DOM setInterval)
- Ecrire `var(--token/opacity)` dans un fichier CSS (syntaxe invalide rejetee par PostCSS — utiliser `rgba()` — INVARIANT 17)
- Passer la valeur de contexte a `.Provider` sans `useMemo` dans un TutorialProvider (INVARIANT 18)
- Faire appeler `useTutorial()` par un composant qui est lui-meme rendu par le TutorialProvider dans son JSX (boucle de rendu Context — INVARIANT 18)
- Lire `localStorage` ou `window` directement dans le state initial ou le corps de rendu d'un composant Next.js (hydration mismatch — INVARIANT 19)
- Terminer l'injection avec moins de fichiers ayant des `data-tour-target` que de flows confirmes en Phase 0 (scan multi-pages incomplet)
- Declarer `TutorialContext` et `useTutorial` dans le meme fichier que `TutorialProvider` quand ce Provider rend des composants UI (`HelpHubMenu`, `OnboardingSlider`) qui importent ce hook — import circulaire garanti (INVARIANT 20)
- Scanner uniquement le layout ou la page d'accueil alors que Phase 0 Q1 confirme N >= 2 flows — les pages secondaires manqueront de `data-tour-target` (Phase 3.0 gate)
- Initialiser `driver()` sans `smoothScroll: true` — elements hors viewport highlights invisibles (INVARIANT 21)
- Generer des flows sans liste de procedures definie par le developpeur — le skill ne decide pas du contenu
- Auto-decouvrir les procedures depuis le code sans validation explicite du developpeur (Phase 0 Q1 bloquante)
- Generer un UI Tour quand le developpeur a demande un Task Walkthrough (Q6 = a) — INVARIANT 16 de paradigme
- Generer des titres de steps Task Walkthrough sans verbe d'action — cf. liste de verbes valides (section 3.3bis)
- Highligter un element conditionnel (dans un modal/drawer) sans declarer `enterAction` — l'element sera hors DOM
- Declarer un bouton Precedent generique qui ignore l'`exitAction` du step courant (INVARIANT 22)
- Utiliser `setTimeout` fixe dans un `enterAction` pour attendre qu'un element apparaisse — utiliser `waitForElement` (polling) a la place

---

## Contexte des Projets Existants

### LOTO (Web — React + Vite)
- Lib : Driver.js (deja installee)
- Flows : `services/tutorialFlows.ts` — interface `TutorialFlow { id, route?, redirectRoute?, steps[] }`
- Persistance : `services/tutorialService.ts` — `fetchProgress()` / `updateProgress()` via authService
- **Pour ajouter un flow** : (1) Creer un export dans `tutorialFlows.ts`, (2) L'enregistrer dans `tutorialService`, (3) Ajouter les `data-tour-target` dans les composants cibles
- i18n : Les textes des popover sont en francais (fr) et anglais (en) dans les flows

### K2 Mobile (React Native — Expo)
- Slider : `components/OnboardingSlider.tsx` — FlatList paginee, pagingEnabled, scrollEnabled=false
- Illustrations : `components/OnboardingIllustrations.tsx` — composants SVG/icones par slide
- Persistance actuelle : AsyncStorage -> **DOIT etre migre vers MMKV** des la prochaine intervention
- Styling : NativeWind (classes Tailwind)
- i18n : Systeme i18n integre — ajouter les cles dans les fichiers de traduction existants avant d'ecrire les textes en dur

### n8n-observability dashboard-ui (Next.js App Router)
- Framework : Next.js App Router
- Point d'entree : `app/layout.tsx`
- Lib tutoriel : driver.js (installee)
- Framer Motion : presente
- Context : `contexts/TutorialContext.tsx` — `TutorialContext`, `TutorialContextValue`, `useTutorial` (INVARIANT 20)
- Provider : `components/TutorialProvider.tsx` ("use client") — importe depuis `contexts/TutorialContext.tsx`
- Help Hub : `components/HelpHubMenu.tsx`
- Slider : `components/OnboardingSlider.tsx` (Framer Motion)
- FAB : `components/TutorialFAB.tsx`
- Flows : `lib/tutorial-flows.tsx` — exports: dashboardFlow, workflowsFlow, executionsFlow, ALL_FLOWS
- Persistance : `hooks/use-tour-state.ts` — localStorage, cles independantes par flowId
- Styling premium : classe `driverjs-premium-popover` dans `app/globals.css`
- Architecture : Help Hub bimodal (slider + 3 tutoriels)
- Auto-launch : Oui, sur premiere visite de chaque page

---

## Regles Critiques (Recapitulatif)

| Regle | Valeur |
|---|---|
| Z-index popovers | > 10 000 |
| Opacite backdrop | 0.50 - 0.75 |
| Nb etapes min PAR FLOW | **3 minimum** (flow a 1 etape = echec) |
| Nb etapes max PAR FLOW | 5 maximum |
| Structure des etapes | Orientation -> Donnees -> Filtres -> CTA -> Feature avancee |
| Persistance web | `localStorage` (ou tutorialService existant) |
| Persistance RN | **MMKV** (jamais AsyncStorage) |
| Ciblage web | `data-tour-target` (jamais classes CSS) |
| Ciblage multi-pages | Prefixe obligatoire par route |
| Lib interdite | Intro.js (AGPL) |
| Next.js Provider | Toujours `"use client"` |
| Langue | Heritee du projet, bilingue si systeme i18n present |
| Modification de code | Lire d'abord, ecrire ensuite — toujours |
| StrictMode Driver.js | activeDriverRef + cleanup destroy() — toujours |
| Styling Driver.js | popoverClass custom + palette extraite du projet — toujours |
| Couleurs CSS popover | Extraites du projet en Phase 1.4 — jamais hardcodees |
| Syntaxe CSS | Jamais `var(--x/opacity)` — utiliser `rgba()` ou `var(--token)` seul |
| Context React stable | `useMemo` sur la valeur de contexte + `useCallback` sur les callbacks — toujours |
| HelpHubMenu props | Recoit les callbacks en props du Provider — ne JAMAIS appeler `useTutorial()` en interne |
| SSR guard Next.js | `isMounted` + `useEffect` avant tout acces `localStorage`/`window` — toujours |
| Couverture multi-pages | Nb fichiers avec `data-tour-target` >= nb flows confirmes en Phase 0 |
| Scan des vues | Inventaire complet Phase 3.0 avant tout flow — toujours |
| Phase 0 | Obligatoire avant tout code |
| Invariant Help Hub | FAB/Menu dans layout global, jamais dans une page |
| Separation Context/Provider | `TutorialContext` + `useTutorial` dans `contexts/TutorialContext.tsx`, `TutorialProvider` dans `components/TutorialProvider.tsx` — toujours separes quand le Provider rend des composants UI |
| Procedures | Toujours definies par le developpeur (Phase 0 Q1) — jamais auto-decouvertes |
| Paradigme par defaut | Task Walkthrough (Q6 = a) — UI Tour uniquement si explicitement demande |
| Titres Task Walkthrough | Verbe d'action obligatoire (creer, configurer, lancer, valider...) |
| smoothScroll Driver.js | `smoothScroll: true` obligatoire dans `driver()` — INVARIANT 21 |
| Elements conditionnels | `enterAction` + `exitAction` obligatoires si l'element n'est pas dans le DOM natif |
| Bouton Precedent | Doit executer `exitAction` du step courant avant de reculer — INVARIANT 22 |
| waitForElement | Polling 150ms obligatoire pour tout `enterAction` — jamais `setTimeout` fixe |
