Module 9 · Architecture de l'app

La carte du projet : où vit quoi

Avant de plonger dans les écrans et l'état, il te faut une carte mentale. Quand tu cherches « où est le code qui parle à la base de données ? », tu dois pouvoir ouvrir le bon dossier sans réfléchir. Ce module est cette carte : on va apprendre non seulement quel dossier contient quoi, mais surtout pourquoi le projet est découpé ainsi — parce que c'est ce « pourquoi » qui te permettra de deviner où vit du code que tu n'as encore jamais vu.

💡 Le concept : un projet est une ville, pas une pièce

Imagine une grande ville. Les boulangeries sont dans un quartier, les garages dans un autre, les écoles ailleurs. Personne n'a écrit de loi disant « tu ne peux pas mettre une école au-dessus d'un garage » — mais l'organisation rend la ville lisible : tu sais à peu près où chercher. Un projet logiciel bien rangé fonctionne pareil. Chaque dossier est un quartier avec une vocation, et une fois la carte en tête, tu trouves ton chemin sans demander à personne. La suite de ce module te donne cette carte, quartier par quartier, plus les règles de circulation qui font tenir l'ensemble.

La séparation des responsabilités : pourquoi on ne mélange pas tout

Le principe qui structure tout le projet porte un nom : la séparation des responsabilités (en anglais, separation of concerns). L'idée tient en une phrase : chaque morceau de code ne doit avoir qu'une seule raison d'exister. Un fichier qui affiche un bouton ne devrait pas, en plus, calculer ta moyenne de soulevé, écrire dans la base de données et vérifier ton mot de passe. Ce sont quatre responsabilités différentes ; on les met à quatre endroits différents.

Pourquoi se donner cette peine, plutôt que de tout écrire d'un bloc « là où ça marche » ? Parce que le code que tu écris une fois, tu le relis dix fois, tu le modifies cinq fois, et tu le débogues à 23 h quand quelque chose casse. La séparation des responsabilités optimise pour ces moments-là, pas pour le moment de l'écriture. Voici concrètement ce qu'elle te rapporte :

  • La lisibilité. Quand un fichier ne fait qu'une chose, tu comprends son rôle en quelques secondes. Tu n'as pas à démêler « est-ce que ce bloc affiche, ou est-ce qu'il calcule ? ». C'est exactement l'objectif de la méthode de lecture vue dans le module sur la méthode pour lire un fichier inconnu : un fichier à responsabilité unique se laisse lire.
  • La testabilité. Une fonction qui calcule, et rien d'autre, se teste sans écran, sans base de données, sans réseau : tu lui donnes une entrée, tu vérifies sa sortie. Si la même fonction était noyée dans un écran, il faudrait lancer toute l'app pour la tester.
  • La remplaçabilité. Tu peux changer une couche sans casser les autres. Si demain le projet remplace sa base de données locale par une autre, seule la couche services/ bouge ; les écrans, eux, ne s'en aperçoivent même pas, car ils n'ont jamais parlé à la base directement.
  • Le travail en parallèle. Deux personnes peuvent travailler en même temps, l'une sur l'apparence, l'autre sur l'accès aux données, sans se marcher dessus, parce que ces choses vivent dans des fichiers séparés.
💡 L'analogie : la cuisine d'un restaurant

Dans une cuisine de restaurant sérieuse, il y a des postes : le garde-manger prépare les entrées froides, le saucier les sauces, le pâtissier les desserts, le plongeur lave. Personne ne fait tout. Pourquoi ? Parce qu'un cuisinier qui devrait à la fois dresser une assiette, surveiller un bouillon et laver les casseroles serait lent, stressé et source d'erreurs. En séparant les postes, on gagne en vitesse, en qualité, et on peut remplacer le saucier malade sans fermer le restaurant. Une architecture logicielle, c'est ça : des postes qui se passent le travail. L'écran « passe commande », le hook « organise », le service « va chercher en réserve ». Chacun reste bon dans son rôle.

