diff --git a/DEVELOPERS.md b/DEVELOPERS.md
index 599c02ec2bde1116abcd12fea32e12cf0e4cf0ba..8c4b21f9d0e047351e72430c894cc5a9554d8c7f 100644
--- a/DEVELOPERS.md
+++ b/DEVELOPERS.md
@@ -56,15 +56,13 @@ L'interface s'appuie sur les principes de **material design** et de **responsive
 
 L'état de la session, des modules, des paramètres et des résultats numériques est conservé par JaLHyd. Seules les représentations graphiques sont conservées par la couche ngHyd.
 
-#### formulaires
-
-nodes
+Pour chaque module chargé en session, un arbre d'objets partant d'une instance de `FormulaireDefinition` représente la structure des éléments à l'écran : groupes de champs (répétables ou non), champs, listes déroulantes. Ces éléments sont déclarés dans les fichiers du dossier `src/app/formulaire`.
 
-ngparam
+#### formulaires
 
-form*, form-compute*, form-result*
+Les formulaires dérivent de `FormulaireBase`. Lors de leur instanciation, ils se chargent de lire leur configuration (voir "configuration/modules" ci-dessous) et instancier les éléments enfants qui y sont déclarés : groupes de champs ("fieldset"), groupes de champs répétables ("fieldset_container"), paramètres d'entrée ("ngparam"), listes déroulantes ("select_field"). Ensuite le formulaire est lié au Nub de JaLHyd correspondant au module créé.
 
-#### résultats
+Chaque formulaire est associé à une instance d'une classe `FormCompute*` et une instance d'une classe `FormResult*`. La première est chargée d'exécuter le Nub de JaLHYd associé au formulaire, et de transmettre les résultats de calcul à la seconde. La seconde est chargée d'organiser les résultats (par défaut les grouper en résultats fixes / résultats variés) afin de faciliter leur représentation par les composants graphiques.
 
 ### configuration
 
@@ -134,7 +132,7 @@ Le dossier `scripts` contient des scripts d'aide à la compilation, utilisés pa
 
 ## Ajouter un module de calcul
 
-Dans cet exemple nous considérerons le module fictif "Addition" proposé dans la documentation de JaLHyd (voir ci-dessous).
+Dans cet exemple nous considérerons le module fictif "Addition" proposé dans la documentation de JaLHyd (voir ci-dessous), tout en ajoutant des informations utiles pour des cas plus complexes.
 
 ### JaLHyd
 
@@ -240,29 +238,167 @@ Dans les fichiers `locale/messages.*.json` :
 
 ### classes de formulaire personnalisées
 
-2. **Si nécessaire** créer la classe du formulaire dans _src/app/formulaire/definition/concrete_ . Une classe de base gérant la majorité des cas est déjà disponible, en général cette étape n'est pas nécessaire 
+En général la classe `FormulaireBase` est suffisante pour gérer un nouveau module, et est instanciée automatiquement par `FormulaireService` lors de la création d'un nouveau module. Elle agrège es instances de `FormComputeFixedVar` et `FormResultFixedVar`.
 
-	- Par ex : _FormulaireMaCalculette_ dans _src/app/formulaire/definition/concrete/form-ma-calculette.ts_
+Mais dans des cas plus complexes, par exemple si le module contient des listes déroulantes ou des sous-modules, répétables ou non, il est nécessaire de créer de nouvelles classes de formulaires dérivées de celles-ci.
 
-		Ces classes concrètes sont construites par composition des classes dans _src/app/formulaire/definition_ :
-		- _form-def-*_ : définition/description du formulaire.
-			- _FormDefSection_ : avec paramètre à varier
-			- _FormDefParamToCalculate_ : avec paramètre à calculer
-			- etc...
-		- _form-compute-*_ : aspects calculatoires
-		- _form-result-*_ : affichage des résultats
+Dans un tel cas, créer la classe du formulaire dans un nouveau fichier, dans le dossier `src/app/formulaire/definition/concrete/`/ Par exemple `form-macrorugo-compound.ts`.
 
