diff --git a/package-lock.json b/package-lock.json
index 4ef85664bae9461ae553d8d592c18a6472171bcf..b525edb49670fdcd7b3d06a4129d93315ee17edf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2455,6 +2455,11 @@
       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
       "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
     },
+    "bootstrap": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
+      "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -8269,6 +8274,11 @@
         "lodash._reinterpolate": "~3.0.0"
       }
     },
+    "lodash.topath": {
+      "version": "4.5.2",
+      "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
+      "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak="
+    },
     "lodash.unescape": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
@@ -10603,6 +10613,26 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
       "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
     },
+    "react-jsonschema-form": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.6.1.tgz",
+      "integrity": "sha512-rDZjAMzI9GrG5EpBbqmhnch3jgFd9YN9U2bk8zc0PBldgGiENjo+ziIh4vseDNijPNU+07wclD5anIN23nmYxw==",
+      "requires": {
+        "ajv": "^6.7.0",
+        "babel-runtime": "^6.26.0",
+        "core-js": "^2.5.7",
+        "lodash.topath": "^4.5.2",
+        "prop-types": "^15.5.8",
+        "react-is": "^16.8.4"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
+          "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
+        }
+      }
+    },
     "react-lifecycles-compat": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
diff --git a/package.json b/package.json
index 5705e7db426c823a238e8d23b7ea637063ef1cfa..078fd8712c694c8243012fc6dc4451f558d43107 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
     "@trendmicro/react-sidenav": "^0.4.5",
     "@types/d3-array": "^1.2.6",
     "@types/roslib": "^0.18.3",
+    "bootstrap": "^4.3.1",
     "d3": "^5.8.0",
     "d3-scale": "^2.2.2",
     "flexlayout-react": "^0.3.3",
@@ -16,6 +17,7 @@
     "prop-types": "^15.7.2",
     "react": "^16.8.6",
     "react-dom": "^16.7.0",
+    "react-jsonschema-form": "^1.5.0",
     "react-numeric-input": "^2.2.3",
     "react-scripts": "^3.0.1",
     "react-sizeme": "^2.5.2",
diff --git a/public/images/default.svg b/public/images/default.svg
new file mode 100644
index 0000000000000000000000000000000000000000..52a3198bc4dce97d28a38dcca658b80794513534
--- /dev/null
+++ b/public/images/default.svg
@@ -0,0 +1 @@
+<svg version="1.1" viewBox="0.0 0.0 944.8818897637796 718.1102362204724" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="p.0"><path d="m0 0l944.8819 0l0 718.1102l-944.8819 0l0 -718.1102z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l944.8819 0l0 718.1102l-944.8819 0z" fill-rule="evenodd"/><path fill="#efefef" d="m0 0l944.8819 0l0 718.1102l-944.8819 0z" fill-rule="evenodd"/><path fill="#cccccc" d="m290.1798 354.03406l0 0c0 -92.37506 71.41586 -167.25983 159.51181 -167.25983l0 0c42.305176 0 82.877594 17.621964 112.7919 48.989273c29.914246 31.367294 46.71991 73.91051 46.71991 118.27055l0 0c0 92.37506 -71.41589 167.25983 -159.51181 167.25983l0 0c-88.09595 0 -159.51181 -74.884766 -159.51181 -167.25983z" fill-rule="evenodd"/><path fill="#d9d9d9" d="m180.32224 75.26723l269.38583 0l0 278.77167l-269.38583 0z" fill-rule="evenodd"/><path fill="#999999" d="m616.34216 513.66l123.93677 -99.38309l123.93677 99.38309l-47.33966 160.80524l-153.19421 0z" fill-rule="evenodd"/></g></svg>
\ No newline at end of file
diff --git a/public/light-flat.css b/public/light-flat.css
index 7bd3b7e5b83a3abfc90dc0d93681a9c2ef2d26c9..a42b7d940cc63a848c85e849523ea057932df727 100755
--- a/public/light-flat.css
+++ b/public/light-flat.css
@@ -77,7 +77,22 @@ 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;
diff --git a/src/Board.js b/src/Board.js
index 6003ca6e0d1207efdf9995efd4276ff4eb15d847..ab9e166a716261fc2a1baa421ad111f798602ace 100755
--- a/src/Board.js
+++ b/src/Board.js
@@ -1,8 +1,9 @@
 import React from 'react';
 // Tab-based dynamic layout manager
-import FlexLayout, {Actions} from 'flexlayout-react';
+import FlexLayout from 'flexlayout-react';
 // MobX, a state manager
-import {decorate, observable} from 'mobx'
+import {toJS} from 'mobx'
+import {observer} from 'mobx-react'
 // The React Components that are going into tabs
 import {VideoStream, DemoVideoStream} from './VideoStream'
 import {RosLineChart, DemoLineChart} from './LineChart'
@@ -16,25 +17,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
@@ -73,21 +65,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);
     }
 
@@ -99,22 +85,18 @@ class Board extends React.Component {
         }, null);
     }
 