On pourrait aussi parler d'une chaîne d'usine, image que tu connais bien : à chaque poste de la chaîne, une opération précise est faite, puis la pièce avance au poste suivant. Aucun poste ne fait tout le travail tout seul, et si un poste tombe en panne, on sait exactement lequel réparer sans démonter toute la chaîne. C'est précisément la promesse de la séparation des responsabilités : quand un bug apparaît, tu sais dans quel « poste » regarder.

Vue d'ensemble : les dossiers de l'app mobile

Tout ce qui t'intéresse vit sous apps/mobile/src/. Voici la carte complète, dossier par dossier. Lis-la une fois en entier pour avoir la vue d'ensemble ; on reprend ensuite chaque dossier en détail dans les sections qui suivent.

apps/mobile/src/
├── app/         ← Les ÉCRANS. Chaque fichier = une route.
├── components/  ← Les briques d'interface réutilisables (boutons, cartes…).
│   └── ui/      ← Le "design system" : Text, Button, Input, Card…
├── hooks/       ← La LOGIQUE réutilisable (use…). Le pont écran ↔ données.
├── services/    ← L'accès au monde extérieur : base de données, serveur, auth.
│   ├── database/  ← La base locale (WatermelonDB)
│   ├── supabase/  ← Le client serveur
│   └── auth/      ← Connexion / inscription
├── stores/      ← L'ÉTAT GLOBAL partagé entre écrans (Zustand).
├── utils/       ← Fonctions PURES : calculs, formatage, validation.
├── lib/         ← Petits utilitaires techniques (ex. la fonction cn).
└── constants/   ← Valeurs fixes : couleurs, tailles, limites.

La carte au complet. Chaque flèche te dit le rôle du dossier — et le rôle, c'est ce qui te dira où chercher.

🧭 Lis cette carte comme une table des matières

Tu n'as pas besoin de retenir le contenu de chaque dossier par cœur. Retiens d'abord les rôles : « afficher », « orchestrer », « accéder aux données », « garder l'état », « calculer ». Une fois ces cinq verbes en tête, la carte se reconstruit toute seule. Le détail des fichiers, lui, s'apprend en lisant — c'est tout l'objet du module sur la méthode pour lire un fichier inconnu.

Chaque dossier en détail : rôle, contenu, contre-exemples

On reprend maintenant chaque quartier. Pour chacun : son rôle (en une phrase), ce que tu y trouves, un exemple concret, et surtout ce qui n'a rien à y faire — car connaître la frontière d'un dossier est aussi utile que connaître son centre.

app/ — les écrans

Rôle : afficher des pages et définir la navigation. Tu y trouves : un fichier par route, plus des fichiers _layout.tsx qui décrivent comment les écrans s'emboîtent (onglets, piles, modales). Exemple : l'écran de la liste des entraînements, l'écran de connexion, l'onglet « Profil ». Ce qui n'a rien à y faire : du calcul métier lourd, des requêtes directes à la base, des règles de validation. Un écran orchestre et affiche ; il ne fabrique pas les données. On détaille tout le système de routes dans le module sur la navigation avec Expo Router.

components/ — les briques d'interface

Rôle : fournir des morceaux d'interface réutilisables, qu'on assemble dans les écrans. Tu y trouves : des cartes d'entraînement, des en-têtes, des listes, et surtout le sous-dossier components/ui/ qui est le design system : les briques de base (Text, Button, Input, Card). Exemple : un <Button variant="primary"> que dix écrans réutilisent. Ce qui n'a rien à y faire : aller chercher des données dans la base. Un composant reçoit ce qu'il doit afficher par ses props ; il ne va pas se servir tout seul. La construction de ces composants et de leurs variantes est le sujet du module sur les composants et le style avec NativeWind et CVA.

hooks/ — la logique réutilisable