-		On peut soit composer la classe concrète directement avec ces classes, soient dériver ces dernières et composer avec.
+Si les mécanismes de calcul ou de récupération des résultats doivent être modifiés, créer les classes nécessaires dans le dossier `src/app/formulaire/definition`, par exemple `form-compute-macrorugo-compound.ts` et `form-result-macrorugo-compound.ts`. Sinon, agréger des classes `FormCompute*` et `FormResult*` existantes.
 
+Si une nouvelle classe de formulaire a été créée, ajouter un `case` dans la méthode `newFormulaire()` de `FormulaireService`, dand `src/app/services/formulaire.service.ts`. Exemple :
 
-5. **Si une nouvelle classe a été créée à l'étape 2**, dans la méthode _FormulaireService.newFormulaire()_, compléter le _switch_ pour fournir la classe à instancier.
+```TypeScript
+case CalculatorType.Trigo:
+  f = new FormulaireTrigo();
+  break;
+```
 
 ### si le module agrège des modules enfants
 
+La traduction des variables des modules enfants doit aussi être ajoutée dans les fichiers de langues, dans le dossier de configuration du module.
+
+#### si ces modules enfants sont répétables ("fs_container)
+
+Il est nécessaire de créer une nouvelle classe de formulaire dérivée de `FormulaireBase` (voir "classes de formulaire personnalisées" ci-dessus), en s'inspirant de `FormulaireMacrorugoCompound` par exemple. Notamment, implémenter ou surcharger les méthodes :
+
+ * `createFieldset()`
+ * `moveFieldsetUp()`
+ * `moveFieldsetDown()`
+ * `removeFieldset()`
+ * `completeParse()`
+ * `update()` (action "newFieldset")
+
+Dans la méthode `create()` de `CalculatorListComponent`, dans le fichier `src/app/components/calculator-list/calculator-list.component.ts`, ajouter la création d'un enfant par défaut. Exemple pour `MacrorugoCompound` :
+
+```TypeScript
+if (f instanceof FormulaireMacrorugoCompound) {
+  for (const e of f.allFormElements) {
+    if (e instanceof FieldsetContainer) {
+        e.addFromTemplate(0, 0, f.mrcNub.children[0]);
+        break;
+    }
+  }
+}
+```
+
+Dans cet exemple, on ajoute l'interface pour le premier enfant du Nub (instancié par JaLHyd), à l'élément de formulaire de type `FieldsetContainer` (ici, il n'y en a qu'un).
+
+Ajouter ensuite la création de fieldsets pour les enfants existants, dans la méthode `createFormulaire()` de `FormulaireService`, dans le fichier `src/app/services/formulaire.service.ts`. Exemple pour `ParallelStructures` :
+
+```TypeScript
+if (f.currentNub instanceof ParallelStructure) {
+  for (const struct of f.currentNub.structures) {
+    for (const e of f.allFormElements) {
+      if (e instanceof FieldsetContainer) {
+        e.addFromTemplate(0, undefined, struct);
+      }
+    }
+  }
+}
+```
+
+Dans chaque fichier de langue du dossier `src/locale`, ajouter les traductions pour le nom type d'enfant (voir la documentation développeurs de JaLHyd), au singulier et au pluriel sous les clés `INFO_CHILD_TYPE_typedenfant` et `INFO_CHILD_TYPE_typedenfant_PLUR`. Par exemple pour le type d'enfant `Macrorugo` en français :
+
+```json
+"INFO_CHILD_TYPE_MACRORUGO": "radier",
+"INFO_CHILD_TYPE_MACRORUGO_PLUR": "radiers",
+```
+
 ### si le formulaire comprend des listes déroulantes
 