-    updateConfig(nodeId, config) {
-        this.state.model.doAction(Actions.updateNodeAttributes(nodeId, {
-            config: config
-        }));
-        console.log("updateConfig called with the following configuration:")
-        console.log(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
@@ -124,70 +106,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.
-             *
-             * This mode is handled through a configuration object handled by FlexLayout.
-             * @param {String} modeName name of the mode to be switched
+             * Switch in and out of a modal. This function returns a fuction
+             * that does the real switching for a given modal.
+             * 
+             * 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)) {
-                        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 {
-                        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
-                          className= "tabset-button"
-                          src="images/icons/settings.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
-                        className= "tabset-button"
-                        src="images/icons/info-grey-outline.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/icons/settings.svg"
+                                alt="settings"
+                                key="settings"
+                                onClick={modeToggle("settings").bind(this)}/>
+                        );
+                    }
+                    // does the active (selected) tab have a readme?
+                    if ('readme' in component) {
+                        renderValues.buttons.push(
+                            <img src="images/icons/info-grey-outline.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/Common.css b/src/Common.css
index 90353611ab4136d9a67b82f0af6a0f19dde6b317..517d051a856cfb874cdf1096b1c6c404b39eb209 100644
--- a/src/Common.css
+++ b/src/Common.css
@@ -29,6 +29,12 @@ h2 {
   margin: 10px 10px 10px 10px;
 }
 
+/* For the forms generated by react-jsonschema-form */
+i.glyphicon { display: none; }
+.btn-add::after { content: 'Add'; }
+.array-item-move-up::after { content: 'Move Up'; }
+.array-item-move-down::after { content: 'Move Down'; }
+.array-item-remove::after { content: 'Remove'; }
 .tabset-button:hover {
   filter: invert(67%) sepia(71%) saturate(551%) hue-rotate(1deg) brightness(200%) contrast(86%);
 }
diff --git a/src/HorizontalGauge/HozirontalGauge.js b/src/HorizontalGauge/HozirontalGauge.js
index 65fca8a360dffc0c2335feba35fdf1f26109ff64..794ba06f1a4e1d4f8aa12d83a86455a58cbcafb6 100644
--- a/src/HorizontalGauge/HozirontalGauge.js
+++ b/src/HorizontalGauge/HozirontalGauge.js
@@ -7,44 +7,34 @@ class HorizontalGauge extends Component {
     render() {
         const value = ('value' in this.props && this.props.value) ? this.props.value : 0;
 
-        // Define the dimensions of the gaug
-        const margin = { left:10, top: 10, right: 10, bottom: 10}
-        , width = this.props.size.width - margin.left - margin.right
-        , height = 20;
+        // Define the dimensions of the gauge
+        const margin = { left:10, top: 10, right: 10, bottom: 10},
+            width = this.props.size.width - margin.left - margin.right,
+            height = 20;
         const viewbox = "0 0 " + (width + margin.left + margin.right) +" "+ (height + margin.top + margin.bottom);
+        const min = this.props.min || 0,
+            max = this.props.max || 1;
 
         // Scale to map from values in [0, 1] to values in [0, width] (callable)
         const xScale = scaleLinear()
-        .domain([0, 1])
+        .domain([min, max])
         .range([0, width])
         .clamp(true);
 
         const svg = <svg viewBox={viewbox}  width={width + margin.left + margin.right} height={height + margin.top + margin.bottom}>
-        {/* translate to add some margin around the gauge */}
-        <g className="gauge" transform={"translate(" + margin.left + "," + margin.top + ")"}>
-        {/* Background of the gauge */}
-        <rect className="background" x="0" y="0" width={width} height={height} ry="5px"/>
-        {/* Representation of the value */}
-        <rect className="overlay" x="0" y="0" width={xScale(value)} height={height} ry="5px"/>
-        {/* Text of the gauge */}
-        <text className="label" x={width/2} y={height/2} textAnchor="middle" dominantBaseline="central">
-        {"" + value.toFixed(2)}
-        </text>
-        </g>
+            {/* translate to add some margin around the gauge */}
+            <g className="gauge" transform={"translate(" + margin.left + "," + margin.top + ")"}>
+            {/* Background of the gauge */}
+            <rect className="background" x="0" y="0" width={width} height={height} ry="5px"/>
+            {/* Representation of the value */}
+            <rect className="overlay" x="0" y="0" width={xScale(value)} height={height} ry="5px"/>
+            {/* Text of the gauge */}
+            <text className="label" x={width/2} y={height/2} textAnchor="middle" dominantBaseline="central">
+            {"" + value.toFixed(2)}
+            </text>
+            </g>
         </svg>;
 
-        const config = this.props.node.getConfig()
-
-        if (config && 'displayMode' in config) {
-            if (config.displayMode === "readme") {
-                return <p className="about">This gauge represent a <b>measure</b> ranging from 0 to 1.</p>;
-            } else if (config.displayMode === "settings") {
-                return <div>
-                    <p>config panel</p>
-                </div>;
-            }
-        }
-
         return <div>{svg}</div>;
     }
 }
diff --git a/src/HorizontalGauge/RosHGauge.js b/src/HorizontalGauge/RosHGauge.js
index 874fd0de0638c3e13dd9ee96d25c16ed32c70886..a14070cce05c4f7e7c32f011828c4fffe2410cca 100644
--- a/src/HorizontalGauge/RosHGauge.js
+++ b/src/HorizontalGauge/RosHGauge.js
@@ -1,29 +1,70 @@
 import React, {Component} from 'react'
-import RosLib from 'roslib';
+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.topic = "/predictions/interaction_involvement"
-        this.type = "april_messages/head_orientation_prediction"
+        this.datum = null;
+
+        this.props.store.components['h-gauge'] = new HGaugeStore();
+        defaultConfig(this.props.store.components['h-gauge']);
+    }
+
+    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) {
-            this.listener = new RosLib.Topic({
-                ros : this.props.ros,
-                name: this.topic,
-                messageType: this.type
-            });
-            
-            this.listener.subscribe(function(message) {
-                this.setState({datum: message.prediction_of_head_orientation.list[0].values[1]});
-            }.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);
         }
     }