Rôle : être le pont entre les écrans et les données. C'est ici que vit la logique qu'on veut partager entre plusieurs écrans. Tu y trouves : des fonctions dont le nom commence par use… (useActiveWorkout, useAuth), conformément aux règles vues dans le module sur useState et useEffect. Exemple : useActiveWorkout() appelle le service, prépare les données et les rend prêtes à afficher pour l'écran. Ce qui n'a rien à y faire : du JSX (de l'affichage) — un hook ne dessine rien, il prépare. Et il n'écrit pas non plus le SQL brut : il appelle un service pour ça.

services/ — l'accès au monde extérieur

Rôle : parler à tout ce qui est « dehors » : la base de données locale, le serveur, l'authentification. C'est la seule couche autorisée à le faire. Tu y trouves : services/database/ (la base locale), services/supabase/ (le client serveur) et services/auth/ (connexion, inscription). Exemple : getWorkoutWithDetails(id) qui lit un entraînement et ses séries depuis la base. Ce qui n'a rien à y faire : de l'affichage, et la connaissance de qui l'appelle. Un service ne sait pas s'il est appelé depuis l'écran A ou l'écran B ; il rend juste un service, comme une réserve qui livre sans savoir à quelle table ira le plat.

stores/ — l'état global

Rôle : garder l'état qui doit être partagé entre plusieurs écrans, hors de l'arbre des composants. Tu y trouves : des « stores » (avec Zustand) — par exemple l'utilisateur connecté, ou les préférences. Exemple : un useAuthStore que n'importe quel écran peut interroger pour savoir qui est connecté, sans qu'on ait à faire « descendre » l'info de props en props. Ce qui n'a rien à y faire : de l'état purement local à un seul écran (ça, c'est useState dans l'écran). On ne met en global que ce qui est vraiment partagé. Tout le détail est dans le module sur l'état global avec Zustand et MMKV.

utils/ — les fonctions pures

Rôle : abriter les fonctions pures : on leur donne une entrée, elles rendent une sortie, sans aucun effet de bord (pas de réseau, pas de base, pas de React). Tu y trouves : des calculs (utils/calculations/), du formatage de dates, et des validateurs (utils/validators/). Exemple : getPasswordError(motDePasse) qui renvoie un message d'erreur ou null. Ce qui n'a rien à y faire : appeler la base, lire un store, ou rendre du JSX. Une fonction pure est l'élément le plus facile à lire et à tester du projet — d'où l'intérêt d'y ranger tout ce qui peut l'être.

lib/ — les utilitaires techniques

Rôle : regrouper de petits outils techniques bas niveau, souvent liés à une bibliothèque tierce, qui ne sont pas vraiment du « métier ». Tu y trouves : par exemple la fonction cn qui fusionne proprement des classes de style. Exemple : cn("p-4", actif && "bg-blue") assemble des classes selon une condition. Ce qui n'a rien à y faire : de la logique propre à Halterofit (le calcul d'un volume d'entraînement, par exemple — ça, c'est utils/). La nuance utils/ vs lib/ : utils/ = « fonctions métier pures », lib/ = « colle technique » autour des outils.

constants/ — les valeurs fixes

Rôle : centraliser les valeurs qui ne changent pas à l'exécution, pour ne pas les dupliquer partout. Tu y trouves : les couleurs, les tailles, les limites (durées, seuils). Exemple : un colors.ts qui définit la couleur primaire, référencée partout au lieu d'écrire le code couleur à la main vingt fois. Ce qui n'a rien à y faire : du code qui fait quelque chose. Une constante ne s'exécute pas : c'est une valeur, pas une fonction. Si tu te surprends à écrire de la logique dans constants/, c'est qu'elle appartient à utils/.

Affichage ou logique ? La frontière qui décide de tout

Une fois la carte connue, 80 % de tes hésitations se ramènent à une seule question : est-ce de l'affichage, ou de la logique ? Cette frontière mérite qu'on s'y attarde, car c'est elle qui sépare le « haut » du projet (app/, components/) de son « moteur » (hooks/, services/, utils/, stores/).

L'affichage répond à « à quoi ça ressemble à l'écran ? » : un titre, un bouton, une liste, une couleur, un espacement. La logique répond à « qu'est-ce qui se passe ? » : un calcul, une décision (si X alors Y), un accès aux données, une mise à jour de l'état. La règle est de ne pas mêler les deux dans le même fichier. Un écran peut appeler un hook et afficher son résultat — ça, c'est sain. Mais un écran qui contient le calcul lui-même mélange les deux, et c'est là que la lisibilité s'effondre.

