diff --git a/public/light-flat.css b/public/light-flat.css
index c96628efa69d7a8561fd5ecbe72034d0c8483898..a94a76e701acac20729f91122d0152589285d0b5 100755
--- a/public/light-flat.css
+++ b/public/light-flat.css
@@ -67,6 +67,23 @@ html,body
     border:1px solid #ddd;
 }
 
+#overlay {
+    background: rgb(156, 156, 151);
+    height: 100%;
+    width: 100%;
+    opacity: .9;
+    top: 0;
+    left: 0;
+    position: absolute;
+    padding: 0;
+    transition: opacity .5s;
+}
+
+#overlay:hover {
+    opacity: .9;
+    transition: opacity .5s;
+}
+
 .flexlayout__layout {
     left: 0;
     top: 0;
diff --git a/src/Board.js b/src/Board.js
index a2f34f1508b6ab78998a7884d8e8be029e06a2f0..6ce96b201a8341c2c616a9bbffc1c3c8b698116f 100755
--- a/src/Board.js
+++ b/src/Board.js
@@ -1,8 +1,8 @@
 import React from "react";
+import {toJS} from 'mobx'
+import {observer} from 'mobx-react'
 // Tab-based dynamic layout manager
-import FlexLayout, {Actions} from "flexlayout-react";
-// MobX, a state manager
-import {decorate, observable} from "mobx"
+import FlexLayout from "flexlayout-react";
 // The React Components that are going into tabs
 import {VideoStream, DemoVideoStream} from "./VideoStream"
 import { RosLineChart, DemoLineChart } from "./LineChart"
@@ -15,25 +15,16 @@ import DemoString from './String'
 
 // get the default configuration for the layout
 import {flexlayout_json as json} from './demo-1-config'
+// import {flexlayout_json as json} from './demo-tabbed-config'
 // import {flexlayout_json as json} from './devel-config'
 
-// CSS common to the whole app
-import './Common.css'
-
-class ObservableStore {
-    components = {}
-}
-decorate(ObservableStore, {
-    components: observable
-})
-
+const Board = observer(
 class Board extends React.Component {
     constructor(props) {
         super(props)
         this.state = {model: FlexLayout.Model.fromJson(json)}
 
         this.refLayout = React.createRef();
-        this.componentStore = new ObservableStore();
     }
 
     /** Instanciates the right React Component to be added in a tab
@@ -70,21 +61,15 @@ class Board extends React.Component {
             if (component in tab_types){
                 return tab_types[component];
             } else {
-                return <div>The component type {component} is not declared.
-                    The mistake is either in the configuration (type of a tab) or
-                    in the source file <pre>index.js</pre> (see <pre>tab_types</pre>).
-                    </div>;
+                return invalidComponent(component);
             }
-
-
         }
 
         var component = node.getComponent();
         return React.createElement(getComponentType(component), {
             node: node,
-            updateConfig: this.updateConfig.bind(this, node.getId()),
             ros: this.props.ros,
-            store: this.componentStore
+            store: this.props.store
         }, null);
     }
 
@@ -96,20 +81,18 @@ class Board extends React.Component {
         }, null);
     }
 
-    updateConfig(nodeId, config) {
-        this.state.model.doAction(Actions.updateNodeAttributes(nodeId, {
-            config: config
-        }));
-    }
-
     /**
-     * Add a settings button to the tabsets whose currently activated tab is configurable.
+     * Add settings and readme buttons to the tabsets whose currently activated
+     * tab is configurable and has a readme.
      *
-     * One tab is declared as configurable through its 'configurable' config setting in the model.
-     * The button will toggle the value of the field 'settingsMode' in the configuration of the currently
-     * activated tab in the tabsed.
+     * One tab is configurable when it has a 'configSchema' (JSON Schema
+     * description of its configuration fields) in its MobX store (accessed at
+     * this.props.store.components['ComponentNameHere']). The readme capability
+     * is detected with the presence of a 'readme' field, which is a JSX object
+     * to be displayed as README.
      *
-     * have a look at `App.tsx` in the source code of FlexLayout to understand when this function is called.
+     * have a look at `App.tsx` in the source code of FlexLayout to understand
+     * when this function is called.
      *
      * @param {TabSetNode|BorderNode} node
      * @param {any} renderValues
@@ -119,70 +102,89 @@ class Board extends React.Component {
         // The node could be either a tab set or a border
         if (node.getType() === "tabset") {
             /**
-             * Switch in and out of a display mode (for a widget in the dashboard). This function returns a fuction
-             * that does the real switching for a given mode.
+             * Switch in and out of a modal. This function returns a fuction
+             * that does the real switching for a given modal.
              * 
-             * This mode is handled through a configuration object handled by FlexLayout.
-             * @param {String} modeName name of the mode to be switched
+             * There currently, 2019/07/02, are two modals : readme and settings.
+             * The former is showing information about a gadget and the latter
+             * displays a form to set the configuration of the related gadget.
+             * @param {String} modeName name of the mode to be switched ("readme"
+             *  or "settings")
              * @return function
              */
             function modeToggle(modeName) {
+                const selectedTab = node.getSelectedNode()
+
                 return function() {
-                    // `this` is bound to the button's tabset
-                    const tab = this.getSelectedNode(); // retrieve the selected tab
-                    const config = tab.getConfig(); // get the configuration of this tab
-                    if (!("displayMode" in config) || (config.displayMode !== modeName)) {
-                        if (!this.isMaximized()) { // Maximize the tabset
-                            this._model.doAction(Actions.maximizeToggle(this.getId()));
-                        }
-                        config.displayMode = modeName;
-                        this._model.doAction(
-                            FlexLayout.Actions.updateNodeAttributes(tab.getId(), {config: config}));
+                    // If no modal is active, display the relevant one;
+                    // otherwise, get out of it.
+                    if (!this.props.store.modal.enabled) {
+                        this.props.store.displayModal(modeName, selectedTab.getComponent());
                     } else {
-                        if (this.isMaximized()) { // Return the tabset to its original size
-                            this._model.doAction(Actions.maximizeToggle(this.getId()));
-                        }
-                        delete config.displayMode;
-                        this._model.doAction(
-                            FlexLayout.Actions.updateNodeAttributes(tab.getId(), {config: config}));
+                        this.props.store.exitModal();
                     }
                 }
-            }
+            };
+
+            // Add the readme and settings buttons to the tabset bar, if applicable
 
-            const selected = node.getSelectedNode()
+            const selectedTab = node.getSelectedNode()
             // did we actually get a node?
-            if (typeof selected !== 'undefined') {
-                // is the active (selected) tab declared as configurable?
-                const config = selected.getConfig()
-                if (config && ('configurable' in config) && config['configurable'] === true) {
-                    renderValues.buttons.push(
-                        <img src="images/baseline-settings-20px.svg"
-                            alt="settings"
-                            key="settings"
-                            onClick={modeToggle("settings").bind(node)}/>
-                    )
-                }
-                // is the active (selected) tab declared as having a readme?
-                if (config && ('hasReadme' in config) && config['hasReadme'] === true) {
-                    renderValues.buttons.push(
-                      <img src="images/readme-grey.svg"
-                          alt="readme"
-                          key="readme"
-                          onClick={modeToggle("readme").bind(node)}/>
-                    )
+            if (typeof selectedTab !== 'undefined') {
+                const components = this.props.store.components;
+                // Is there a store for this gadget?
+                if (selectedTab.getComponent() in components) {
+                    const component = components[selectedTab.getComponent()];
+                    // is the active (selected) tab configurable?
+                    if ('configSchema' in component) {
+                        renderValues.buttons.push(
+                            <img src="images/baseline-settings-20px.svg"
+                                alt="settings-test"
+                                key="settings-test"
+                                onClick={modeToggle("settings").bind(this)}/>
+                        );
+                    }
+                    // does the active (selected) tab have a readme?
+                    if ('readme' in component) {
+                        renderValues.buttons.push(
+                            <img src="images/readme-grey.svg"
+                                alt="readme"
+                                key="readme"
+                                onClick={modeToggle("readme").bind(this)}/>
+                        );
+                    }
                 }
             }
         }
     }
 
     render() {
+        // This line is there only to consume a mobX variable and trigger re-render of FlexLayout on changes in components.
+        // We need it so that we can have the "readme" and "settings" buttons displayed in the tabSet bars.
+        // It produces no other side-effect and the return value of this function call is ignored...
+        toJS(this.props.store.components);
+
         return (
             <FlexLayout.Layout ref={this.refLayout}
                                 model={this.state.model}
                                 factory={this.factory.bind(this)}
-                                onRenderTabSet={this.RenderTabsetHandle}/>
+                                onRenderTabSet={this.RenderTabsetHandle.bind(this)}/>
         );
     }
+})
+
+/**
+ * This function creates a placeholder React component in the case where the user
+ * asks for a gadget that does not exist.
+ * @param {String} component name of the required component
+ */
+function invalidComponent(component) {
+    return function(props) {
+        return <div>The component type "{component}"" is not declared.
+                The mistake is either in the configuration (type of a tab) or
+                in the source file <tt>src/Board.js</tt> (search for <tt>tab_types</tt>).
+            </div>;
+    }
 }
 
 export {Board}
diff --git a/src/HorizontalGauge/RosHGauge.js b/src/HorizontalGauge/RosHGauge.js
index 3cdb3a899264aa998492aa6ccfa440ceef468616..7341eb30f552ca3e390ec6776541ba16bbb7e63f 100644
--- a/src/HorizontalGauge/RosHGauge.js
+++ b/src/HorizontalGauge/RosHGauge.js
@@ -1,47 +1,70 @@
 import React, {Component} from 'react'
+import {decorate, observable, computed, autorun} from 'mobx'
+import {observer} from 'mobx-react'
 import RosLib from 'roslib'
 import {get} from 'lodash/object'
 
+import {defaultConfig} from '../utils/configurationHelpers'
 import HorizontalGauge from './index'
 
+class HGaugeStore {
+    readme = <p className="about">This gauge represents a <b>measure</b> ranging from 0 to 1.</p>;
+    configSchema = {
+        title: "Horizontal Gauge",
+        type: "object",
+        required: ["topic", "field", "min", "max"],
+        properties: {
+            topic: {type: "string", title: "ROS topic (absolute name)",
+                    default: "/predictions/interaction_involvement"},
+            field: {type: "string", title: "the field to be read from the message",
+                    default: "prediction_of_head_orientation.list[0].values[1]"},
+            min: {type: "number", title: "Lower end of the data range", default: 0},
+            max: {type: "number", title: "Upper end of the data range", default: 1},
+        }
+    };
+}
+
+const RosHGauge = observer(
 class RosHGauge extends Component {
     constructor(props) {
         super(props)
-        
-        this.state = {datum: null}
+        this.datum = null;
+
+        this.props.store.components['h-gauge'] = new HGaugeStore();
+        defaultConfig(this.props.store.components['h-gauge']);
     }
 
-    static get modes() {
-        return {
-            readme: <p className="about">This gauge represents a <b>measure</b> ranging from 0 to 1.</p>,
-            settingsSchema: {
-                title: "Horizontal Gauge",
-                type: "object",
-                required: ["topic", "field", "min", "max"],
-                properties: {
-                    topic: {type: "string", title: "ROS topic (absolute name)",
-                            default: "/predictions/interaction_involvement"},
-                    field: {type: "string", title: "the field to be read from the message",
-                            default: "prediction_of_head_orientation.list[0].values[1]"},
-                    min: {type: "number", title: "Lower end of the data range", default: 0},
-                    max: {type: "number", title: "Upper end of the data range", default: 1},
-                }
-            },
-        };
+    get topic() {
+        return this.props.store.components['h-gauge'].config.topic;
+    }
+    get field() {
+        return this.props.store.components['h-gauge'].config.field;
+    }
+    get min() {
+        return this.props.store.components['h-gauge'].config.min;
+    }
+    get max() {
+        return this.props.store.components['h-gauge'].config.max;
     }
     
     componentDidMount() {
         if ('ros' in this.props && this.props.ros) {
-            const config = this.props.node.getConfig();
-            this.listener = new RosLib.Topic({
-                ros : this.props.ros,
-                name: config.topic,
-            });
-            
-            this.listener.subscribe(function(message) {
-                this.setState({datum: get(message, config.field)});
-            }.bind(this));
-        }else {
+            this.topicAutorun = autorun( reaction => {
+                if ('listener' in this) {
+                    this.listener.unsubscribe();
+                }
+                this.listener = new RosLib.Topic({
+                    ros : this.props.ros,
+                    name: this.topic,
+                });
+                
+                // callback on the topic, that takes stores the value of a field
+                // (defined in configuration) in 'this.datum'.
+                this.listener.subscribe(function(message) {
+                    this.datum = get(message, this.field);
+                }.bind(this));
+            })
+        } else {
             console.warn('RosHGauge expects to be passed a valid Ros object as property, got ', this.props.ros);
         }
     }
@@ -53,14 +76,19 @@ class RosHGauge extends Component {
     }
     
     render() {
-        const config = this.props.node.getConfig();
-
         return <HorizontalGauge
-            value={this.state.datum}
-            min={config.min}
-            max={config.max}
+            value={this.datum}
+            min={this.min}
+            max={this.max}
             {...this.props}/>;
     }
-}
+})
+decorate(RosHGauge, {
+    datum: observable,
+    topic: computed,
+    field: computed,
+    min: computed,
+    max: computed,
+})
 
 export default RosHGauge
\ No newline at end of file
diff --git a/src/HorizontalGauge/index.js b/src/HorizontalGauge/index.js
index aca98b163416f442df91e95771b29bc68f9750f3..7959d5c24bbd2b652bffe0f3949fc952148b64b6 100644
--- a/src/HorizontalGauge/index.js
+++ b/src/HorizontalGauge/index.js
@@ -1,11 +1,6 @@
-import React from 'react'
 import HorizontalGauge from './HozirontalGauge'
 import RosHGauge from './RosHGauge'
-import {Modal} from '../utils/modeHandler'
 
 export default HorizontalGauge;
 
-function modal(props){
-  return <Modal component={RosHGauge} {...props}/>;
-}
-export {modal as RosHGauge};
\ No newline at end of file
+export {RosHGauge};
\ No newline at end of file
diff --git a/src/InteractionTrace/InteractionTrace.js b/src/InteractionTrace/InteractionTrace.js
index 689c0fdc9ca48de25262d520591c214f11d93d23..021e3df75175ec887fca6efec0b0831eabac283b 100644
--- a/src/InteractionTrace/InteractionTrace.js
+++ b/src/InteractionTrace/InteractionTrace.js
@@ -1,8 +1,9 @@
 import React, {Component} from 'react'
-import {decorate, observable} from 'mobx'
+import {decorate, observable, computed} from 'mobx'
 import {observer} from 'mobx-react'
 import * as d3 from 'd3'
 
+import {defaultConfig} from '../utils/configurationHelpers'
 import LinearLayout from '../utils/LinearLayout'
 import { extent } from '../utils/extent';
 import { getBoredom, getIValence, getEValence } from './accessors';
@@ -13,10 +14,50 @@ import { Backgrounds, Iteration, Actions, Mood,
 class InteractionTraceStore {
   currentInteraction = null;
   showInteraction = false;
+  config = {};
+  configSchema = {
+    title: "Interaction trace",
+    type: "object",
+    properties: {
+      maxBoredom: {
+        title: "Maximal expected value for the boredom (used for scaling)",
+        type: "integer",
+        default: 2,
+      },
+      nbPrimitiveActions: {
+        title: "How many primitive actions are displayed",
+        type: "integer",
+        default: 3,
+      },
+      widthInEm: {
+        title: "The width of each column in em units",
+        type: "number",
+        default: 2.5,
+      },
+      debug: {
+        title: "Debug mode",
+        type: "boolean",
+        default: false
+      }
+    }
+  };
+  readme = (
+    <div className="about">
+      <h1>Trace of the reasoning process performed by the agent</h1>
+      <p>The <b>mood</b> represents the effect of the <b>valence</b> on the mood of the agent.</p>
+      <h2>Boredom</h2>
+      <p>
+        Observe the evolution of the <b>interactions</b> performed by the agent. <b>Intended</b> interactions are the
+        interactions the agent plans to perform whereas <b>enacted</b> interactions are the interactions actually
+        performed in the physical world of the agent. An interaction is composed of an <b>action</b> and a
+        <b>valence</b>, the valence being dependent of the response from the environment.
+      </p>
+    </div>);
 }
 decorate(InteractionTraceStore, {
   currentInteraction: observable,
   showInteraction: observable,
+  config: observable,
 })
 
 const InteractionTrace = observer(
@@ -27,10 +68,11 @@ class InteractionTrace extends Component {
 
     this.interactionStore = new InteractionTraceStore();
     this.props.store.components['int-trace'] = this.interactionStore;
+    // Fill the undefined fields of the configuration object with their default values.
+    defaultConfig(this.interactionStore);
 
-    // Use the margin convention practice
+    // Use the margin convention practice of D3
     this.margin = {top: 20, right: 30, bottom: 20, left: 20};
-    // this.margin = {top: 0, right: 0, bottom: 0, left: 0};
 
     // Blanck space between each element of the figure
     this.spacing = {v: 20, h: 15};
@@ -54,38 +96,37 @@ class InteractionTrace extends Component {
       .ratioCell(0.5) // Row for the valences (takes a ratio of the avalable space)
       .fixedCell(75); // Row for the actions (temporary value, updated later in function of this.em)
 
-    const config = this.props.node.getConfig();
-    // Value for the boredom at which we plot a red line (showing max value)
-    this.maxBoredom = config.maxBoredom;
-    // How many primitive actions (at least) we want to display
-    this.nbPrimitiveActions = config.nbPrimitiveActions;
-
-    // Will store the conversion from 1em in px for the LabelColumn.
-    // This is a tricky trick to set the size of the LabelColumn to match with the longest chain of actions that
-    // it will have to show.
+    // Will store the conversion from 1em in px. We have to assume that this computation is approximative.
     this.em = undefined;
-
-    // In debug mode, we add bounding boxes for the different areas of this figure
-    this.debug = config.debug;
-
-    // Other parameter: how many em the minimal column width has to be (function minXWidth).
   }
 
-  // The smallest allowed space around each point on the x axis.
-  minXWidth() {
-    return 2.5*this.em;
+  /** In debug mode, we add bounding boxes for the different areas of this figure */ 
+  get debug() {
+    return this.interactionStore.config.debug;
+  }
+  /** A red line, showing the maximum accepted boredom level, will be displayed using this value */
+  get maxBoredom() {
+    return this.interactionStore.config.maxBoredom;
+  }
+  /** How many primitive actions (at least) we want to display */
+  get nbPrimitiveActions() {
+    return this.interactionStore.config.nbPrimitiveActions;
+  }
+  /** The smallest allowed space around each point on the x axis. */
+  get minXWidth() {
+    return this.interactionStore.config.widthInEm * this.em;
   }
 
   /** Get the font size of a node in the DOM. It tells us how big one em is in px.
    *
-   * This value is used to adjust the width of the LabelColumn based on the width of its content.
+   * This value is used to adjust the width of the columns in the trace, based on the size of their content (which has text).
    *
    * @param {DOMnode} node the DOM node from which we retrieve information
    */
   getEmSize(node) {
-      if (node) {
-          this.em = parseFloat(getComputedStyle(node).fontSize);
-      }
+    if (node) {
+      this.em = parseFloat(getComputedStyle(node).fontSize);
+    }
   }
 
   /**
@@ -96,7 +137,7 @@ class InteractionTrace extends Component {
    *  the second is a function that will return true iff the datum we give it belongs to the domain.
    */
   filter(data) {
-    const maxNumberOfInteractions = Math.floor(this.xLayout.width(2) / this.minXWidth()) || 0;
+    const maxNumberOfInteractions = Math.floor(this.xLayout.width(2) / this.minXWidth) || 0;
     const highestId = d3.max(data, (datum => datum.id)) || 0;
     const xDomain = [highestId-maxNumberOfInteractions, highestId];
     const domainFilter = (datum) => (datum.id >= xDomain[0] && datum.id <= xDomain[1]);
@@ -343,6 +384,12 @@ class InteractionTrace extends Component {
   }
 }
 )
+decorate(InteractionTrace, {
+  debug: computed,
+  maxBoredom: computed,
+  nbPrimitiveActions: computed,
+  em: observable,
+});
 export default InteractionTrace;
 
 class MakeSVGComponents extends Component {
diff --git a/src/InteractionTrace/RosInteractionTrace.js b/src/InteractionTrace/RosInteractionTrace.js
index 511477e4899d89b5b1d7259ca4ad591d5321aa78..d957505659fa9c12259de40cce9f9663a1c24e4b 100644
--- a/src/InteractionTrace/RosInteractionTrace.js
+++ b/src/InteractionTrace/RosInteractionTrace.js
@@ -1,102 +1,102 @@
 import React, {Component} from 'react'
-import {Modal} from '../utils/modeHandler'
-import {observable, decorate} from 'mobx'
+import {observable, decorate, computed, autorun} from 'mobx'
 import {observer} from 'mobx-react'
 import sizeMe from 'react-sizeme'
 import RosLib from 'roslib'
 
+import {appendConfigSchema} from '../utils/configurationHelpers'
 import InteractionTrace from './InteractionTrace'
 
 const RosInteractionTrace = observer(
 class RosInteractionTrace extends Component {
+  newSchema = {
+    required: ["trace", "boredom", "mood"],
+    properties: {
+      trace: {
+        title: "Topic for the trace", 
+        type: "string",
+        default: "/algorithm/trace"
+      },
+      boredom: {
+        title: "Topic for the boredom", 
+        type: "string",
+        default: "/algorithm/boredom"
+      },
+      mood: {
+        title: "Topic for the mood", 
+        type: "string",
+        default: "/algorithm/mood"
+      },
+      lowerIdResets: {
+        title: "Reset the display when a new interaction arrives with an ID lower than the latest one?",
+        type: "boolean",
+        default: true,
+      }
+    }
+  };
+
   constructor(props) {
     super(props)
     
     this.data = [];
   }
 
-  static get modes() {
-    return {
-      readme: (
-      <div className="about">
-        <h1>Trace of the reasoning process performed by the agent</h1>
-        <p>The <b>mood</b> represents the effect of the <b>valence</b> on the mood of the agent.</p>
-        <h2>Boredom</h2>
-        <p>
-          Observe the evolution of the <b>interactions</b> performed by the agent. <b>Intended</b> interactions are the
-          interactions the agent plans to perform whereas <b>enacted</b> interactions are the interactions actually
-          performed in the physical world of the agent. An interaction is composed of an <b>action</b> and a
-          <b>valence</b>, the valence being dependent of the response from the environment.
-        </p>
-      </div>),
-      settingsSchema: {
-        title: "Interaction trace",
-        type: "object",
-        required: ["trace", "boredom", "mood"],
-        properties: {
-          trace: {
-            title: "Topic for the trace", 
-            type: "string",
-            default: "/algorithm/trace"
-          },
-          boredom: {
-            title: "Topic for the boredom", 
-            type: "string",
-            default: "/algorithm/boredom"
-          },
-          mood: {
-            title: "Topic for the mood", 
-            type: "string",
-            default: "/algorithm/mood"
-          },
-          lowerIdResets: {
-            title: "Reset the display when a new interaction arrives with an ID lower than the latest one?",
-            type: "boolean",
-            default: true,
-          },
-          maxBoredom: {
-            title: "Maximal expected value for the boredom (used for scaling)",
-            type: "integer",
-            default: 2,
-          },
-          nbPrimitiveActions: {
-            title: "How many primitive actions are displayed",
-            type: "integer",
-            default: 3,
-          },
-          debug: {
-            title: "Debug mode",
-            type: "boolean",
-            default: false
-          }
-        }
-      }
-    };
+  get traceTopic() {
+    return this.props.store.components['int-trace'].config.trace;
+  }
+
+  get boredomTopic() {
+    return this.props.store.components['int-trace'].config.boredom;
+  }
+
+  get moodTopic() {
+    return this.props.store.components['int-trace'].config.mood;
+  }
+
+  get lowerIdResets() {
+    return this.props.store.components['int-trace'].config.lowerIdResets;
   }
   
   componentDidMount() {
+    // Append the configuration schema of this component to the one of InteractionTrace
+    // Doing so, the configuration form will have all fields for both components.
+    appendConfigSchema(this.props.store.components['int-trace'], this.newSchema);
+    
     if ('ros' in this.props && this.props.ros) {
-      const config = this.props.node.getConfig();
-      this.trace_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: config.trace,
-        messageType: "april_messages/trace"
-      });
-      this.trace_listener.subscribe(this.interaction_to_data.bind(this));
+      // With this MobX autorun, if the topics are changed in the configuration,
+      // we will automatically unsubscribe from the old ones and register to the
+      // new topic names.
+      this.topicAutorun = autorun( reaction => {
+        if ('trace_listener' in this) {
+          this.trace_listener.unsubscribe();
+        }
+        this.trace_listener = new RosLib.Topic({
+          ros : this.props.ros,
+          name: this.traceTopic,
+          messageType: "april_messages/trace"
+        });
+        this.trace_listener.subscribe(this.interaction_to_data.bind(this));
 
-      this.boredom_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: config.boredom,
-        messageType: "april_messages/boredom"
-      });
-      this.boredom_listener.subscribe(this.boredom_to_data.bind(this));
+        if ('boredom_listener' in this) {
+          this.boredom_listener.unsubscribe();
+        }
+        this.boredom_listener = new RosLib.Topic({
+          ros : this.props.ros,
+          name: this.boredomTopic,
+          messageType: "april_messages/boredom"
+        });
+        this.boredom_listener.subscribe(this.boredom_to_data.bind(this));
 
-      this.mood_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: config.mood,
-        messageType: "april_messages/mood"
+        if ('mood_listener' in this) {
+          this.mood_listener.unsubscribe();
+        }
+        this.mood_listener = new RosLib.Topic({
+          ros : this.props.ros,
+          name: this.moodTopic,
+          messageType: "april_messages/mood"
+        });
+        this.mood_listener.subscribe(this.mood_to_data.bind(this));
       });
-      this.mood_listener.subscribe(this.mood_to_data.bind(this));
     } else {
       console.warn('RosInteractionTrace expects to be passed a valid Ros object as property, got ', this.props.ros);
     }
@@ -156,8 +156,7 @@ class RosInteractionTrace extends Component {
    */
   mergeDataInState(newDatum) {
     // Empty the data and get the first interaction of an experiment
-    const config = this.props.node.getConfig();
-    if (config.lowerIdResets && this.data.length > 0 && newDatum.id < this.data[this.data.length-1].id) {
+    if (this.lowerIdResets && this.data.length > 0 && newDatum.id < this.data[this.data.length-1].id) {
       this.data = [];
     }
 
@@ -173,11 +172,10 @@ class RosInteractionTrace extends Component {
 )
 decorate(RosInteractionTrace, {
   data: observable,
-  // lowerIdResets: observable,
+  traceTopic: computed,
+  boredomTopic: computed,
+  moodTopic: computed,
+  lowerIdResets: computed,
 })
 
-const ModalRosInteractionTrace = observer(function(props) {
-  return <Modal component={RosInteractionTrace} {...props}/>
-});
-
-export default sizeMe({monitorHeight: true})(ModalRosInteractionTrace);
\ No newline at end of file
+export default sizeMe({monitorHeight: true})(RosInteractionTrace);
\ No newline at end of file
diff --git a/src/LatestInteraction/RosLastestInteraction.js b/src/LatestInteraction/RosLastestInteraction.js
index 2899cc9ece45e1c9fdd73c168dff61b7364f731d..49031be36e3f24f7d3a1603db2f6ec13ef78834c 100644
--- a/src/LatestInteraction/RosLastestInteraction.js
+++ b/src/LatestInteraction/RosLastestInteraction.js
@@ -1,64 +1,89 @@
 import React, {Component} from 'react'
 import RosLib from 'roslib'
+import {autorun, decorate, observable} from 'mobx'
+import {observer} from 'mobx-react'
 
-import {Modal} from '../utils/modeHandler'
+import {defaultConfig} from '../utils/configurationHelpers'
 import {LatestInteraction} from './LatestInteraction'
 
+class LatestInteractionStore {
+  readme = (
+    <div className="about">
+      <p>
+        <b>Last action</b> done by the agent and its <b>valence</b>.
+      </p>
+      <p>
+        If you place your mouse pointer over an <b>action</b> in the interaction trace, this widget will display
+        the <b>full interaction</b>.
+      </p>
+    </div>);
+  config = {};
+  configSchema = {
+    title: "Interaction trace",
+    type: "object",
+    required: ["trace"],
+    properties: {
+      trace: {
+        title: "Topic for the trace", 
+        type: "string",
+        default: "/algorithm/trace"
+      },
+    }
+  };
+}
+decorate(LatestInteractionStore, {
+  config: observable,
+})
+
+const RosLatestInteraction = observer(
 class RosLatestInteraction extends Component {
   constructor(props) {
-    super(props)
+    super(props);
 
-    this.state = {intended: {
-      action_names: [],
-      primitive_valences: []
-    }, enacted: {
-      action_names: [],
-      primitive_valences: []
-    }};
-    this.trace = {
-      topic: "/algorithm/trace",
-      type: "april_messages/trace"
-    }
-  }
+    this.props.store.components['latest-interaction'] = new LatestInteractionStore();
+    // Fill the undefined fields of the configuration object with their default values.
+    defaultConfig(this.props.store.components['latest-interaction']);
 
-  static get modes() {
-    return {
-      readme: (
-        <div className="about">
-          <p>
-            <b>Last action</b> done by the agent and its <b>valence</b>.
-          </p>
-          <p>
-            If you place your mouse pointer over an <b>action</b> in the interaction trace, this widget will display
-            the <b>full interaction</b>.
-          </p>
-        </div>),
-      settingsSchema: {
-        title: "Interaction trace",
-        type: "object",
-        required: ["trace"],
-        properties: {
-          trace: {
-            title: "Topic for the trace", 
-            type: "string",
-            default: "/algorithm/trace"
-          },
-        }
+    this.latestInteraction = {
+      intended: {
+        action_names: [],
+        primitive_valences: []
+      },
+      enacted: {
+        action_names: [],
+        primitive_valences: []
       }
     };
+    this.topicType = "april_messages/trace";
+  }
+
+  get topicName() {
+    return this.props.store.components['latest-interaction'].config.trace;
   }
 
   componentDidMount() {
     if ('ros' in this.props && this.props.ros) {
-      this.trace_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: this.trace.topic,
-        messageType: this.trace.type
-      });
-      this.trace_listener.subscribe(message => {
-        this.setState(message);
+      // This will be executed now and anytime later, if the topic name or type
+      // is modified
+      this.topicAutorun = autorun(reaction => {
+        // Unsubscribe if there was a subscription
+        if ('trace_listener' in this) {
+          this.trace_listener.unsubscribe();
+        }
+
+        // Create a listener
+        this.trace_listener = new RosLib.Topic({
+          ros : this.props.ros,
+          name: this.topicName,
+          messageType: this.topicType
+        });
+        
+        // Subscribe (and declare a callback)
+        this.trace_listener.subscribe(message => {
+          this.latestInteraction = message;
+        });
       });
-    }else {
+    } else {
       console.warn('RosMood expects to be passed a valid Ros object as property, got ', this.props.ros);
     }
   }
@@ -70,12 +95,13 @@ class RosLatestInteraction extends Component {
   }
 
   render() {
-    return <LatestInteraction intended={this.state.intended} enacted={this.state.enacted} {...this.props}/>
+    return <LatestInteraction intended={this.latestInteraction.intended}
+      enacted={this.latestInteraction.enacted}
+      {...this.props}/>
   }
-}
-
-const ModalLatestInteraction = function(props) {
-  return <Modal component={RosLatestInteraction} {...props}/>
-};
+})
+decorate(RosLatestInteraction, {
+  latestInteraction: observable,
+});
 
-export { ModalLatestInteraction as RosLatestInteraction };
+export {RosLatestInteraction };
diff --git a/src/LineChart/RosLineChart.js b/src/LineChart/RosLineChart.js
index e45e8e23ea7c5ec863306eafa5f45d87ad302bcf..e90c012bbedf1129e7ddaa542850465313d612d6 100644
--- a/src/LineChart/RosLineChart.js
+++ b/src/LineChart/RosLineChart.js
@@ -1,123 +1,133 @@
 import React, {Component} from 'react';
 import property from 'lodash/property';
+import {autorun, decorate, observable, action} from 'mobx'
+import {observer} from 'mobx-react'
 import RosLib from 'roslib';
 
-import {Modal} from '../utils/modeHandler'
+import {defaultConfig} from '../utils/configurationHelpers'
 import {SizedLineChart} from './LineChart'
 
+class LineChartStore {
+    configSchema = {
+        title: "Generic plot",
+        type: "object",
+        required: ["curves", "maxPoints"],
+        properties: {
+            curves: {
+                title: "Plotted lines",
+                type: "array",
+                minItems: 1,
+                maxItems: 10,
+                default: [{
+                    topic: "/predictions/interaction_involvement",
+                    field: "prediction_of_head_orientation.list[0].values[1]"
+                }],
+                items: {
+                    type: "object",
+                    properties: {
+                        topic: {
+                            title: "topic", 
+                            type: "string",
+                            default: "/std_msgs/int64"
+                        },
+                        field: {
+                            title: "field",
+                            type: "string",
+                            default: "data[0]"
+                        }
+                    }
+                },
+            },
+            maxPoints: {
+                title: "How many data points are plotted",
+                type: "integer",
+                minimum: 0,
+                maximum: 200,
+                default: 50,
+            },
+            xLabel: {
+                title: "Label of abscissa",
+                type: "string",
+            },
+            yLabel: {
+                title: "Label of ordinate",
+                type: "string",
+                default: "Involvement",
+            },
+        }
+    };
+    config = {};
+    readme = (
+        <div className="about">
+            <p>
+                This linechart represents a measure evolving over time. It is possible to choose
+                any viable ROS topic to be plotted.
+            </p>
+            <p>
+                For readibility's sake, we chose to limit the number of curves to ten on a single plot. Hopefully
+                you would never reach such an unreasonable number.
+            </p>
+        </div>
+    );
+}
+decorate(LineChartStore, {
+    config: observable,
+})
+
+const RosLineChart = observer(
 class RosLineChart extends Component {
     constructor(props) {
         super(props);
 
+        this.store = new LineChartStore();
+        this.props.store.components['line-chart'] = this.store;
+        // Fill the undefined fields of the configuration object with their default values.
+        defaultConfig(this.store);
+
         this.listeners = [];
-        this.state = {
-            data: {"" : []}
-        };
+        this.data = observable.map();
     }
 
-    static get modes() {
-        return {
-            readme: (
-                <div className="about">
-                    <p>
-                        This linechart represents a measure evolving over time. It is possible to choose
-                        any viable ROS topic to be plotted.
-                    </p>
-                    <p>
-                        For readibility's sake, we chose to limit the number of curves to ten on a single plot. Hopefully
-                        you would never reach such an unreasonable number.
-                    </p>
-                </div>
-            ),
-            settingsSchema: {
-                title: "Generic plot",
-                type: "object",
-                required: ["curves", "maxPoints"],
-                properties: {
-                    curves: {
-                        title: "Plotted lines",
-                        type: "array",
-                        minItems: 1,
-                        maxItems: 10,
-                        default: [{
-                            topic: "/predictions/interaction_involvement",
-                            field: "prediction_of_head_orientation.list[0].values[1]"
-                        }],
-                        items: {
-                            type: "object",
-                            properties: {
-                                topic: {
-                                    title: "topic", 
-                                    type: "string",
-                                    default: "/std_msgs/int64"
-                                },
-                                field: {
-                                    title: "field",
-                                    type: "string",
-                                    default: "data[0]"
-                                }
-                            }
-                        },
-                    },
-                    maxPoints: {
-                        title: "How many data points are plotted",
-                        type: "integer",
-                        minimum: 0,
-                        maximum: 200,
-                        default: 50,
-                    },
-                    xLabel: {
-                        title: "Label of abscissa",
-                        type: "string",
-                    },
-                    yLabel: {
-                        title: "Label of ordinate",
-                        type: "string",
-                        default: "Involvement",
-                    },
-                }
-            }
-        };
+    /**
+     * Extract a field from a ros message and add it to the state, for plotting.
+     * 
+     * If there are already maxPoints in storage, we remove the oldest data point.
+     * @param {Object} curve has two entries, `field`, path to the field to be extracted from incoming messages
+     *  and `topic` the ROS topic from which we extract the field.
+     * @param {integer} maxPoints mupper limit on the number of data entries for the plot
+     * @param {Object} message messages from a ROS topic
+     */
+    process_message(curve, maxPoints, message) {
+        const field = curve.field;
+        const data_field = curve.topic+field;
+        if (!this.data.has(data_field)) {
+            this.data.set(data_field, []);
+        }
+        if (this.data.get(data_field).length >= maxPoints) {
+            this.data.get(data_field).shift();
+        }
+        this.data.set(data_field, this.data.get(data_field).concat([{"y": property(field)(message)}]));
     }
 
     componentDidMount() {
         if ('ros' in this.props && this.props.ros) {
-            /**
-             * Extract a field from a ros message and add it to the state, for plotting.
-             * 
-             * If there are already maxPoints in storage, we remove the oldest data point.
-             * @param {Object} curve has two entries, `field`, path to the field to be extracted from incoming messages
-             *  and `topic` the ROS topic from which we extract the field.
-             * @param {integer} maxPoints mupper limit on the number of data entries for the plot
-             * @param {Object} message messages from a ROS topic
-             */
-            function process_message(curve, maxPoints, message) {
-                this.setState((state) => {
-                    let new_data = this.state.data;
-                    const field = curve.field;
-                    const data_field = curve.topic+field;
-                    if (!(data_field in new_data)) {
-                        new_data[data_field] = []
-                    }
-                    if (new_data[data_field].length >= maxPoints) {
-                        new_data[data_field].shift();
-                    }
-                    new_data[data_field] = this.state.data[data_field].concat([{"y": property(field)(message)}])
-                    return {
-                        data: new_data,
-                    }
-                });
-            }
-
-            // Create a ROS topic listener for each curve (topic + field) in the configuration
-            const config = this.props.node.getConfig();
-            if ('curves' in config) {
-                config.curves.forEach((curve) => {
-                    const listener = new RosLib.Topic({ros : this.props.ros, name: curve.topic});
-                    listener.subscribe(process_message.bind(this, curve, config.maxPoints));
-                    this.listeners.push(listener);
-                });
-            }
+            this.topicAutorun = autorun(reaction => {
+                if ('listeners' in this) {
+                    this.listeners.forEach((listener) => {listener.unsubscribe();});
+                }
+                // Create a ROS topic listener for each curve (topic + field) in the configuration
+                if ('curves' in this.store.config) {
+                    this.store.config.curves.forEach((curve) => {
+                        const listener = new RosLib.Topic({
+                            ros : this.props.ros,
+                            name: curve.topic});
+                        listener.subscribe(
+                            this.process_message.bind(this, curve,
+                                    this.store.config.maxPoints));
+                        this.listeners.push(listener);
+                    });
+                }
+            });
         } else {
             console.warn('RosLineChart expects to be passed a valid Ros object as property, got ', this.props.ros);
         }
@@ -130,13 +140,15 @@ class RosLineChart extends Component {
     }
 
     render () {
-        const config = this.props.node.getConfig()
-        return <SizedLineChart data={this.state.data} xLabel={config.xLabel} yLabel={config.yLabel} {...this.props}/>
+        return <SizedLineChart data={Object.fromEntries(this.data)}
+            xLabel={this.store.config.xLabel}
+            yLabel={this.store.config.yLabel}
+            {...this.props}/>
     }
-}
-
-const ModalLineChart = function(props) {
-    return <Modal component={RosLineChart} {...props}/>
-};
+})
+decorate(RosLineChart, {
+    data: observable,
+    process_message: action,
+})
 
-export {ModalLineChart as RosLineChart}
+export {RosLineChart}
diff --git a/src/MemoryRanked/MemoryRanked.js b/src/MemoryRanked/MemoryRanked.js
index f724953589796f4b1f01b5f15430a29658704d0c..f39c5222760e49b96d548ce7f593c150110845b2 100644
--- a/src/MemoryRanked/MemoryRanked.js
+++ b/src/MemoryRanked/MemoryRanked.js
@@ -1,18 +1,101 @@
 import React, {Component} from 'react';
-import sizeMe from 'react-sizeme';
+import {decorate, observable, computed} from 'mobx'
+import {observer} from 'mobx-react'
 import {property} from 'lodash'
 
+import {defaultConfig} from '../utils/configurationHelpers'
 import './MemoryRanked.css';
 import LinearLayout from '../utils/LinearLayout'
 import { Scatter } from './Scatter';
 import { LabelColumn } from './LabelColumn';
 import { NumberColumn } from './NumberColumn';
 
+class MemoryStore {
+  readme = (
+    <div>
+      <h1>Display the sequences of the interactions learnt by the agent</h1>
+      <p>
+        Each time the agent performs a <b>sequence of interactions</b> in the environment, it records the
+        corresponding <b>valence</b> and stores it in the memory. Each record is called an <b>occurence</b>
+        of the sequence.
+      </p>
+    </div>);
+  configSchema = {
+    title: "Robot's memory",
+    type: "object",
+    required: ["guideLinesStep", "maxActionLength", "sorting"],
+    properties: {
+      guideLinesStep: {
+        title: "How many entries between two horizontal rules",
+        type: "integer",
+        minimum: 2,
+        default: 3,
+      },
+      maxActionLength: {
+        title: "The highest number of actions displayed in one memory entry",
+        type: "integer",
+        minimum: 2,
+        default: 10,
+      },
+      sorting: {
+        title: "How to sort the entries of the memory",
+        type: "object",
+        required: ["value", "order"],
+        default: {
+          value: "valence",
+          order: "descending"
+        },
+        properties: {
+          value: {
+            title: "Value used for sorting",
+            type: "string",
+            default: "valence",
+            anyOf: [
+              {
+                title: "valence",
+                type: "string",
+                enum: ["valence"]
+              },
+              {
+                title: "action length",
+                type: "string",
+                enum: ["action_names.length"]
+              },
+              {
+                title: "number of occurences",
+                type: "string",
+                enum: ["occurence"]
+              },
+            ],
+          },
+          order: {
+            title: "Order of sorting",
+            type: "string",
+            default: "descending",
+            enum: [
+              "ascending",
+              "descending"
+            ]
+          }
+        }
+      }
+    }
+  };
+  config = {};
+}
+decorate(MemoryStore, {
+  config: observable
+})
+
+const MemoryRanked = observer(
 class MemoryRanked extends Component {
   constructor(props) {
     super(props);
 
-    const config = this.props.node.getConfig();
+    this.store = new MemoryStore();
+    this.props.store.components['memory'] = this.store;
+    // Fill the undefined fields of the configuration object with their default values.
+    defaultConfig(this.store);
     
     // Use the margin convention practice
     this.margin = {top: 10, right: 20, bottom: 20, left: 20}
@@ -28,12 +111,20 @@ class MemoryRanked extends Component {
     // The increment in Y coordinate between two entries in the memory
     this.vStep = 30;
     
-    // How frequent the horizontal guide lines should be put
-    // If set to 4, one guide line will be displayed every four line of data
-    this.guideLinesStep = config.guideLinesStep; 
+    // // How frequent the horizontal guide lines should be put
+    // // If set to 4, one guide line will be displayed every four line of data
+    // this.guideLinesStep = config.guideLinesStep; 
     
-    // The highest number of actions displayed in one memory entry.
-    this.maxActionLength = config.maxActionLength;
+    // // The highest number of actions displayed in one memory entry.
+    // this.maxActionLength = config.maxActionLength;
+  }
+
+  get guideLinesStep() {
+    return this.store.config.guideLinesStep;
+  }
+
+  get maxActionLength() {
+    return this.store.maxActionLength;
   }
   
   /** Get the font size of a node in the DOM. It tells us how big one em is in px.
@@ -80,11 +171,11 @@ class MemoryRanked extends Component {
     let sorter;
     // Make a sorting function based on the configuration or, by default, sort
     // by descending valence.
-    const config = this.props.node.getConfig();
-    if ('sorting' in config) {
-      const getter = property(config.sorting.value);
+    if ('sorting' in this.store.config) {
+      const getter = property(this.store.config.sorting.value);
+      const order = this.store.config.sorting.order;
       sorter = function(a, b) {
-        if(config.sorting.order === "ascending") {
+        if(order === "ascending") {
           return getter(a) - getter(b);
         } else {
           return getter(b) - getter(a);
@@ -148,14 +239,13 @@ class MemoryRanked extends Component {
     ];
 
 
-    const config = this.props.node.getConfig()
-    if (config && 'displayMode' in config) {
-      if (config.displayMode === "readme") {
+    if (this.store.config && 'displayMode' in this.store.config) {
+      if (this.store.config.displayMode === "readme") {
         return <div className="about">
           <h1>Display the sequences of the interactions learnt by the agent</h1>
           <p>Each time the agent performs a <b>sequence of interactions</b> in the environment, it records the corresponding <b>valence</b> and stores it in the memory. Each record is called an <b>occurence</b> of the sequence.</p>
         </div>;
-      } else if (config.displayMode === "settings") {
+      } else if (this.store.config.displayMode === "settings") {
         return <div>
           <p>config panel</p>
         </div>;
@@ -225,6 +315,10 @@ class MemoryRanked extends Component {
       </g>
       );
   }
-}
+})
+decorate(MemoryRanked, {
+  guideLinesStep: computed,
+  maxActionLength: computed,
+})
     
-export default sizeMe({monitorHeight: true})(MemoryRanked);
\ No newline at end of file
+export default MemoryRanked;
\ No newline at end of file
diff --git a/src/MemoryRanked/RosMemoryRanked.js b/src/MemoryRanked/RosMemoryRanked.js
index ee418cea51cb81d1a57e8e34881648771354a4b8..1fa39d09e15d8144820a8a42ba89033b2fe4414b 100644
--- a/src/MemoryRanked/RosMemoryRanked.js
+++ b/src/MemoryRanked/RosMemoryRanked.js
@@ -1,126 +1,78 @@
 import React, {Component} from 'react';
+import {observable, decorate, computed, toJS, autorun} from 'mobx'
+import {observer} from 'mobx-react'
+import sizeMe from 'react-sizeme';
 import RosLib from 'roslib';
 
-import {Modal} from '../utils/modeHandler'
+import {appendConfigSchema} from '../utils/configurationHelpers'
 import MemoryRanked from './MemoryRanked'
 
+const RosMemoryRanked = sizeMe({monitorHeight: true})(observer(
 class RosMemoryRanked extends Component {
     constructor(props) {
         super(props);
 
-        this.state = {data: []};
+        this.data = observable.array();
     }
 
-    static get modes() {
-        return {
-            readme: (
-                <div>
-                  <h1>Display the sequences of the interactions learnt by the agent</h1>
-                  <p>
-                    Each time the agent performs a <b>sequence of interactions</b> in the environment, it records the
-                    corresponding <b>valence</b> and stores it in the memory. Each record is called an <b>occurence</b>
-                    of the sequence.
-                  </p>
-                </div>),
-            settingsSchema: {
-                title: "Robot's memory",
-                type: "object",
-                required: ["guideLinesStep", "maxActionLength", "topic", "sorting"],
-                properties: {
-                    topic: {
-                        title: "Topic for the memory",
-                        type: "string",
-                        default: "/algorithm/memory"
-                    },
-                    guideLinesStep: {
-                        title: "How many entries between two horizontal rules",
-                        type: "integer",
-                        minimum: 2,
-                        default: 3,
-                    },
-                    maxActionLength: {
-                        title: "The highest number of actions displayed in one memory entry",
-                        type: "integer",
-                        minimum: 2,
-                        default: 10,
-                    },
-                    sorting: {
-                        title: "How to sort the entries of the memory",
-                        type: "object",
-                        required: ["value", "order"],
-                        default: {
-                            value: "valence",
-                            order: "descending"
-                        },
-                        properties: {
-                            value: {
-                                title: "Value used for sorting",
-                                type: "string",
-                                default: "valence",
-                                anyOf: [
-                                    {
-                                        title: "valence",
-                                        type: "string",
-                                        enum: ["valence"]
-                                    },
-                                    {
-                                        title: "action length",
-                                        type: "string",
-                                        enum: ["action_names.length"]
-                                    },
-                                    {
-                                        title: "number of occurences",
-                                        type: "string",
-                                        enum: ["occurence"]
-                                    },
-                                ],
-                            },
-                            order: {
-                                title: "Order of sorting",
-                                type: "string",
-                                default: "descending",
-                                enum: [
-                                    "ascending",
-                                    "descending"
-                                ]
-                            }
-                        }
-                    },
-                }
-            }
+    newSchema = {
+        title: "Robot's memory",
+        type: "object",
+        required: ["topic"],
+        properties: {
+            topic: {
+                title: "Topic for the memory",
+                type: "string",
+                default: "/algorithm/memory"
+            },
         }
+    };
+
+    get topic() {
+        return this.props.store.components['memory'].config.topic;
     }
 
     componentDidMount() {
+        // Append the configuration schema of this component to the one of MemoryRanked
+        // Doing so, the configuration form will have all fields for both components.
+        appendConfigSchema(this.props.store.components['memory'], this.newSchema);
+
         if ('ros' in this.props && this.props.ros) {
-            const config = this.props.node.getConfig();
-            const topicConfig = {
-                ros : this.props.ros,
-                name: config.topic,
-                messageType: "april_messages/memory",
-            };
-            this.listener = new RosLib.Topic(topicConfig);
-            this.listener.subscribe(this.memory_to_data.bind(this));
+            // With this MobX autorun, if the topics are changed in the configuration,
+            // we will automatically unsubscribe from the old ones and register to the
+            // new topic names.
+            this.topicAutorun = autorun( reaction => {
+                if ('listener' in this) {
+                    this.listener.unsubscribe();
+                }
+                const topicConfig = {
+                    ros : this.props.ros,
+                    name: this.topic,
+                    messageType: "april_messages/memory",
+                };
+                this.listener = new RosLib.Topic(topicConfig);
+                this.listener.subscribe(message => {
+                    this.data.replace(message.interactions);
+                });
+            });
         } else {
             console.warn('RosInteractionTrace expects to be passed a valid Ros object as property, got ', this.props.ros);
         }
     }
 
     componentWillUnmount() {
-        this.listener.unsubscribe();
-    }
-
-    memory_to_data(message) {
-        this.setState({data: message.interactions});
+        if ('listener' in this) {
+            this.listener.unsubscribe();
+        }
     }
 
     render () {
-        return <MemoryRanked data={this.state.data} {...this.props}/>
+        return <MemoryRanked data={toJS(this.data)} {...this.props}/>
     }
-}
-
-function ModalRosMemoryRanked(props) {
-    return <Modal component={RosMemoryRanked} {...props}/>
-}
+}))
+decorate(RosMemoryRanked, {
+    data: observable,
+    topic: computed,
+})
 
-export {ModalRosMemoryRanked as RosMemoryRanked}
+export {RosMemoryRanked}
diff --git a/src/Mood/RosMood.js b/src/Mood/RosMood.js
index ffaab93f72aab9038e1283580878d9e7731605de..8df2fa4a1e4d66e04cdda47b6be82078d8268de7 100644
--- a/src/Mood/RosMood.js
+++ b/src/Mood/RosMood.js
@@ -1,47 +1,65 @@
 import React, {Component} from 'react'
+import {decorate, observable, autorun} from 'mobx'
+import {observer} from 'mobx-react'
 import RosLib from 'roslib'
+import sizeMe from 'react-sizeme'
 
-import { Modal } from '../utils/modeHandler'
-import { SizedMood } from './Mood'
+import {defaultConfig} from '../utils/configurationHelpers'
+import { Mood } from './Mood'
 
+class MoodStore {
+  readme = (
+    <p className="about">
+      The <b> agent's current mood</b> regarding the valences of its actions.
+    </p>);
+  configSchema = {
+    title: "Robot's mood",
+    type: "object",
+    required: ["topic"],
+    properties: {
+      topic: {
+        title: "Topic used",
+        type: "string",
+        default: "/algorithm/mood"
+      }
+    }
+  };
+  config = {};
+}
+decorate(MoodStore, {
+  config: observable,
+})
+
+const RosMood = sizeMe({monitorHeight: true})(observer(
 class RosMood extends Component {
   constructor(props) {
     super(props)
 
-    this.state = {mood: "PLEASED"};
-  }
+    this.store = new MoodStore();
+    this.props.store.components['mood'] = this.store;
+    // Fill the undefined fields of the configuration object with their default values.
+    defaultConfig(this.store);
 
-  static get modes() {
-    return {
-      readme: (
-        <p className="about">
-          The <b> agent's current mood</b> regarding the valences of its actions.
-        </p>),
-      settingsSchema: {
-        title: "Robot's mood",
-        type: "object",
-        required: ["topic"],
-        properties: {
-          topic: {
-            title: "Topic used",
-            type: "string",
-            default: "/algorithm/mood"
-          }
-        }
-      }
-    }
+    this.mood = observable.box("PLEASED");
   }
 
   componentDidMount() {
     if ('ros' in this.props && this.props.ros) {
-      const config = this.props.node.getConfig();
-      this.mood_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: config.topic,
-        messageType: "april_messages/mood"
-      });
-      this.mood_listener.subscribe(message => {
-        this.setState(message);
+      // With this MobX autorun, if the topics are changed in the configuration,
+      // we will automatically unsubscribe from the old ones and register to the
+      // new topic names.
+      this.topicAutorun = autorun( reaction => {
+        if ('mood_listener' in this) {
+          this.mood_listener.unsubscribe();
+        }
+        this.mood_listener = new RosLib.Topic({
+          ros : this.props.ros,
+          name: this.store.config.topic,
+          messageType: "april_messages/mood"
+        });
+        this.mood_listener.subscribe(message => {
+          this.mood.set(message.mood);
+        });
       });
     } else {
       console.warn('RosMood expects to be passed a valid Ros object as property, got ', this.props.ros);
@@ -55,12 +73,11 @@ class RosMood extends Component {
   }
 
   render() {
-    return <SizedMood mood={this.state.mood} {...this.props}/>
+    return <Mood mood={this.mood.get()} {...this.props}/>
   }
-}
+}))
 
-function ModalRosMood(props) {
-  return <Modal component={RosMood} {...props}/>;
-}
+const SizedRosMood = sizeMe({monitorHeight: true})(RosMood);
+export {SizedRosMood};
 
-export { ModalRosMood as RosMood };
+export {RosMood };
diff --git a/src/VideoStream/index.js b/src/VideoStream/index.js
index 4f5609af7c09555128de610c9dda0deec3430324..92900a7ae55875a45c5705835ff004191f3110a8 100644
--- a/src/VideoStream/index.js
+++ b/src/VideoStream/index.js
@@ -1,53 +1,66 @@
 import React, {Component} from 'react'
-import {Modal} from '../utils/modeHandler'
-import {has} from 'lodash/object'
+import {decorate, observable, computed} from 'mobx'
+import {observer} from 'mobx-react'
+
+import {defaultConfig} from '../utils/configurationHelpers'
 import './VideoStream.css'
 
+class VideoStore {
+    readme = (
+        <div className="about">
+            <h1>See what the agent is seeing</h1>
+            <p><b>Video stream</b> coming from the robot or the device.</p>
+        </div>
+    );
+    configSchema = {
+        title: "Video streamer",
+        type: "object",
+        required: ["host", "topic"],
+        properties: {
+            topic: {
+                type: "string",
+                title: "ROS topic (absolute name)",
+                default: "/pepper/image_raw"
+            },
+            host: {
+                type: "string",
+                title: "Host (IP or name)",
+                default:"localhost:8081"
+            },
+        }
+    };
+    config = {};
+}
+decorate(VideoStore, {
+    config: observable,
+})
+
+const VideoStream = observer(
 class VideoStream extends Component {
     constructor(props) {
         super(props);
 
-        this.defaultImage = 'images/default.svg';
+        this.store = new VideoStore();
+        // Fill the undefined fields of the configuration object with their default values.
+        defaultConfig(this.store);
+        this.props.store.components['video'] = this.store;
     }
 
-    /**
-     * Method that returns the display modes and desired settings properties (in JSON Schema for the settings).
-     * These are used by Modal, from modeHandler, to switch from this component to a configuration view or a readme
-     * view.
-     */
-    static get modes() {
-        return {
-            readme: (
-                <div className="about">
-                    <h1>See what the agent is seeing</h1>
-                    <p><b>Video stream</b> coming from the robot or the device.</p>
-                </div>
-            ),
-            settingsSchema: {
-                title: "Video streamer",
-                type: "object",
-                required: ["host", "topic"],
-                properties: {
-                    topic: {type: "string", title: "ROS topic (absolute name)", default: "/pepper/image_raw"},
-                    host: {type: "string", title: "Host (IP or name)", default:"localhost:8081"},
-                }
-            }
-        };
-    }
+    defaultImage = 'images/default.svg';
 
     /**
-     * Return the url of the video stream to display, based on the current configuration.
-     * If there is no stream configured, gives the URL passed as a property to this object, or the one defined in
-     * this.defaultImage.
+     * Return the url of the video stream to display, based on the current
+     * configuration.
+     * If there is no stream configured, gives the URL passed as a property to
+     * this object, or the one defined in this.defaultImage.
      */
-    getStreamUrl() {
-        const config = this.props.node.getConfig();
-        const config_fields = ['topic', 'host'];
+    get streamUrl() {
         if ('url' in this.props && this.props.url) {
             return this.props.url;
-        } else if (config_fields.reduce((accumulator, field) => accumulator && has(config, field), true)) {            
+        } else if ('topic' in this.store.config && 'host' in this.store.config) {            
             // all requierd configuration fields are defined
-            return 'http://' + config.host + '/stream?topic=' + config.topic;
+            return 'http://' + this.store.config.host + '/stream?topic='
+                    + this.store.config.topic;
         } else {
             return this.defaultImage;
         }
@@ -56,17 +69,17 @@ class VideoStream extends Component {
     render() {
         return (
         <div className="video-stream container">
-            <img src={this.getStreamUrl()} alt="there should have been a video stream here"/>
+            <img src={this.streamUrl} alt="there should have been a video stream
+                here"/>
         </div>);
     }
-}
+})
+decorate(VideoStream, {
+    streamUrl: computed,
+})
 
 function DemoVideoStream(props) {
     return <VideoStream url="images/table-right.jpg" {...props}/>;
 }
 
-function ModalVideoStream(props){
-    return <Modal component={VideoStream} {...props}/>;
-}
-
-export {ModalVideoStream as VideoStream, DemoVideoStream};
+export {VideoStream, DemoVideoStream};
diff --git a/src/demo-tabbed-config.js b/src/demo-tabbed-config.js
new file mode 100644
index 0000000000000000000000000000000000000000..12cbf926332fe27b4504613cb5052ea5c3d4f07c
--- /dev/null
+++ b/src/demo-tabbed-config.js
@@ -0,0 +1,94 @@
+export const flexlayout_json = {
+  global: {
+    splitterSize: 5,
+    tabSetTabStripHeight: 30,
+    tabSetHeaderHeight: 25,
+    enableEdgeDock: false,
+    tabEnableRename: false,
+    tabSetEnableHeader: false,
+    // tabSetEnableTabStrip: false,
+  },
+  layout: {
+    "type": "row",
+    "weight": 100,
+    "children": [
+      {
+        "type": "tabset",
+        "weight": 50,
+        "selected": 0,
+        "children": [
+          {
+            "type": "tab",
+            "name": "Interaction trace",
+            "component": "int-trace",
+          },
+        ]
+      },
+      {
+        "type": "row",
+        "weight": 50,
+        "children": [
+          {
+            "type": "tabset",
+            "weight": 60,
+            "selected": 0,
+            "children": [
+              {
+                "type": "tab",
+                "name": "Robot's memory",
+                "component": "memory",
+              },
+            ]
+          },
+          {
+            "type": "row",
+            "weight": 40,
+            "children": [
+              {
+                "type": "row",
+                "weight": 25,
+                "children": [
+                  {
+                    "type": "tabset",
+                    "weight": 78,
+                    "selected": 0,
+                    "children": [
+                      {
+                        "type": "tab",
+                        "name": "Latest interaction",
+                        "component": "latest-interaction",
+                      },
+                    ]
+                  },
+                  {
+                    "type": "tabset",
+                    "weight": 22,
+                    "height": 70,
+                    "children": [
+                      {
+                        "type": "tab",
+                        "name": "Involvement",
+                        "component": "h-gauge",
+                      },
+                    ]
+                  },
+                ]
+              },
+              {
+                "type": "tabset",
+                "weight": 75,
+                "children": [
+                  {
+                    "type": "tab",
+                    "name": "Robot video stream",
+                    "component": "video",
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+};
diff --git a/src/index.js b/src/index.js
index 161069eb862e0ccea3097481e6da90cdce38f232..966bb929f9f431d5d3c34146d0abdac715f16aea 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,16 +1,48 @@
 import React from "react";
 import ReactDOM from "react-dom";
+// MobX, a state manager
+import {decorate, observable} from "mobx"
+import {observer} from 'mobx-react'
 // ROS library
 import RosLib from 'roslib';
 // React Components to be displayed
 import {Board} from './Board';
-import { Header } from "./Header";
+import {Header} from './Header';
+import {Modal} from './utils/modeModal';
 
 import 'bootstrap/dist/css/bootstrap.min.css'
+// CSS common to the whole app
+import './Common.css'
 
+class ObservableStore {
+  components = {}
+  modal = {
+    enabled: false,
+    mode: null,
+    component: null,
+  }
+  displayModal(mode, component) {
+    this.modal.enabled = true;
+    this.modal.mode = mode;
+    this.modal.component = component;
+  }
+  exitModal() {
+    this.modal.enabled = false;
+    this.modal.mode = null;
+    this.modal.component = null;
+  }
+}
+decorate(ObservableStore, {
+  components: observable,
+  modal: observable,
+})
+
+const Main = observer(
 class Main extends React.Component {
   constructor(props) {
     super(props);
+    
+    this.store = new ObservableStore();
 
     this.ros = undefined;
     if ('props' in this && 'ros' in this.props && this.props.ros) {
@@ -40,17 +72,26 @@ class Main extends React.Component {
   }
   
   render() {
+    let modal = null;
+    if (this.store.modal.enabled) {
+      modal = (
+      <div id="overlay" tabIndex="-1" role="dialog">
+        <Modal store={this.store}/>
+      </div>);
+    }
+    
     return (
       <div id="outer">
         <div id="header">
           <Header ros={this.ros}/>
         </div>
         <div id="board">
-          <Board ros={this.ros}/>
+          <Board ros={this.ros} store={this.store}/>
         </div>
+        {modal}
       </div>
     )
   }
-}
+})
 
 ReactDOM.render(<Main ros={true}/>, document.getElementById("container"));
\ No newline at end of file
diff --git a/src/utils/configurationHelpers.js b/src/utils/configurationHelpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ae533d4a50957b4b0c365ad74ee8064a170d8e3
--- /dev/null
+++ b/src/utils/configurationHelpers.js
@@ -0,0 +1,67 @@
+import { merge } from 'lodash/object';
+import { extendObservable } from 'mobx';
+
+/**
+ * Fill the configuration with default values taken from a JSON Schema.
+ * 
+ * Take a MobX store (i.e. an observable Object), get the 'configSchema' attribute and merge the default values from
+ * this schema into the 'config' attribute of our store. The merge is done so that values already in 'config' are not 
+ * verwritten, but missing ones are filled.
+ * 'configSchema' is following the JSON Schema standard, as used for https://react-jsonschema-form.readthedocs.io/en/latest/
+ * @param {Object} store An observable (per MobX) object containing a 'configSchema' property
+ */
+export function defaultConfig(store) {
+  // get the current config; if it does not exist yet, create it
+  if (!('config' in store)) {
+    extendObservable(store, {config: {}});
+  }
+  let configuration = store.config;
+  // for each configuration field that is not yet defined, use the default value (from configSchema)
+  if ('configSchema' in store) {
+    merge(configuration, defaultConfigFromSchema(configuration, store.configSchema));
+    store.config = configuration;
+  }
+}
+
+/**
+ * Merge an object with the default values from a JSON Schema.
+ * 
+ * Search for properties in a JSON Schema. If they are here, use the default value of each JsonSchema property for
+ * the configuration. This means that if a property is already defined in the configuration, it will not be
+ * altered.
+ * @param {Object} config current configuration
+ * @param {Object} configSchema JSON Schema for the configuration
+ * @return the new configuration object (possibly unmodified)
+ */
+export function defaultConfigFromSchema(config, configSchema) {
+  if (!('properties' in configSchema)) {
+    console.warn("no properties in the schema ", configSchema);
+    return config;
+  }
+  else {
+    for (let [key, value] of Object.entries(configSchema.properties)) {
+      if (!(key in config)) {
+        // let message = "The key '" + key + "' is missing in the configuration.";
+        if ('default' in value) {
+          config[key] = value.default;
+          // message += " Using default value '" + value.default + "'.";
+        }
+        // console.debug(message);
+      }
+    }
+    return config;
+  }
+}
+
+/** Append a JSON-Schema description of configuration fields to an already existing one.
+ * This practically allows to add new configuration fields for a component.
+ * 
+ * @param {Object} store MobX observable in which the configuration and configuration JSON-Schema are stored
+ * @param {Object} configSchema JSON-Schema formated object describing fields of the configuration for a React component
+ */
+export function appendConfigSchema(store, configSchema) {
+  // Append the configuration schema of this component to the one currently in the store
+  merge(store.configSchema, configSchema);
+  // Fill the undefined fields of the configuration object with their default values.
+  defaultConfig(store);
+}
\ No newline at end of file
diff --git a/src/utils/modeHandler.js b/src/utils/modeHandler.js
deleted file mode 100644
index 3451f9b4b563e9ede46caa0dd31c2f22a805e6e7..0000000000000000000000000000000000000000
--- a/src/utils/modeHandler.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import React, {Component} from 'react';
-import Form from 'react-jsonschema-form';
-import { merge } from 'lodash/object';
-
-/**
- * This class will look for the `modes` attribute if the class passed as the `component` property. This object is used
- * to add buttons to show a readme about the component or a settings form.
- * 
- * All changes to the node's settings are saved through the updateConfig function (passed as props).
- * 
- * Expected React properties : 
- *  - component: class for the component to be instanciated
- *  - updateConfig & node: as provided by the `Board` component to all its children
- */
-export class Modal extends Component {
-    constructor(props) {
-        super(props);
-
-        this.modes = ('modes' in this.props.component) ? this.props.component.modes : {};
-        this.configGetter = this.props.node.getConfig.bind(this.props.node);
-        this.configSetter = this.props.updateConfig;
-
-        // get the current config; if it does not exist yet, create it
-        let config = this.configGetter() || {};
-        
-        merge(config, this.announceCapabilities(config));
-        if (config.configurable) {
-            merge(config, this.defaultConfigFromSchema(config));
-        }
-        this.configSetter(config);
-    }
-
-    /**
-     * Search for the 'settingsSchema' and 'readme' properties of the managed component. A corresponding field in the
-     * configuration is set accordingly. This is used to display or not the UI buttons to show the Readme or the
-     * configuration of a component.
-     * @param {Object} config current configuration of the object
-     * @return the new configuration object (possibly unmodified)
-     */
-    announceCapabilities(config) {
-        if (this.modes.settingsSchema) {
-            config.configurable = true;
-        }
-        else {
-            config.configurable = false;
-        }
-        if (this.modes.readme) {
-            config.hasReadme = true;
-        }
-        else {
-            config.hasReadme = false;
-        }
-        return config;
-    }
-
-    /**
-     * Search for properties in a JSON Schema. If they are here, use the default value of each JsonSchema property for
-     * the configuration. This means that if a property is already defined in the configuration, it will not be
-     * altered.
-     * @param {Object} config current configuration of the object
-     * @return the new configuration object (possibly unmodified)
-     */
-    defaultConfigFromSchema(config) {
-        const schema = this.modes.settingsSchema;
-        if (!schema) {
-            return config;
-        }
-        else if (!('properties' in schema)) {
-            console.warn("no properties in the schema");
-            return config;
-        }
-        else {
-            for (let [key, value] of Object.entries(schema.properties)) {
-                if (!(key in config)) {
-                    // let message = "The key '" + key + "' is missing in the configuration.";
-                    if ('default' in value) {
-                        config[key] = value.default;
-                        // message += " Using default value '" + value.default + "'.";
-                    }
-                    // console.debug(message);
-                }
-            }
-            return config;
-        }
-    }
-
-    /**
-     * Render either the value of the normal parameter, in normal mode, or the content for the `readme` and `settings`
-     * modes. The display mode is defined by the 'displayMode' attribute of the component's configuration.
-     */
-    render() {
-        const config = this.configGetter();
-        if (config && 'displayMode' in config) {
-            if (config.configurable && config.displayMode === "settings") {
-                return [
-                    <Form
-                        key="form"
-                        className = "container"
-                        schema={this.modes.settingsSchema}
-                        onSubmit={this.handleSettingsChange.bind(this)}
-                        formData={config}
-                        liveValidate={true} />,
-                    <br key="line-break"/>,
-                    <p className="container" key="required-field">
-                        <span clasName="required">*</span> : required field
-                    </p>];
-            }
-            if (config.hasReadme && config.displayMode === "readme") {
-                return this.modes.readme;
-            }
-        }
-        return React.createElement(this.props.component, this.props, null);
-    }
-
-    /**
-     * Given a new configuration object (from the form), update the configuration of the component (in FlexLayout).
-     *
-     * The update is done through Lodash's merge function.
-     * @param {Object} value update for the configuration of the component (we take the field formData)
-     * @return Also return the updated configuration.
-     */
-    handleSettingsChange({ formData }) {
-        let config = this.configGetter();
-        merge(config, formData);
-        this.configSetter(config);
-        return config;
-    }
-    
-}
\ No newline at end of file
diff --git a/src/utils/modeModal.js b/src/utils/modeModal.js
new file mode 100644
index 0000000000000000000000000000000000000000..645dc52c40473048c737d2fa9a6606db53fa787b
--- /dev/null
+++ b/src/utils/modeModal.js
@@ -0,0 +1,68 @@
+import React, {Component} from 'react';
+import Form from 'react-jsonschema-form';
+import { merge } from 'lodash/object';
+
+/**
+ * This component displays either the readme information about a component or a form to change the settings of a
+ * component.
+ * 
+ * All changes to the node's settings are saved through a mobX store (passed as props).
+ * 
+ * Expected React properties : 
+ *  - node: as provided by the `Board` component to all its children
+ *  - store: object containing information about all relevant React components, as well as information on the current
+ *      mode.
+ */
+export class Modal extends Component {
+    constructor(props) {
+        super(props);
+        
+        this.component = this.props.store.components[this.props.store.modal.component];
+        this.hasReadme = 'readme' in this.component;
+        this.configurable = 'configSchema' in this.component;
+    }
+
+    /**
+     * Render either the content for the `readme` or `settings` modes. The display mode is defined by the 'mode'
+     * attribute of the modal's configuration.
+     */
+    render() {
+      const mode = this.props.store.modal.mode;
+      if (this.configurable && mode === "settings") {
+        return [
+          <Form
+              key="form"
+              className = "container"
+              schema={this.component['configSchema']}
+              onSubmit={this.handleSettingsChange.bind(this)}
+              formData={this.component.config}
+              liveValidate={true} />,
+          <br key="line-break"/>,
+          <p className="container" key="required-field">
+            <span className="required">*</span> : required field
+          </p>];
+      }
+      if (this.hasReadme && mode === "readme") {
+          return (
+            <div onClick={() => {this.props.store.exitModal()}}>
+              {this.component['readme']}
+            </div>);
+      }
+    }
+
+    /**
+     * Given a new configuration object (from the form), update the configuration of the component and exit the modal.
+     *
+     * The update is done through Lodash's merge function.
+     * @param {Object} value update for the configuration of the component (we take the field formData)
+     * @return Also return the updated configuration.
+     */
+    handleSettingsChange({ formData }) {
+        let config = this.component.config;
+        merge(config, formData);
+        this.component.config = config;
+        this.props.store.exitModal();
+        return config;
+    }
+    
+}
\ No newline at end of file