@@ -35,8 +76,19 @@ class RosHGauge extends Component {
     }
     
     render() {
-        return <HorizontalGauge value={this.state.datum} {...this.props}/>
+        return <HorizontalGauge
+            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
+export default RosHGauge
diff --git a/src/InteractionTrace/InteractionTrace.js b/src/InteractionTrace/InteractionTrace.js
index 2d3a1bf9e73ad2ed86b36686a4748f38da7af250..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(
@@ -26,11 +67,12 @@ class InteractionTrace extends Component {
     super(props);
 
     this.interactionStore = new InteractionTraceStore();
-    this.props.store.components['interactionTrace'] = this.interactionStore;
+    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,37 +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)
 
-    // Value for the boredom at which we plot a red line (showing max value)
-    this.maxBoredom = 2;
-    // How many primitive actions (at least) we want to display
-    this.nbPrimitiveActions = 3;
-
-    // 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 = false;
-
-    // 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);
+    }
   }
 
   /**
@@ -95,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]);
@@ -291,25 +333,6 @@ class InteractionTrace extends Component {
     const viewbox = "0 0 " + (width + this.margin.left + this.margin.right)
                        +" "+ (height + this.margin.top + this.margin.bottom);
 
-    const config = this.props.node.getConfig()
-
-    if (config && 'displayMode' in config) {
-      if (config.displayMode === "readme") {
-          return <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>;
-      } else  if (config.displayMode === "settings") {
-          return <div>
-              <p>config panel</p>
-          </div>;
-      }
-    }
-
     return (
     <svg viewBox={viewbox} width="100%" height="99%" className="interaction-trace">
       {/* Compute how many pixels an em takes */}
@@ -361,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 9e7140d8e67639fb3e5e01439853b0c0bdcd3aad..fb0caea1cc806f5c47ef886e80271eb28a8a01cd 100644
--- a/src/InteractionTrace/RosInteractionTrace.js
+++ b/src/InteractionTrace/RosInteractionTrace.js
@@ -1,55 +1,102 @@
 import React, {Component} from 'react'
-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 = [];
-    this.lowerIdResets = true;
-    this.trace = {
-      topic: "/algorithm/trace",
-      type: "april_messages/trace"
-    }
-    this.boredom = {
-      topic: "/algorithm/boredom",
-      type: "april_messages/boredom"
-    }
-    this.mood = {
-      topic: "/algorithm/mood",
-      type: "april_messages/mood"
-    }
+  }
 
+  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) {
-      this.trace_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: this.trace.topic,
-        messageType: this.trace.type
-      });
-      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: this.boredom.topic,
-        messageType: this.boredom.type
-      });
-      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: this.mood.topic,
-        messageType: this.mood.type
+        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);
     }
@@ -109,7 +156,7 @@ class RosInteractionTrace extends Component {
    */
   mergeDataInState(newDatum) {
     // Empty the data and get the first interaction of an experiment
-    if (this.lowerIdResets && newDatum.id === 0 && this.data.length > 1) {
+    if (this.lowerIdResets && this.data.length > 0 && newDatum.id < this.data[this.data.length-1].id) {
       this.data = [];
     }
 
@@ -125,7 +172,10 @@ class RosInteractionTrace extends Component {
 )
 decorate(RosInteractionTrace, {
   data: observable,
-  lowerIdResets: observable,
+  traceTopic: computed,
+  boredomTopic: computed,
+  moodTopic: computed,
+  lowerIdResets: computed,
 })
 
-export default sizeMe({monitorHeight: true})(RosInteractionTrace);
\ No newline at end of file
+export default sizeMe({monitorHeight: true})(RosInteractionTrace);
diff --git a/src/LatestInteraction/DemoLatestInteraction.js b/src/LatestInteraction/DemoLatestInteraction.js
index 7f805eeaf158cbca59f236d02907060e38a5d1e9..fafd8715cce7751069b1a949b437d2288bc891ba 100644
--- a/src/LatestInteraction/DemoLatestInteraction.js
+++ b/src/LatestInteraction/DemoLatestInteraction.js
@@ -1,5 +1,5 @@
 import React from 'react'
-import {LatestInteraction} from './index'
+import {LatestInteraction} from './LatestInteraction'
 
 export function DemoLatestInteraction(props) {
   const intended = {action_names: ["A", "A", "B", "C"], primitive_valences:[1, 2, 3, 4]};
diff --git a/src/LatestInteraction/LatestInteraction.js b/src/LatestInteraction/LatestInteraction.js
new file mode 100644
index 0000000000000000000000000000000000000000..24aea55f137ae0e0ebe078b4703767fd50e75f03
--- /dev/null
+++ b/src/LatestInteraction/LatestInteraction.js
@@ -0,0 +1,65 @@
+import React from 'react'
+import {observer} from 'mobx-react'
+
+import { actionClassName } from '../utils/valenceColours';
+
+export const LatestInteraction = observer(
+function LatestInteraction(props) {
+  let intended, enacted;
+  let iteration = null;
+  if ('int-trace' in props.store.components && props.store.components['int-trace'].showInteraction) {
+    intended = props.store.components['int-trace'].currentInteraction.intended;
+    enacted = props.store.components['int-trace'].currentInteraction.enacted;
+    const iteration_id = props.store.components['int-trace'].currentInteraction.id;
+  iteration = [<p key="first"><span className="label">Iteration:</span> {iteration_id}</p>, <hr key="last"/>];
+  } else {
+    intended = props.intended;
+    enacted = props.enacted;
+  }
+
+  /**
+   * Return the 
+   * @param {Object} inter object representing the interaction to display (either intended or enacted)
+   * @param {Number} i index of primitive action which row this is makeing
+   */
+  function inter2row(inter, length, i) {
+    if (i < length) {
+      return (
+      <td className={actionClassName(inter.primitive_valences[i])}>
+        {inter.action_names[i]} ({inter.primitive_valences[i]})
+      </td>); 
+    } else {
+      return null;
+    }
+  }
+
+  const length_i = intended.action_names.length,
+        length_e = enacted.action_names.length;
+  let rows = [];
+  for (let i=0; i<Math.max(length_i, length_e); i++) {
+    rows.push(
+      <tr key={i}>
+        {inter2row(intended, length_i, i)}
+        {inter2row(enacted, length_e, i)}
+      </tr>
+    );
+  }
+
+  return (
+    <div className="latest-interaction">
+      {iteration}
+      <table>
+        <thead>
+          <tr>
+            <th className="label">Intended</th>
+            <th className="label">Enacted</th>
+          </tr>
+        </thead>
+        <tbody>
+          {rows}
+        </tbody>
+      </table>
+    </div>
+  );
+}
+)
diff --git a/src/LatestInteraction/RosLastestInteraction.js b/src/LatestInteraction/RosLastestInteraction.js
index 8e5913ca24b7ea1e8207d9ccbb65b708d1a3cdc8..49031be36e3f24f7d3a1603db2f6ec13ef78834c 100644
--- a/src/LatestInteraction/RosLastestInteraction.js
+++ b/src/LatestInteraction/RosLastestInteraction.js
@@ -1,36 +1,89 @@
 import React, {Component} from 'react'
 import RosLib from 'roslib'
+import {autorun, decorate, observable} from 'mobx'
+import {observer} from 'mobx-react'
 
-import {LatestInteraction} from './index'
+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)
-
-    this.state = {intended: {
-      action_names: [],
-      primitive_valences: []
-    }, enacted: {
-      action_names: [],
-      primitive_valences: []
-    }};
-    this.trace = {
-      topic: "/algorithm/trace",
-      type: "april_messages/trace"
-    }
+    super(props);
+
+    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']);
+
+    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);
     }
   }