Le test mental est simple : devant une ligne de code, demande-toi « si je change le look de l'app, dois-je toucher cette ligne ? ». Si oui, c'est de l'affichage et ça va en haut. Si la ligne survivrait à un changement de look complet (un calcul de volume reste vrai quelle que soit la couleur du bouton), c'est de la logique et ça descend dans le moteur. Cette gymnastique, tu la fais déjà sans le savoir quand tu appliques la méthode du module sur la méthode pour lire un fichier inconnu : repérer ce qu'un fichier fait vraiment, c'est exactement repérer cette frontière.

Le sens de circulation des données

La carte t'a dit sont les choses. Maintenant, la règle qui les fait travailler ensemble : les données circulent dans un sens unique, en couches, du haut (ce que voit l'utilisateur) vers le bas (le stockage).

  ÉCRAN            (app/)         "Je veux afficher l'entraînement actif"
    │  appelle
    ▼
  HOOK             (hooks/)       useActiveWorkout() : prépare les données
    │  demande
    ▼
  SERVICE          (services/)    getWorkoutWithDetails() : lit la base
    │  lit / écrit
    ▼
  BASE DE DONNÉES  (locale)       les vraies données
                                   (↕ se synchronise avec le serveur)

L'écran ne parle jamais directement à la base : il passe par un hook, qui passe par un service. Chaque couche a un rôle.

Pourquoi imposer ce sens unique, plutôt que de laisser chacun appeler n'importe qui ? Pour la même raison qu'une chaîne d'usine va dans un seul sens : si la pièce pouvait revenir en arrière ou sauter des postes à sa guise, plus personne ne saurait dans quel état elle est. Le sens unique rend le flux prévisible. Quand tu lis un bug, tu sais que les données sont descendues par ce chemin et pas un autre, donc tu remontes le chemin poste par poste jusqu'à trouver le coupable. C'est précisément l'exercice qu'on fait en grand dans le module sur le traçage d'une fonctionnalité de bout en bout : on prend une fonctionnalité et on suit ce chemin d'un bout à l'autre.

Et pourquoi un écran ne parle-t-il jamais directement à la base ? Trois raisons :

  1. Pour pouvoir changer la base sans toucher aux écrans. Si tout l'accès aux données est confiné dans services/, remplacer la base ne casse que cette couche.
  2. Pour cacher la complexité. L'écran demande « donne-moi le workout » ; il ignore s'il faut lire en local, attendre le réseau, ou fusionner deux sources. Toute cette complexité reste sous le tapis du service.
  3. Pour ne pas dupliquer. Si dix écrans lisent les entraînements, et que chacun écrit sa propre requête, tu as dix endroits à corriger le jour où la requête change. Un seul service appelé par dix écrans : un seul endroit à corriger.
🏋️ Pourquoi cette discipline aide Halterofit

Ton app est « offline-first » : elle marche sans réseau, puis se synchronise quand le réseau revient. Ça n'est possible que parce que l'accès aux données est isolé dans services/. Les écrans ignorent s'ils sont en ligne ou non — ils demandent juste « donne-moi le workout » à un hook. Toute la complexité réseau (lire le local, mettre en file d'attente, synchroniser) est cachée derrière la couche service. C'est exactement la séparation des responsabilités qui rend cette magie gérable : sans elle, chaque écran devrait gérer le hors-ligne lui-même, et ce serait l'enfer.

La règle de dépendance : qui a le droit de connaître qui

Le sens de circulation des données a un jumeau, plus discret mais tout aussi important : la règle de dépendance. Elle dit qui a le droit d'importer (de « connaître ») qui. La règle est simple : les couches du haut connaissent celles du bas, jamais l'inverse.

Concrètement : un écran a le droit de connaître un hook ; un hook a le droit de connaître un service ; un service a le droit de connaître la base. Mais jamais dans l'autre sens — un service ne doit pas importer un écran, un hook ne doit pas importer un écran. La couche basse ne doit pas savoir qui l'utilise « au-dessus ». C'est ce qui rend une couche basse réutilisable : comme elle ignore ses appelants, n'importe qui peut l'appeler.