+Il est nécessaire de créer une nouvelle classe de formulaire dérivée de `FormulaireBase` (voir "classes de formulaire personnalisées" ci-dessus), en s'inspirant de FormulaireTrigo par exemple. Notamment, implémenter ou surcharger les méthodes :
+
+ * `afterParseFieldset()`
+ * `parseOptions()`
+ * `update()` (appeler `reset()` lors de l'action "propertyChange")
+
+#### configuration
+
+Dans le fichier de configuration du module, ajouter la définition des listes déroulantes dans "fields" notamment leur **source** (voir "sources" plus bas), ainsi que leur valeur par défaut dans le "fieldset" parent. Exemple dans `trigo.config.json`
+
+```json
+{
+  "id": "fs_trigo",
+  "type": "fieldset",
+  "defaultOperation": "COS",
+  "defaultUnit": "DEG",
+  "fields": [
+    {
+      "id": "select_operation",
+      "type": "select",
+      "source": "trigo_operation"
+    },
+    {
+      "id": "select_unit",
+      "type": "select",
+      "source": "trigo_unit"
+    }
+  ]
+},
+```
+
+Dans ce même fichier de configuration, dans le dernier élément "options", ajouter une entrée par liste déroulante :
+
+```json
+…
+{
+  "type": "options",
+  "operationSelectId": "select_operation",
+  "unitSelectId": "select_unit",
+  …
+```
+ 
+#### sources
+ 
+Chaque liste déroulante est associée à une **source** (voir "configuration" plus haut), qui détermine quels sont les choix possibles. Pour ajouter une source, modifier la méthode `parseConfig()` de la classe `SelectField`, dans le fichier `src/app/formulaire/select-field.ts`. Exemple pour "trigoOperation" :
+
+```TypeScript
+case "trigo_operation": // (cos, sin…)
+  for (let j = 0; j < Object.keys(TrigoOperation).length / 2; j++) {
+    this.addEntry(new SelectEntry(this._entriesBaseId + j, j));
+  }
+  break;
+```
+
+#### lien avec les propriétés
+
+Les listes déroulantes doivent être liées à des **propriétés** du Nub. Pour ce faire, modifier la classe `FieldSet` dans le fichier `src/app/formulaire/fieldset.ts` comme suit (exemple pour le module `Trigo`).
+
+Ajouter un `case` dans la fonction `updateFields()`
+
+```TypeScript
+case "fs_trigo": // Trigo
+  this.setSelectValueFromProperty("select_operation", "trigoOperation");
+  this.setSelectValueFromProperty("select_unit", "trigoUnit");
+  break;
+```
+
+Ajouter un `case` dans la fonction `update()`
+
+```TypeScript
+case "select_operation": // Trigo
+  this.setPropValue("trigoOperation", data.value.value);
+  break;
+case "select_unit": // Trigo
+  this.setPropValue("trigoUnit", data.value.value);
+  break;
+```
+
+Dans la fonction `parseConfig()`, ajouter un appel par à `setPropertyValueFromConfig()` pour chaque liste déroulante.
+
+```TypeScript
+  this.setPropertyValueFromConfig(json, "defaultOperation", "trigoOperation", TrigoOperation);
+  this.setPropertyValueFromConfig(json, "defaultUnit", "trigoUnit", TrigoUnit);
+```
+ 
 ### documentation
 
 Pour chaque langue, ajouter un fichier .md dans les dossiers `docs-*/calculators`, puis placer ce nouveau fichier dans la hiérarchie de la documentation, en ajoutant son chemin dans les fichiers `mkdocs-*.yml`.
 
 Lier ce fichier au module via la clé `help` du bloc d'options de la configuration du module. Exemple pour un fichier de documentation dont le chemin est `calculators/math/addition.md` : `"help" : "math/addition.html"` (MkDocs convertit les fichiers MarkDown en HTML)
+
+### tests unitaires
+
+Plusieurs tests unitaires passent en revue les modules pour les tester un par un. Pour que le nouveau module soit testé, son `CalculatorType` doit être ajouté à la liste dans le fichier `e2e/tested_calctypes.ts.
+ 
\ No newline at end of file