@@ -42,8 +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}/>
   }
-}
+})
+decorate(RosLatestInteraction, {
+  latestInteraction: observable,
+});
 
-export { RosLatestInteraction };
+export {RosLatestInteraction };
diff --git a/src/LatestInteraction/index.js b/src/LatestInteraction/index.js
index 47c459b88fd0823a66d5e741d8b232a4cd4ecfe0..624763d0ce870e4db0a5e9b3ddc76fccf0955706 100644
--- a/src/LatestInteraction/index.js
+++ b/src/LatestInteraction/index.js
@@ -1,83 +1,4 @@
-import React from 'react'
-import {observer} from 'mobx-react'
-
 import './LatestInteraction.css'
-import { actionClassName } from '../utils/valenceColours';
-
-export const LatestInteraction = observer(
-function LatestInteraction(props) {
-  let intended, enacted;
-  let iteration = null;
-  if ('interactionTrace' in props.store.components && props.store.components['interactionTrace'].showInteraction) {
-    intended = props.store.components['interactionTrace'].currentInteraction.intended;
-    enacted = props.store.components['interactionTrace'].currentInteraction.enacted;
-    const iteration_id = props.store.components['interactionTrace'].currentInteraction.id;
-  iteration = [<p key="first"><span className="label">Iteration:</span> {iteration_id}</p>, <hr key="last"/>];
-  } else {
-    intended = props.intended;
-    enacted = props.enacted;
-  }
-
-  /**
-   * Return the 
-   * @param {Object} inter object representing the interaction to display (either intended or enacted)
-   * @param {Number} i index of primitive action which row this is makeing
-   */
-  function inter2row(inter, length, i) {
-    if (i < length) {
-      return (
-      <td className={actionClassName(inter.primitive_valences[i])}>
-        {inter.action_names[i]} ({inter.primitive_valences[i]})
-      </td>); 
-    } else {
-      return null;
-    }
-  }
-
-  const length_i = intended.action_names.length,
-        length_e = enacted.action_names.length;
-  let rows = [];
-  for (let i=0; i<Math.max(length_i, length_e); i++) {
-    rows.push(
-      <tr key={i}>
-        {inter2row(intended, length_i, i)}
-        {inter2row(enacted, length_e, i)}
-      </tr>
-    );
-  }
-
-  const config = this.props.node.getConfig()
-  if (config && 'displayMode' in config) {
-    if (config.displayMode === "readme") {
-        return <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>;
-    } else if (config.displayMode === "settings") {
-        return <div>
-            <p>config panel</p>
-        </div>;
-    }
-  }
-
-  return (
-    <div className="latest-interaction">
-      {iteration}
-      <table>
-        <thead>
-          <tr>
-            <th className="label">Intended</th>
-            <th className="label">Enacted</th>
-          </tr>
-        </thead>
-        <tbody>
-          {rows}
-        </tbody>
-      </table>
-    </div>
-  );
-}
-)
 
 export { DemoLatestInteraction } from './DemoLatestInteraction'
 export { RosLatestInteraction } from './RosLastestInteraction'