app/        (le plus haut)   peut utiliser : hooks, components, utils, lib, constants…
components/                  peut utiliser : utils, lib, constants
hooks/                       peut utiliser : services, stores, utils, constants
services/                    peut utiliser : utils, constants
stores/                      peut utiliser : services, utils, constants
utils/      (le plus bas)    ne dépend de RIEN d'autre
constants/  (le plus bas)    ne dépend de RIEN d'autre

Les flèches de dépendance pointent toujours vers le bas. utils/ et constants/ sont tout en bas : ils ne connaissent personne, donc tout le monde peut s'appuyer dessus sans risque.

Le cas de utils/ et constants/ mérite qu'on s'y arrête : ils ne dépendent de rien. Pas de React, pas de base, pas de réseau, pas d'autres dossiers du projet. C'est voulu. Ce sont les fondations : on construit dessus, jamais l'inverse. Une fondation qui dépendrait des étages au-dessus d'elle, ce serait absurde — et pourtant, en code, c'est une erreur fréquente. Garder le bas « indépendant » est ce qui permet de le tester en isolation et de le réutiliser partout sans entraîner la moitié du projet avec lui.

🧭 Un signal d'alarme utile

Si tu vois un import qui « remonte » — un service qui importe un écran, un util qui importe un hook — c'est presque toujours le signe que quelque chose est mal rangé. Ce n'est pas une faute de syntaxe (le code peut compiler), mais une faute d'architecture. Repère ces remontées : elles trahissent une responsabilité posée au mauvais étage.

Comment trouver n'importe quoi : la méthode « rôle → dossier → fichier »

Voici la compétence la plus rentable de tout ce module. Face à n'importe quelle tâche — « où est le code qui fait X ? » — tu n'as pas à fouiller au hasard. Tu suis trois pas, du plus abstrait au plus concret.

  1. Rôle. Reformule la tâche en un verbe de rôle. « Changer la couleur » → c'est une valeur fixe. « Valider l'email » → c'est un calcul pur. « Lire les entraînements » → c'est un accès aux données. « Afficher l'écran de profil » → c'est de l'affichage.
  2. Dossier. Traduis le rôle en dossier, grâce à la carte. Valeur fixe → constants/. Calcul pur → utils/. Accès aux données → services/. Affichage → app/ ou components/. Logique partagée → hooks/. État partagé → stores/.
  3. Fichier. Une fois dans le bon dossier, le nom des fichiers te guide vers le bon (les noms sont en anglais, descriptifs). Là, tu ouvres et tu lis — et c'est exactement la technique du module sur la méthode pour lire un fichier inconnu qui prend le relais.

Remarque la puissance de cette méthode : les deux premiers pas se font sans ouvrir un seul fichier, juste avec la carte en tête. Tu élimines 90 % du projet avant même de lire une ligne. C'est ça, « savoir où chercher » — et c'est ce qui distingue quelqu'un qui connaît un codebase de quelqu'un qui le subit.

💡 Le réflexe en une ligne

Avant de chercher « où est ce code ? », demande-toi « quel est son rôle ? ». Le rôle te donne le dossier ; le dossier réduit la recherche à une poignée de fichiers ; la lecture fait le reste. Rôle → dossier → fichier.

Tout en haut : le monorepo et les décisions documentées

On a parlé de apps/mobile/src/. Mais qu'y a-t-il au-dessus ? Le projet est un monorepo : un seul dépôt qui contient plusieurs applications côte à côte. Concrètement, on trouve un dossier apps/ avec apps/mobile (l'app, active, celle qui t'intéresse) et apps/web (un futur site, encore embryonnaire).

halterofit/                  ← la racine du monorepo
├── apps/
│   ├── mobile/              ← l'app React Native (active) ← TOUT est ici
│   └── web/                ← un futur site (encore vide)
├── docs/
│   └── decisions/          ← les ADR : pourquoi telle décision a été prise
├── package.json
├── pnpm-workspace.yaml     ← pnpm : un seul dépôt, plusieurs paquets
└── turbo.json              ← turbo : orchestre les tâches du monorepo

