diff --git a/e2e/clone-calc.e2e-spec.ts b/e2e/clone-calc.e2e-spec.ts
index 459b3d7677ad34afc3e41932b10226fc678d8eb6..dab35d14926c0479f57d88661c61e5192392e63d 100644
--- a/e2e/clone-calc.e2e-spec.ts
+++ b/e2e/clone-calc.e2e-spec.ts
@@ -2,7 +2,6 @@ import { AppPage } from "./app.po";
 import { ListPage } from "./list.po";
 import { CalculatorPage } from "./calculator.po";
 import { Navbar } from "./navbar.po";
-import { SideNav } from "./sidenav.po";
 import { browser } from "protractor";
 
 /**
@@ -13,14 +12,12 @@ describe("ngHyd − clone a calculator", () => {
   let listPage: ListPage;
   let calcPage: CalculatorPage;
   let navbar: Navbar;
-  let sidenav: SideNav;
 
   beforeEach(() => {
     startPage = new AppPage();
     listPage = new ListPage();
     calcPage = new CalculatorPage();
     navbar = new Navbar();
-    sidenav = new SideNav();
   });
 
   it("when cloning a calculator, the clone should have the same values for all parameters", async () => {
diff --git a/src/app/components/base-param-input/base-param-input.component.ts b/src/app/components/base-param-input/base-param-input.component.ts
index e04b0a4607e2d3632eab3e07700c7a8b2c0bb888..001a186d784d62138e45772d9570d56c08e56a0b 100644
--- a/src/app/components/base-param-input/base-param-input.component.ts
+++ b/src/app/components/base-param-input/base-param-input.component.ts
@@ -75,7 +75,7 @@ export class NgBaseParam extends Observable {
                 valid = true;
             } catch (e) {
                 if (e instanceof Message) {
-                    // @TODO ici au début le service de localisation n'a pas encore chargé ses messages…
+                    // ici au début le service de localisation n'a pas encore chargé ses messages…
                     msg = ServiceFactory.instance.i18nService.localizeMessage(e);
                 } else {
                     msg = "invalid value";
diff --git a/src/app/components/fixedvar-results/fixedvar-results.component.ts b/src/app/components/fixedvar-results/fixedvar-results.component.ts
index d6dca7a41a47100383d7af0cc518c1190b680ad1..3392fb698cf71523bca8cd1a036c9f8380bfb0dc 100644
--- a/src/app/components/fixedvar-results/fixedvar-results.component.ts
+++ b/src/app/components/fixedvar-results/fixedvar-results.component.ts
@@ -98,6 +98,7 @@ export class FixedVarResultsComponent implements DoCheck {
             this.resultsGraphComponent.results = undefined;
         }
 
+        // set _doUpdate flag so that results are rebuilt on the next Angular display cycle
         this._doUpdate = false;
         if (this._fixedResults !== undefined) {
             this._doUpdate = this._fixedResults.hasResults || this._fixedResults.hasLog;
diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts
index 07a32d45538a814032ef953f0ff3ccffcd3cdea2..95c9e3c71a6a8a95a7399f84c05c1bd35e04644c 100644
--- a/src/app/components/param-values/param-values.component.ts
+++ b/src/app/components/param-values/param-values.component.ts
@@ -63,7 +63,7 @@ export class ParamValuesComponent implements AfterViewInit, Observer {
                 this.openDialog();
             });
         }
-        // subscribe to parameter values change (through dialog actions) @TODO draft
+        // subscribe to parameter values change (through dialog actions)
         this.param.addObserver(this);
     }
 
diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
index 4e96da330ac707ad9818125c23386e0047384dfd..99be68efbe0f6056950e05f519570a271993fbaf 100644
--- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts
+++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
@@ -49,6 +49,9 @@ export class FormulaireCourbeRemous extends FormulaireBase {
     // interface Observer
 
     update(sender: IObservable, data: any) {
+
+        super.update(sender, data);
+
         if (sender instanceof FieldSet && data.action === "propertyChange") {
             switch (sender.id) {
                 case "fs_section":
diff --git a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
index c84864baaa2b1821e56e86e9ff037f2882d24a22..db299120666ff8b777715e0a25fe388e005c912a 100644
--- a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
+++ b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
@@ -26,6 +26,9 @@ export class FormulaireLechaptCalmon extends FormulaireBase implements Observer
     // interface Observer
 
     public update(sender: any, data: any) {
+
+        super.update(sender, data);
+
         // en cas de changement de valeur du select de matériau, effacement des résultats et MAJ des champs L,M,N
         if (sender instanceof SelectField) {
             if (data.action === "select") {
diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index d71119c87ae08f5e629eed734231dbed1536fb98..ec840a97075a5039d7851a883607fb9cb9346d2c 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -338,6 +338,9 @@ export class FormulaireParallelStructure extends FormulaireBase {
     // interface Observer
 
     public update(sender: any, data: any) {
+
+        super.update(sender, data);
+
         if (sender instanceof FieldsetContainer) {
             switch (data.action) {
                 case "newFieldset":
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index 35a08225c68a004936013c8447310cde4c8b3d8d..503bdaba7730c5063406427250e04d9364a0e252 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -33,6 +33,9 @@ export class FormulaireRegimeUniforme extends FormulaireBase implements Observer
     // interface Observer
 
     update(sender: IObservable, data: any) {
+
+        super.update(sender, data);
+
         // changement de propriété du FieldSet contenant le select de choix du type de section
         if (sender instanceof FieldSet && sender.id === "fs_section" && data.action === "propertyChange") {
             this.replaceCurrentNub(sender.properties);
diff --git a/src/app/formulaire/definition/concrete/form-section-parametree.ts b/src/app/formulaire/definition/concrete/form-section-parametree.ts
index 9c48c038bcf3278528703ad13e4cd98268e1238b..1a3ef62b123e5dbdd21200d20c06966c34da069f 100644
--- a/src/app/formulaire/definition/concrete/form-section-parametree.ts
+++ b/src/app/formulaire/definition/concrete/form-section-parametree.ts
@@ -31,6 +31,9 @@ export class FormulaireSectionParametree extends FormulaireBase {
     // interface Observer
 
     update(sender: IObservable, data: any) {
+
+        super.update(sender, data);
+
         // changement de propriété du FieldSet contenant le select de choix du type de section
         if (sender instanceof FieldSet && data.action === "propertyChange") {
             switch (sender.id) {
diff --git a/src/app/formulaire/definition/form-compute-courbe-remous.ts b/src/app/formulaire/definition/form-compute-courbe-remous.ts
index dc3a6a2e26789f4c9f2431fbb663e1150b4fbd81..b69986e83c954b0795184a17145b424b6c3e7958 100644
--- a/src/app/formulaire/definition/form-compute-courbe-remous.ts
+++ b/src/app/formulaire/definition/form-compute-courbe-remous.ts
@@ -7,6 +7,11 @@ import { FormCompute } from "./form-compute";
 import { FormResultRemous } from "./form-result-remous";
 
 export class FormComputeCourbeRemous extends FormCompute {
+
+    private resultYn: Result;
+
+    private resultYc: Result;
+
     constructor(formBase: FormulaireDefinition, private _formSection: FormDefSection, formResult: FormResultRemous) {
         super(formBase, formResult);
     }
@@ -21,8 +26,15 @@ export class FormComputeCourbeRemous extends FormCompute {
         const prmCR: CourbeRemousParams = cr.prms as CourbeRemousParams;
         const sect: acSection = prmCR.Sn;
 
-        const Yn: Result = sect.Calc("Yn"); // hauteur normale
-        const Yc: Result = sect.Calc("Yc"); // hauteur critique
+        this.resultYn = sect.Calc("Yn"); // hauteur normale
+        this.resultYc = sect.Calc("Yc"); // hauteur critique
+
+        this.reaffectResultComponents();
+    }
+
+    protected reaffectResultComponents() {
+        const cr: CourbeRemous = this._formBase.currentNub as CourbeRemous;
+        const prmCR: CourbeRemousParams = cr.prms as CourbeRemousParams;
 
         this.remousResults.parameters = prmCR;
 
@@ -33,8 +45,8 @@ export class FormComputeCourbeRemous extends FormCompute {
         this.remousResults.result = cr.calculRemous(this.remousResults.extraParamSymbol);
 
         // données du graphe
-        this.remousResults.hauteurNormale = Yn.resultElement;
-        this.remousResults.hauteurCritique = Yc.resultElement;
+        this.remousResults.hauteurNormale = this.resultYn.resultElement;
+        this.remousResults.hauteurCritique = this.resultYc.resultElement;
         if (this.remousResults.extraParamSymbol) {
             this.remousResults.extraGraph = ["Hs", "Hsc", "Yf", "Yt", "Yco"].indexOf(this.remousResults.extraParamSymbol) === -1;
         } else {
diff --git a/src/app/formulaire/definition/form-compute-fixedvar.ts b/src/app/formulaire/definition/form-compute-fixedvar.ts
index 151b875bfbae1e4c4178b8c09b228b54e2febc7f..6168991607cff3a6c066e4bcaec1ad521da6f040 100644
--- a/src/app/formulaire/definition/form-compute-fixedvar.ts
+++ b/src/app/formulaire/definition/form-compute-fixedvar.ts
@@ -1,4 +1,4 @@
-import { Nub, Result, ComputeNode, ParamValueMode } from "jalhyd";
+import { Nub, Result, ComputeNode } from "jalhyd";
 
 import { FormCompute } from "./form-compute";
 import { NgParameter, ParamRadioConfig } from "../ngparam";
@@ -45,22 +45,28 @@ export class FormComputeFixedVar extends FormCompute {
     protected compute() {
         const nub: Nub = this._formBase.currentNub;
         const computedParam: NgParameter = this.getComputedParameter();
+
+        const res: Result = this.runNubCalc(nub, computedParam);
+
+        this.reaffectResultComponents();
+    }
+
+    protected reaffectResultComponents() {
+        const nub: Nub = this._formBase.currentNub;
+        const computedParam: NgParameter = this.getComputedParameter();
         this.formResult.addFixedParameters();
         const varParam: NgParameter = this.getVariatedParameter();
 
         if (varParam === undefined) {
             // pas de paramètre à varier
-            const res: Result = this.runNubCalc(nub, computedParam);
-            this.formResult.fixedResults.result = res;
+            this.formResult.fixedResults.result = nub.result;
             this.formResult.fixedResults.calculatedParameter = computedParam;
         } else {
             // il y a un paramètre à varier
-            const res: Result = this.runNubCalc(nub, computedParam);
-
             this.formResult.varResults.variatedParameter = varParam;
             this.formResult.varResults.calculatedParameter = computedParam;
 
-            this.formResult.varResults.result = res;
+            this.formResult.varResults.result = nub.result;
             this.formResult.varResults.update(false);
         }
     }
diff --git a/src/app/formulaire/definition/form-compute-section-parametree.ts b/src/app/formulaire/definition/form-compute-section-parametree.ts
index fc41974d9505d7c5da73d60c060969968198c296..068dce9d2aac4b7024fa2028e01afedc374c8306 100644
--- a/src/app/formulaire/definition/form-compute-section-parametree.ts
+++ b/src/app/formulaire/definition/form-compute-section-parametree.ts
@@ -12,6 +12,8 @@ import { FormulaireNode } from "../formulaire-node";
 
 export class FormComputeSectionParametree extends FormCompute {
 
+    private tmpResult: Result;
+
     constructor(formBase: FormulaireDefinition, private _formSection: FormDefSection, formResult: FormResult) {
         super(formBase, formResult);
     }
@@ -62,20 +64,25 @@ export class FormComputeSectionParametree extends FormCompute {
 
         const sectNub: SectionParametree = this._formBase.currentNub as SectionParametree;
 
-        const sect: acSection = sectNub.section;
-        this._sectionResults.section = sect;
-
-        const tmpResult: Result = sectNub.CalcSerie(
+        this.tmpResult = sectNub.CalcSerie(
             undefined, // valeur initiale, non utilisée dans ce cas
             undefined // variable à calculer, non utilisée
         );
 
+        this.reaffectResultComponents();
+    }
+
+    protected reaffectResultComponents() {
+        const sectNub: SectionParametree = this._formBase.currentNub as SectionParametree;
+        const sect: acSection = sectNub.section;
+        this._sectionResults.section = sect;
+
         // résultats de section (avec le graphique de section)
-        this._sectionResults.result = tmpResult;
+        this._sectionResults.result = this.tmpResult;
 
         // résultats complémentaires des paramètres fixés
         this._formSectionResult.addSectionFixedParameters(false);
-        this._formSectionResult.fixedResults.result = tmpResult;
+        this._formSectionResult.fixedResults.result = this.tmpResult;
     }
 
     /**
diff --git a/src/app/formulaire/definition/form-compute.ts b/src/app/formulaire/definition/form-compute.ts
index 4e8494753862af7cb478da8204cf880959a5a5b5..d1e848b28cb3c79ee81d2fdff7e39c4325c736cc 100644
--- a/src/app/formulaire/definition/form-compute.ts
+++ b/src/app/formulaire/definition/form-compute.ts
@@ -1,11 +1,14 @@
-import { Nub, Result, ParamDomainValue } from "jalhyd";
+import { Nub, Result, ParamDomainValue, Observer } from "jalhyd";
 
 import { FormResult } from "./form-result";
 import { FormulaireDefinition } from "./form-definition";
 import { NgParameter } from "../ngparam";
 
-export abstract class FormCompute {
+export abstract class FormCompute implements Observer {
+
     constructor(protected _formBase: FormulaireDefinition, protected _formResult: FormResult) {
+        // indirectly subscribe to Nub result updates
+        this._formBase.addObserver(this);
     }
 
     protected abstract compute();
@@ -23,14 +26,25 @@ export abstract class FormCompute {
     }
 
     /**
-     * lance le calcul d'un paramètre en déterminant une valeur initiale
+     * Copies current Nub result into result components for display on page.
+     * Should be called every time the Nub result changes
+     */
+    protected abstract reaffectResultComponents();
+
+    /**
+     * Lance le calcul d'un paramètre en déterminant une valeur initiale.
+     * Si nécessaire déclenche un calcul en chaîne des modules en amont.
      */
     protected runNubCalc(nub: Nub, computedParam: NgParameter): Result {
         let init: number;
+        // require chain computation; redundant with Nub.CalcSerie but required
+        // to get initial value here...
+        const computedParamValue = computedParam.getValue(true);
+
         switch (computedParam.domain.domain) {
             case ParamDomainValue.ANY:
                 if (computedParam && computedParam.isDefined) {
-                    init = computedParam.getValue();
+                    init = computedParamValue;
                 }
                 if (init === undefined) {
                     init = 0;
@@ -39,7 +53,7 @@ export abstract class FormCompute {
 
             case ParamDomainValue.POS_NULL:
                 if (computedParam && computedParam.isDefined) {
-                    init = Math.max(computedParam.getValue(), 0);
+                    init = Math.max(computedParamValue, 0);
                 }
                 if (init === undefined) {
                     init = 0;
@@ -52,7 +66,7 @@ export abstract class FormCompute {
 
             case ParamDomainValue.NOT_NULL:
                 if (computedParam && computedParam.isDefined) {
-                    init = computedParam.getValue();
+                    init = computedParamValue;
                 }
                 if (init === undefined || init === 0) {
                     init = 1e-8;
@@ -61,7 +75,7 @@ export abstract class FormCompute {
 
             case ParamDomainValue.POS:
                 if (computedParam && computedParam.isDefined) {
-                    init = Math.max(computedParam.getValue(), 1e-8);
+                    init = Math.max(computedParamValue, 1e-8);
                 }
                 if (init === undefined) {
                     init = 1e-8;
@@ -72,6 +86,9 @@ export abstract class FormCompute {
         return nub.CalcSerie(init, this.getParameterRefid(computedParam));
     }
 
+    /**
+     * Triggers computation of the Nub, updates form results
+     */
     public doCompute() {
         this._formResult.resetResults();
 
@@ -81,4 +98,21 @@ export abstract class FormCompute {
             "action": "resultsUpdated",
         }, this._formBase);
     }
+
+    // interface Observer
+
+    public update(sender: any, data: any): void {
+        if (sender instanceof Nub) {
+            switch (data.action) {
+                case "nubResultUpdated":
+                    // forward Nub results update notification to FormCompute objects
+                    this.reaffectResultComponents();
+                    /* console.log("_____forwarding 2");
+                    this._formBase.notifyObservers({
+                        "action": "resultsUpdated",
+                    }, this._formBase); */
+                    break;
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/definition/form-def-parallel-structures.ts b/src/app/formulaire/definition/form-def-parallel-structures.ts
index a4856994d88a189b00a3d07e67a66a25113147b6..5206d9cc0f74141f5474fb99eafef8d12dfd6427 100644
--- a/src/app/formulaire/definition/form-def-parallel-structures.ts
+++ b/src/app/formulaire/definition/form-def-parallel-structures.ts
@@ -1,7 +1,3 @@
-import { CalculatorType, StructureType, LoiDebit } from "jalhyd";
-
-import { FieldSet } from "../fieldset";
-
 /**
  * gestion des formulaires "ouvrages parallèles"
  */
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 4d14b15b7bdadc31e5a26e1c21d09b46d51d9d31..daa57be194f36df52f60597dd48d540606cabee4 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -1,4 +1,4 @@
-import { CalculatorType, ComputeNodeType, Nub, Props, Observer, Session, ParallelStructure } from "jalhyd";
+import { CalculatorType, ComputeNodeType, Nub, Props, Observer, Session } from "jalhyd";
 
 import { FormulaireElement } from "../formulaire-element";
 import { NgParameter, ParamRadioConfig } from "../ngparam";
@@ -94,7 +94,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     }
 
     public initNub(props?: Props) {
-        this._currentNub = this.createNub(props ? props : new Props(this.defaultProperties));
+        this.currentNub = this.createNub(props ? props : new Props(this.defaultProperties));
     }
 
     public get currentNub(): Nub {
@@ -108,7 +108,15 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
                 `Nub ${n.properties["calcType"]} incompatible avec le formulaire ${this._calculatorName} (${this._props["calcType"]})`
             );
         }
+        // unsubscribe from old Nub
+        if (this._currentNub) {
+            this._currentNub.removeObserver(this);
+        }
+        // replace Nub
         this._currentNub = n;
+        // subscribe to new Nub (for result updates)
+        console.log("SET CURRENT NUB -- (re)subscribe to Nub", this._currentNub.uid);
+        this._currentNub.addObserver(this);
     }
 
     /**
@@ -351,6 +359,16 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         }, this);
     }
 
+    /**
+     * Forwards Nub's result updated notification.
+     * Used by FormCompute to update results display
+     */
+    protected notifyNubResultUpdated(sender) {
+        this.notifyObservers({
+            action: "nubResultUpdated"
+        }, sender);
+    }
+
     /**
      * réinitialisation du formulaire suite à un changement d'une valeur, d'une option, ... :
      * effacement des résultats, application des dépendances, ...
@@ -469,5 +487,14 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     //  interface Observer
 
     public update(sender: any, data: any) {
+        console.log("--- FormDefinition received update", sender.constructor.name, data);
+        if (sender instanceof Nub) {
+            switch (data.action) {
+                case "resultUpdated":
+                    // forward Nub results update notification to FormCompute objects
+                    this.notifyNubResultUpdated(sender);
+                    break;
+            }
+        }
     }
 }
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index ffa32a38d50050e230430c5671c7e06bde31ee6e..ff2789eb397c9c9e4cdd7f06657be1d9669e92d7 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -133,9 +133,6 @@ export class FieldSet extends FormulaireElement implements Observer {
 
         if (res) {
             res.parseConfig(json, { "radioConfig": default_radio_config });
-            // set parent Nub on Parameter to ensure UID availability
-            // should always be done @TODO check
-            // res.paramDefinition.parent = this._nub;
         }
 
         return res;
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 809b9d1f74ac8ac7d720ed2f6decc96464700030..10ed519628c99864921882ea8ba5d925b68f5fc1 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -51,7 +51,6 @@ export class NgParameter extends InputField implements Observer {
 
     /**
      * Returns a text preview of the current value(s), depending on the value mode
-     * @TODO use display precision to limit decimals
      */
     public static preview(p: ParamDefinition): string {
         let valuePreview: string;
@@ -107,7 +106,12 @@ export class NgParameter extends InputField implements Observer {
                                         return v.toFixed(nDigits);
                                     }).slice(0, 5).join("; ") + "…";
                                 } else {
-                                    valuePreview = String(p.referencedValue.nub.result.vCalc.toFixed(nDigits));
+                                    const vCalc = p.referencedValue.nub.result.vCalc;
+                                    if (vCalc) {
+                                        valuePreview = String(vCalc.toFixed(nDigits));
+                                    } else {
+                                        throw new Error("NgParameter.preview() : No vCalc for computed target Nub !");
+                                    }
                                 }
                             } else {
                                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
@@ -120,7 +124,6 @@ export class NgParameter extends InputField implements Observer {
                         // was the result already computed ?
                         try {
                             const remoteValues = p.referencedValue.getParamValues();
-                            // @TODO is the computed value fresh or stale ?
                             if (p.referencedValue.hasMultipleValues()) {
                                 // like LIST mode
                                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
@@ -269,8 +272,11 @@ export class NgParameter extends InputField implements Observer {
         throw new Error("invalid parameter radio configuration " + s);
     }
 
-    public getValue() {
-        return this._paramDef.v;
+    /**
+     * Asks the ParamDefinition for its current value
+     */
+    public getValue(triggerChainComputation: boolean = false) {
+        return this._paramDef.getValue(triggerChainComputation);
     }
 
     /**