\ No newline at end of file
diff --git a/src/LineChart/LineChart.js b/src/LineChart/LineChart.js
index d7bc96e173e7a58dbb4fd2d87fd6045d5018344e..866659999f0fa27a7ef09ffff3602d7499d38952 100644
--- a/src/LineChart/LineChart.js
+++ b/src/LineChart/LineChart.js
@@ -49,8 +49,8 @@ class LineChart extends Component {
   /**
   * Give the extent of values for an Array of Array (of whatever, see `accessor`).
   * 
-  * Say we have two curves to plot. Each curve is an array of coordinates (objects {x: , y:}). They are added
-  * to `dataObject` with their legend as a key.
+  * Say we have two curves to plot. Each curve is an array of coordinates (objects {x: , y:}) (the 'x' key is optional).
+  * They are added to `dataObject` with their legend as a key.
   * 
   * This function is a replacement of d3's `extent` function, as the data structure is more complex here.
   * 
@@ -67,27 +67,6 @@ class LineChart extends Component {
     }));
     return [lower, upper];
   }
-
-  /**
-   * Add default values for the abscissa axis, if there is none
-   * @param {Object(Array(Object))} data the data to be plotted
-   */
-  pre_process(data) {
-    let new_data = {}
-    // Iterate over the plot names (keys in data)
-    Object.keys(data).forEach((name) => {
-      // Iterate over the data of the plot (entries in the Array)
-      new_data[name] = data[name].map((entry, i) => {
-        // `entry` is an object supposed to have two keys: `x` and `y`. If the latter is missing, we add one based
-        // on the index of `entry` in the array.
-        if (!('x' in entry)) {
-          entry.x = i;
-        }
-        return entry;
-      })
-    });
-    return new_data
-  }
   
   render() {
     // Use the margin convention practice 
@@ -97,7 +76,7 @@ class LineChart extends Component {
     
     const viewbox = "0 0 " + (width + margin.left + margin.right) +" "+ (height + margin.top + margin.bottom);
     
-    const data = this.pre_process(this.props.data);
+    const data = this.props.data;
     
     // Allocate one colour per line to plot
     const lineNames = Object.keys(data);
@@ -107,7 +86,7 @@ class LineChart extends Component {
     
     // X scale will use the index of our data
     const xScale = d3.scaleLinear()
-        .domain(this.extent(data, value => value.x)) // input
+        .domain(this.extent(data, (value, i) => 'x' in value ? value.x : i)) // input
         .range([0, width])
         .nice(); // output
     
@@ -126,7 +105,7 @@ class LineChart extends Component {
     
     // d3's line generator
     const line = d3.line()
-        .x(d => xScale(d.x)) // set the x values for the line generator
+        .x((d, i) => xScale('x' in d ? d.x: i)) // set the x values for the line generator
         .y(d => yScale(d.y)) // set the y values for the line generator 
         .curve(d3.curveMonotoneX) // apply smoothing to the line
     
@@ -194,7 +173,7 @@ function Circles(props) {
   // One circle for each data point that is provided as a property to this Component
   const output = props.data.map((point, i) => (
     <circle key={i} className="dot" fill={props.colour}
-    cx={props.xScale(point.x)} cy={props.yScale(point.y)} r="5"/>
+    cx={props.xScale('x' in point ? point.x : i)} cy={props.yScale(point.y)} r="5"/>
   ));
   return output;
 }
diff --git a/src/LineChart/RosLineChart.js b/src/LineChart/RosLineChart.js
index 2879cea0a75ee102ff89f884dc74ca083e9177bd..e90c012bbedf1129e7ddaa542850465313d612d6 100644
--- a/src/LineChart/RosLineChart.js
+++ b/src/LineChart/RosLineChart.js
@@ -1,83 +1,154 @@
 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 {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.state = {
-            data: {"" : []},
-            topic: {
-                name: "/predictions/interaction_involvement",
-                messageType: "april_messages/head_orientation_prediction",
-                fields: [
-                    "prediction_of_head_orientation.list[0].values[1]",
-                ]
-            },
-            // xLabel: "Count",
-            yLabel: "Involvement",
-        };
+        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.data = observable.map();
+    }
+
+    /**
+     * 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) {
-            this.listener = new RosLib.Topic(Object.assign({ros : this.props.ros}, this.state.topic));
-
-            this.listener.subscribe(function(message) {
-
-                this.setState((state) => {
-                    let fields;
-                    if (!('fields' in this.state.topic) && ('field' in this.state.topic)) {
-                        fields = [this.state.topic.field];
-                    } else {
-                        fields = this.state.topic.fields;
-                    }
-
-                    let new_data = this.state.data;
-                    fields.forEach((field) => {
-                        if (!(field in new_data)) {
-                            new_data[field] = []
-                        }
-                        console.debug("Raw message: ", message);
-                        console.debug("Selected field: ", property(field)(message));
-                        new_data[field] = this.state.data[field].concat([{"y": property(field)(message)}])
-                    })
-                    return {
-                        data: new_data,
-                    }
-                });
-            }.bind(this));
+            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);
         }
     }
 
     componentWillUnmount() {
-        if ('listener' in this) {
-            this.listener.unsubscribe();
+        if ('listeners' in this) {
+            this.listeners.forEach((listener) => {listener.unsubscribe();});
         }
     }
 
     render () {
-        const config = this.props.node.getConfig()
-
-        if (config && 'displayMode' in config) {
-            if (config.displayMode === "readme") {
-                return <p className="about">This <b>linechart</b> represents a <b>measure</b> evolving over time.</p>;
-            }
-
-            if (config.displayMode === "settings") {
-                return <div>
-                    <p>config panel</p>
-                </div>;
-            }
-        }
-
-        return <SizedLineChart data={this.state.data} xLabel={this.state.xLabel} yLabel={this.state.yLabel} {...this.props}/>
+        return <SizedLineChart data={Object.fromEntries(this.data)}
+            xLabel={this.store.config.xLabel}
+            yLabel={this.store.config.yLabel}
+            {...this.props}/>
     }
-}
+})
+decorate(RosLineChart, {
+    data: observable,
+    process_message: action,
+})
 
 export {RosLineChart}
diff --git a/src/MemoryRanked/MemoryRanked.js b/src/MemoryRanked/MemoryRanked.js
index 202784df12651c9422fcba0dab89534559582acd..f39c5222760e49b96d548ce7f593c150110845b2 100644
--- a/src/MemoryRanked/MemoryRanked.js
+++ b/src/MemoryRanked/MemoryRanked.js
@@ -1,15 +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);
+
+    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}
@@ -25,16 +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
-    this.guideLineCount = 3; // If set to 4, one guide line will be displayed every four line of data
-    
-    // The highest number of actions displayed in one memory entry.
-    this.maxActionLength = 15;
+    // // 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; 
     
-    this.sorter = function (interaction) {
-      // return interaction.action_names.length
-      return interaction.valence
-    }
+    // // 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.
@@ -63,32 +153,45 @@ class MemoryRanked extends Component {
   guideLines() {
     const data = this.props.data;
     // Pre-computation for the guide lines, inserted below, in 6.
-    let NbGuideLines = Math.floor(data.length / this.guideLineCount);
-    if (data.length % this.guideLineCount === 0) { // no line at the bottom
+    let NbGuideLines = Math.floor(data.length / this.guideLinesStep);
+    if (data.length % this.guideLinesStep === 0) { // no line at the bottom
       --NbGuideLines;
     }
     // Build a set of ordinates, one for each line
     let y = []
     for (let i = 1; i <= NbGuideLines; i++) {
       // Here, 50 is the top margin used in all the columns of this component
-      y.push(this.margin.top + 50 + this.yScale(this.guideLineCount*i) + this.vStep / 2)
+      y.push(this.margin.top + 50 + this.yScale(this.guideLinesStep*i) + this.vStep / 2)
     }
     
     return y;
   }
   
-  sort(data, accessor = (value => 0), revert = false) {
-    return data.sort((a, b) => {
-      if (!revert) {
-        return accessor(a) - accessor(b)
-      } else {
-        return accessor(b) - accessor(a)
+  sort(data) {
+    let sorter;
+    // Make a sorting function based on the configuration or, by default, sort
+    // by descending valence.
+    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(order === "ascending") {
+          return getter(a) - getter(b);
+        } else {
+          return getter(b) - getter(a);
+        }
       }
-    });
+    } else {
+      sorter = function(a, b) {
+        return b.valence - a.valence;
+      }
+    }
+    
+    return data.sort(sorter);
   }
   
   render() {
-    const data = this.sort(this.props.data, this.sorter, true);
+    const data = this.sort(this.props.data);
     
     
     // Minimal required height for this figure
@@ -136,18 +239,17 @@ class MemoryRanked extends Component {
     ];
 
 
-    const config = this.props.node.getConfig()
-    if (config && 'displayMode' in config) {
-        if (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") {
-            return <div>
-                <p>config panel</p>
-            </div>;
-        }
+    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 (this.store.config.displayMode === "settings") {
+        return <div>
+          <p>config panel</p>
+        </div>;
+      }
     }
     
     const viewbox = "0 0 " + (width + this.margin.left + this.margin.right)
@@ -179,40 +281,44 @@ class MemoryRanked extends Component {
         x2={this.xLayout.upper(2)} y2={yValue}/>
       })}
       </svg>);
-    }
+  }
     
-    /**
-    * Create the react Components declared in declaration and place them at the required x and y coordinates.
-    * 
-    * *Note*: it passes an additional `width` attribute on to the elements so that they know how much horizontal
-    *      space is available for them.
-    * @param {Object} declaration object defining what elements will be appended to the SVG.
-    *      Expected attributes: 
-    *      - component: React component (function or class) to be added
-    *      - x: index of the element, on the horizontal axis
-    *      - all additional attributes are passed to the React Component
-    * @param {Number} i unique value used by React to track the Components it generates
-    */
-    makeSVGComponent(declaration, i) {
-      // Coordinates used for hte translation
-      const x = this.xLayout.lower(declaration.x);
-      const y = this.margin.top + this.yOffset;
+  /**
+  * Create the react Components declared in declaration and place them at the required x and y coordinates.
+  * 
+  * *Note*: it passes an additional `width` attribute on to the elements so that they know how much horizontal
+  *      space is available for them.
+  * @param {Object} declaration object defining what elements will be appended to the SVG.
+  *      Expected attributes: 
+  *      - component: React component (function or class) to be added
+  *      - x: index of the element, on the horizontal axis
+  *      - all additional attributes are passed to the React Component
+  * @param {Number} i unique value used by React to track the Components it generates
+  */
+  makeSVGComponent(declaration, i) {
+    // Coordinates used for hte translation
+    const x = this.xLayout.lower(declaration.x);
+    const y = this.margin.top + this.yOffset;
 
-      // Take the extra properties and pass them to the Component
-      let props = Object.assign({}, declaration);
-      delete props.x; delete props.y; delete props.component;
-      props.width = this.xLayout.width(declaration.x);
-      
-      // Instanciate the component
-      const component = React.createElement(declaration.component, props);
-      
-      return (
-        <g key={"SVG"+i} transform={"translate(" + x + "," + y + ")"}>
-        {/* <rect x="0" y="0" width={valenceLocation.width} height={valenceLocation.height} fill="none" stroke="steelblue"/> */}
-        {component}
-        </g>
-        );
-      }
-    }
+    // Take the extra properties and pass them to the Component
+    let props = Object.assign({}, declaration);
+    delete props.x; delete props.y; delete props.component;
+    props.width = this.xLayout.width(declaration.x);
+    
+    // Instanciate the component
+    const component = React.createElement(declaration.component, props);
+    
+    return (
+      <g key={"SVG"+i} transform={"translate(" + x + "," + y + ")"}>
+      {/* <rect x="0" y="0" width={valenceLocation.width} height={valenceLocation.height} fill="none" stroke="steelblue"/> */}
+      {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 f95fd883ee6e408775477eda60b749db9eed3e05..1fa39d09e15d8144820a8a42ba89033b2fe4414b 100644
--- a/src/MemoryRanked/RosMemoryRanked.js
+++ b/src/MemoryRanked/RosMemoryRanked.js
@@ -1,59 +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 {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();
+    }
+
+    newSchema = {
+        title: "Robot's memory",
+        type: "object",
+        required: ["topic"],
+        properties: {
             topic: {
-                name: "/algorithm/memory",
-                messageType: "april_messages/memory"
-            }
-        };
+                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) {
-            this.listener = new RosLib.Topic(Object.assign({ros : this.props.ros}, this.state.topic));
-            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 () {
-        const config = this.props.node.getConfig()
-
-        if (config && 'displayMode' in config) {
-            if (config.displayMode === "readme") {
-                return (
-                <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>);
-            } else if (config.displayMode === "settings") {
-                return <div>
-                    <p>config panel</p>
-                </div>;
-            }
-        }
-
-        return <MemoryRanked data={this.state.data} xLabel={this.state.xLabel} yLabel={this.state.yLabel} {...this.props}/>
+        return <MemoryRanked data={toJS(this.data)} {...this.props}/>
     }
-}
+}))
+decorate(RosMemoryRanked, {
+    data: observable,
+    topic: computed,
+})
 
 export {RosMemoryRanked}
diff --git a/src/Mood/Mood.js b/src/Mood/Mood.js
index b41f8fcaefdc8918569fd1ba0d11d17d5ed14828..d68d91f107b29b7169912c992a583588fcfd8579 100644
--- a/src/Mood/Mood.js
+++ b/src/Mood/Mood.js
@@ -1,33 +1,29 @@
-import React from 'react';
+import React, {Component} from 'react';
 import sizeMe from 'react-sizeme'
 
 import './Mood.css'
 
 /**
  * Display a picture based on the agent's mood.
- * @param {any} props React properties
  */
-export function Mood(props) {
-  const image = {
-    "PAINED": "images/mood/mood-pained.svg",
-    "PLEASED": "images/mood/mood-happy.svg",
-    "BORED": "images/mood/mood-bored.svg"
-  };
-
-  const config = props.node.getConfig()
-  if (config && 'displayMode' in config) {
-    if (config.displayMode === "readme") {
-        return <p className="about">The <b>current mood</b> of the agent regarding the valences of its actions.</p>;
-    } else if (config.displayMode === "settings") {
-        return <div>
-            <p>config panel</p>
-        </div>;
-    }
+export class Mood extends Component {
+  constructor(props) {
+    super(props);
+    
+    this.image = {
+      "PAINED": "images/mood/mood-pained.svg",
+      "PLEASED": "images/mood/mood-happy.svg",
+      "BORED": "images/mood/mood-bored.svg"
+    };
   }
 
-  return (<img alt={"the current mood of the agent is "+props.mood} src={image[props.mood]}
-               width={props.size.height} height={props.size.height}
-               className="mood" />);
+  render() {
+
+    return (
+      <img alt={"the current mood of the agent is "+this.props.mood} src={this.image[this.props.mood]}
+          width={this.props.size.height} height={this.props.size.height}
+          className="mood" />);
+  }
 }
 
 const SizedMood = sizeMe({monitorHeight: true})(Mood);
diff --git a/src/Mood/RosMood.js b/src/Mood/RosMood.js
index 226251b15f5ede0809e72ab63bd64285903c49e4..71afde0b1b8bfe6e7d1be6f880e0517a113689cb 100644
--- a/src/Mood/RosMood.js
+++ b/src/Mood/RosMood.js
@@ -1,30 +1,68 @@
 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 { 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.mood = {
-      topic: "/algorithm/mood",
-      type: "april_messages/mood"
-    }
+    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);
+
+    this.mood = observable.box("PLEASED");
   }
 
   componentDidMount() {
     if ('ros' in this.props && this.props.ros) {
-      this.mood_listener = new RosLib.Topic({
-        ros : this.props.ros,
-        name: this.mood.topic,
-        messageType: this.mood.type
-      });
-      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 {
+    } else {
       console.warn('RosMood expects to be passed a valid Ros object as property, got ', this.props.ros);
     }
   }
@@ -36,8 +74,11 @@ class RosMood extends Component {
   }
 
   render() {
-    return <SizedMood mood={this.state.mood} {...this.props}/>
+    return <Mood mood={this.mood.get()} {...this.props}/>
   }
-}
+}))
+
+const SizedRosMood = sizeMe({monitorHeight: true})(RosMood);
+export {SizedRosMood};
 
-export { RosMood };
+export {RosMood };
diff --git a/src/VideoStream/index.js b/src/VideoStream/index.js
index 1bdcea11e145ca67a4e6c0f3280875c0a1aec1d4..9ec20f96bffc94b71ac264ede71d878651787a7a 100644
--- a/src/VideoStream/index.js
+++ b/src/VideoStream/index.js
@@ -1,66 +1,126 @@
 import React, {Component} from 'react'
+import {reaction, 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)
+        super(props);
 
-        const hasUrl = ('url' in this.props && this.props.url);
-        this.topic = '/pepper/image_raw';
-        this.defaultImage = '';
+        this.store = new VideoStore();
 
-        this.state = {
-            url: hasUrl ? this.props.url : this.defaultImage
-        };
+        // Automatically detect if the configured topic name is valid
+        this.hasTopic = observable.box(false);
+        this.searchForTopic()
 
-        if (this.props.ros) {
-            this.props.ros.getTopics(function(infos){
-                if (undefined !== infos.topics.find(elem => elem === this.topic)) {
-                    this.setState({url: 'http://localhost:8081/stream?topic=' + this.topic});
-                }
-            }.bind(this))
-        }
+        // Fill the undefined fields of the configuration object with their default values.
+        defaultConfig(this.store);
+        this.props.store.components['video'] = this.store;
     }
 
-    handleSettingsChange(event) {
-        this.setState({url: event.target.value})
-    }
+    defaultImage = 'images/default.svg';
 
-    render() {
-
-        const config = this.props.node.getConfig()
-        if (config && 'displayMode' in config) {
-            if (config.displayMode === "settings") {
-                return <div>
-                    <form className="pure-form pure-form-stacked" onSubmit={(event)=>event.preventDefault()}>
-                        <fieldset>
-                            <label>Nice modification option for the module</label>
+    /**
+     * 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.
+     */
+    get streamUrl() {
+        if ('url' in this.props && this.props.url) {
+            return this.props.url;
+        } else if (this.hasTopic.get()) { 
+            // all requierd configuration fields are defined
+            return 'http://' + this.store.config.host + '/stream?topic='
+                    + this.store.config.topic;
+        } else {
+            return this.defaultImage;
+        }
+    }
 
-                            <label htmlFor="url">URL of the stream</label>
-                            <input className="pure-input-1" id="url" name="url" type="text" placeholder="http://localhost:8080/..."
-                                value={this.state.url} onChange={this.handleSettingsChange.bind(this)}/>
-                        </fieldset>
-                    </form>
-                    <p>Changes are automatically saved!</p>
-                </div>;
-            }
-            if (config.displayMode === "readme") {
-                return <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>;
-            }
+    /**
+     * Check that all the requirements are met for subscribing to the required
+     * topic
+     * 
+     * Thanks to MobX magic, the result is computed again each time the topic
+     * configuration is changed.
+     */
+    searchForTopic() {
+        if (this.props.ros) {
+            this.topicWatcher = reaction(() => (
+                this.store.config.topic + this.store.config.host
+            ),
+            (data, reaction) => {
+                if ('topic' in this.store.config && 'host' in this.store.config) {
+                    this.props.ros.getTopics(infos => {
+                        this.hasTopic.set(undefined !== infos.topics.find(
+                            elem => elem === this.store.config.topic));
+                            
+                        if (!this.hasTopic.get()) {
+                            console.warn("[VideoStream] The required topic "
+                                + this.store.config.topic + " is not published");
+                        }
+                        console.debug(this.hasTopic.get());
+                    })
+                } else {
+                    console.warn("[VideoStream] either no topic is configured "
+                        + "or no host is configured. This should never "
+                        + "happen at runtime. Contact the developpers.");
+                }
+            })
+        } else {
+            console.warn("[VideoStream] ROS property is provided. This should "
+                + "never happen at runtime. Contact the developpers.");
         }
+    }
 
+    render() {
         return (
-            <div className="video-stream container">
-                <img src={this.state.url} alt="there should have been a video stream here"/>
-            </div>);
+        <div className="video-stream container">
+            <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" node={props.node}/>;
+    return <VideoStream url="images/table-right.jpg" {...props}/>;
 }
 
 export {VideoStream, DemoVideoStream};
diff --git a/src/demo-1-config.js b/src/demo-1-config.js
index e5f7169c2f55451620493a52014e4fd27236f12d..1e920f48fa1ec733c22e8de2b9dc7017c0bbe779 100644
--- a/src/demo-1-config.js
+++ b/src/demo-1-config.js
@@ -17,6 +17,11 @@ export const flexlayout_json = {
         "weight": 50,
         "selected": 0,
         "children": [
+          {
+            "name": "Interaction trace",
+            "type": "tab",
+            "component": "int-trace",
+          },
           {
             "name": "The magic graph",
             "type": "tab",
@@ -34,16 +39,13 @@ export const flexlayout_json = {
         "children": [
           {
             "type": "tabset",
-            "name": "Robot's memory",
             "weight": 60,
             "selected": 0,
             "children": [
               {
+                "name": "Robot's memory",
                 "type": "tab",
                 "component": "memory",
-                "config": {
-                  "hasReadme": true
-                },
               },
             ]
           },
@@ -57,31 +59,25 @@ export const flexlayout_json = {
                 "children": [
                   {
                     "type": "tabset",
-                    "name": "Latest interaction",
                     "weight": 78,
                     "selected": 0,
                     "children": [
                       {
+                        "name": "Latest interaction",
                         "type": "tab",
                         "component": "latest-interaction",
-                        "config": {
-                          "hasReadme": true
-                        },
                       },
                     ]
                   },
                   {
                     "type": "tabset",
-                    "name": "Involvement",
                     "weight": 22,
                     "height": 70,
                     "children": [
                       {
+                        "name": "Involvement",
                         "type": "tab",
                         "component": "h-gauge",
-                        "config": {
-                          "hasReadme": true
-                        },
                       },
                     ]
                   },
@@ -89,15 +85,12 @@ export const flexlayout_json = {
               },
               {
                 "type": "tabset",
-                "name": "Robot video stream",
                 "weight": 75,
                 "children": [
                   {
+                    "name": "Robot video stream",
                     "type": "tab",
                     "component": "video",
-                    "config": {
-                      "hasReadme": true
-                    }
                   }
                 ]
               }
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/devel-config.js b/src/devel-config.js
index ee0d339e005bd3fb7220b677d4f63aac50d946cc..ff6964fad27321f71a719ab359452e7b224146e5 100644
--- a/src/devel-config.js
+++ b/src/devel-config.js
@@ -35,9 +35,6 @@ export const flexlayout_json = {
           {
             "type": "tab",
             "component": "int-trace",
-            "config": {
-              "hasReadme": true
-            }
           },
         ]
       },
@@ -56,7 +53,7 @@ export const flexlayout_json = {
                 "type": "tab",
                 "component": "memory",
                 "config": {
-                  "hasReadme": true
+                  "mockConfigField": true // Just to show that we can store config in here.
                 },
               },
               {
@@ -79,9 +76,6 @@ export const flexlayout_json = {
                   {
                     "type": "tab",
                     "component": "latest-interaction",
-                    "config": {
-                      "hasReadme": true
-                    },
                   },
                 ]
               },
@@ -96,9 +90,6 @@ export const flexlayout_json = {
                       {
                         "type": "tab",
                         "component": "mood",
-                        "config": {
-                          "hasReadme": true
-                        },
                       },
                     ]
                   },
@@ -109,9 +100,6 @@ export const flexlayout_json = {
                       {
                         "type": "tab",
                         "component": "h-gauge",
-                        "config": {
-                          "hasReadme": true
-                        },
                       },
                     ]
                   },
@@ -124,10 +112,7 @@ export const flexlayout_json = {
                 "children": [
                   {
                     "type": "tab",
-                    "component": "demo-video",
-                    "config": {
-                      "hasReadme": true
-                    }
+                    "component": "video",
                   }
                 ]
               }
diff --git a/src/index.js b/src/index.js
index bc5fddff32cbf27003af8ceff15694538fdb7465..73d63ae0e86e7f6b56f1f6815751746913e11138 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,15 +1,49 @@
 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 {Menu} from "./Menu";
+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) {
@@ -39,6 +73,14 @@ 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="level-1">
         <div id="menu">
@@ -49,12 +91,13 @@ class Main extends React.Component {
             <Header ros={this.ros}/>
           </div>
           <div id="board">
-            <Board ros={this.ros}/>
+            <Board ros={this.ros} store={this.store}/>
           </div>
         </div>
+        {modal}
       </div>
     )
   }
-}
+})
 
 ReactDOM.render(<Main ros={true}/>, document.getElementById("container"));
diff --git a/src/utils/configurationHelpers.js b/src/utils/configurationHelpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..f79e45fa09e13f9d215a422e4ca423d61bdb32d1
--- /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);
+}
diff --git a/src/utils/modeModal.js b/src/utils/modeModal.js
new file mode 100644
index 0000000000000000000000000000000000000000..544a581d919d1710a1b2b4f320bba56298f10f42
--- /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;
+    }
+    
+}