La racine du projet. Pour l'instant, 100 % de ce qui t'intéresse est dans apps/mobile — mais c'est utile de savoir ce qu'il y a autour.

Pourquoi un monorepo plutôt que deux dépôts séparés ? Parce que l'app mobile et le futur site partageront du code (des types, des règles métier, des utilitaires). Un seul dépôt permet de partager ce code sans le copier-coller. Deux outils orchestrent l'ensemble : pnpm (un gestionnaire de paquets qui sait gérer plusieurs applications dans un même dépôt, ce qu'on appelle des workspaces) et turbo (qui lance les tâches — build, tests — efficacement, sans tout refaire à chaque fois). Tu n'as pas besoin de les maîtriser pour lire le projet ; sache juste qu'ils expliquent la présence des fichiers pnpm-workspace.yaml et turbo.json à la racine.

🧭 Le projet documente ses décisions : les ADR

Regarde le dossier docs/decisions/. Il contient des ADR (de l'anglais Architecture Decision Records, « comptes-rendus de décision d'architecture »). Chaque ADR est un court document qui répond à : « quelle décision a-t-on prise, à quel sujet, et pourquoi ? ». Par exemple : « pourquoi avoir choisi cette base de données plutôt qu'une autre ». Pour un lecteur comme toi qui veut comprendre le « pourquoi » derrière le « comment », c'est une mine d'or : avant de te demander pourquoi le projet est fait ainsi, vérifie si un ADR l'explique déjà. Et c'est un excellent réflexe à reprendre dans tes propres projets : écrire pourquoi tu décides, pas seulement quoi.

Pourquoi cette carte rend l'app robuste

Prenons un peu de hauteur pour relier tout ce qu'on a vu. La séparation des responsabilités, le sens de circulation unique, la règle de dépendance et la méthode « rôle → dossier » ne sont pas quatre règles séparées : ce sont quatre facettes d'une même idée, celle de garder chaque chose à sa place pour que l'ensemble reste compréhensible et modifiable.

Imagine la situation inverse, un projet « en vrac » où tout se mélange : un écran qui calcule, lit la base, garde son état et s'affiche, le tout dans un fichier de 800 lignes. Tant que tu n'y touches pas, ça marche. Mais le jour où tu dois corriger un bug, tu dois tout lire ; le jour où tu changes la base, tu casses l'affichage ; le jour où un deuxième développeur arrive, il ne sait pas par où commencer. Le « en vrac » a un coût caché qui ne se paie pas à l'écriture, mais à chaque modification — et un projet vit bien plus longtemps qu'il ne s'écrit.

La carte que tu viens d'apprendre est l'antidote. Elle te donne quatre garanties concrètes : (1) tu trouves vite, parce que le rôle indique le dossier ; (2) tu lis vite, parce que chaque fichier ne fait qu'une chose ; (3) tu modifies sans peur, parce qu'une couche peut changer sans casser les autres ; (4) tu testes facilement, parce que le moteur (logique, calculs, services) est isolé de l'affichage. C'est ce socle qui rend possible l'exercice de synthèse du module sur le traçage d'une fonctionnalité de bout en bout : si l'app n'était pas rangée ainsi, on ne pourrait pas suivre une fonctionnalité proprement d'un bout à l'autre.

✍️ Exercice de lecture

On te confie cinq tâches. Applique « rôle → dossier » : dans quel dossier irais-tu chercher en premier, et pourquoi ?

  1. « Changer la couleur principale de l'app. »
  2. « Ajouter une règle : le mot de passe doit contenir un chiffre. »
  3. « Modifier l'apparence de la barre d'onglets en bas de l'écran. »
  4. « Ajouter une fonction qui calcule le volume total d'un entraînement. »
  5. « Comprendre pourquoi le projet a choisi sa base de données locale. »
Voir le corrigé

1. Rôle = valeur fixeconstants/ (un fichier colors.ts centralise les couleurs).
2. Rôle = calcul / validation purutils/validators/ — c'est exactement le genre de getPasswordError évoqué plus haut, une fonction pure de validation.
3. Rôle = affichage / routeapp/, précisément le _layout.tsx des onglets (qu'on détaille dans le module sur la navigation avec Expo Router).
4. Rôle = calcul métier purutils/calculations/. (Pas lib/ : lib/ c'est la colle technique, pas le métier.)
5. Ce n'est pas du code à modifier mais une décision à comprendre → docs/decisions/, les ADR. Le « pourquoi » y est peut-être déjà écrit.
Le réflexe « rôle → dossier » te fait gagner un temps fou avant même d'ouvrir un fichier.

🧠 Quiz éclair

1. Quel dossier contient les écrans / routes ?

app/ — chaque fichier y correspond à une route. On voit le mécanisme en détail dans le module sur la navigation avec Expo Router.

2. Un écran peut-il lire directement la base de données ?

Non, par convention. Il passe par un hook (hooks/), qui passe par un service (services/). Chaque couche son rôle, et le sens de circulation est unique : écran → hook → service → base.

3. Où vivent les fonctions « pures » comme les validateurs et les calculs ?

Dans utils/ (ex. utils/validators/, utils/calculations/). Ce sont des fonctions sans dépendance à React ni à la base — elles sont tout en bas de la règle de dépendance et ne connaissent personne.

4. Selon la règle de dépendance, un service a-t-il le droit d'importer un écran ?

Non. Les couches du haut connaissent celles du bas, jamais l'inverse. Un service (bas) ne doit pas connaître un écran (haut) : c'est ce qui le garde réutilisable. Un import qui « remonte » est un signal d'alarme d'architecture.

5. À quoi sert le dossier docs/decisions/ ?

Il contient les ADR (Architecture Decision Records) : de courts documents qui expliquent pourquoi telle décision technique a été prise. Idéal pour comprendre le « pourquoi » du projet avant de le deviner soi-même.

6. Quelle est la méthode pour trouver n'importe quel code rapidement ?

« Rôle → dossier → fichier » : on reformule la tâche en verbe de rôle, le rôle donne le dossier (via la carte), et le nom des fichiers guide vers le bon. Les deux premiers pas se font sans ouvrir un seul fichier.

À retenir

Chaque dossier = un rôle : app/ affiche, components/ assemble des briques, hooks/ orchestre, services/ accède aux données, stores/ garde l'état global, utils/ calcule, lib/ colle, constants/ fige les valeurs. Les données descendent dans un sens unique : écran → hook → service → base. La règle de dépendance pointe toujours vers le bas, et utils//constants/ ne dépendent de rien. Pour trouver quoi que ce soit : rôle → dossier → fichier. Apprends cette carte : elle te dit où chercher avant même de lire.

⚠️ Piège fréquent

Deux fautes d'architecture reviennent sans cesse, et toutes deux compilent sans erreur — c'est ce qui les rend sournoises. Un : mettre de la logique métier dans un écran (calculer un total, valider une saisie directement dans le fichier de app/). L'écran devient illisible et sa logique impossible à réutiliser ; ces calculs appartiennent à utils/. Deux : accéder à la base depuis un composant (un <Card> qui va lire les données tout seul). Le composant cesse d'être réutilisable et viole le sens de circulation ; il doit recevoir ses données par ses props, et c'est le hook qui les a obtenues du service. Si ça compile mais que ça « sent mauvais », c'est souvent une responsabilité posée au mauvais étage.

🔄 Transférable

« Sépare l'affichage, la logique et l'accès aux données » est un principe universel. On parle d'architecture en couches (ou « en oignon ») : des couches concentriques où l'extérieur connaît l'intérieur, jamais l'inverse. Tu retrouveras exactement cette idée — séparation des responsabilités, sens de circulation unique, règle de dépendance vers le bas — dans presque tous les projets sérieux : web, mobile, back-end, jeux. Le vocabulaire change parfois (couches, oignon, hexagonal…), l'idée reste la même. Apprends-la ici, elle te servira partout.